Merge "Ignore down touches in QS while animating" into tm-qpr-dev
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 70a23cd..93c0c4d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1139,7 +1139,6 @@
public final class CameraManager {
method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException;
method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
- field public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L; // 0xef10e60L
}
public abstract static class CameraManager.AvailabilityCallback {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4a4ba63..bab2061 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6787,6 +6787,8 @@
* {@link #ENCRYPTION_STATUS_UNSUPPORTED}, {@link #ENCRYPTION_STATUS_INACTIVE},
* {@link #ENCRYPTION_STATUS_ACTIVATING}, {@link #ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY},
* {@link #ENCRYPTION_STATUS_ACTIVE}, or {@link #ENCRYPTION_STATUS_ACTIVE_PER_USER}.
+ *
+ * @throws SecurityException if called on a parent instance.
*/
public int getStorageEncryptionStatus() {
throwIfParentInstance("getStorageEncryptionStatus");
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2ea0d82..a320f1e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -11488,7 +11488,7 @@
private void toUriInner(StringBuilder uri, String scheme, String defAction,
String defPackage, int flags) {
if (scheme != null) {
- uri.append("scheme=").append(scheme).append(';');
+ uri.append("scheme=").append(Uri.encode(scheme)).append(';');
}
if (mAction != null && !mAction.equals(defAction)) {
uri.append("action=").append(Uri.encode(mAction)).append(';');
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 9e5e8de..2e3b5d2 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1058,6 +1058,41 @@
public static final long ALWAYS_SANDBOX_DISPLAY_APIS = 185004937L; // buganizer id
/**
+ * This change id excludes the packages it is applied to from the camera compat force rotation
+ * treatment. See com.android.server.wm.DisplayRotationCompatPolicy for context.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION =
+ 263959004L; // buganizer id
+
+ /**
+ * This change id excludes the packages it is applied to from activity refresh after camera
+ * compat force rotation treatment. See com.android.server.wm.DisplayRotationCompatPolicy for
+ * context.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH = 264304459L; // buganizer id
+
+ /**
+ * This change id makes the packages it is applied to do activity refresh after camera compat
+ * force rotation treatment using "resumed -> paused -> resumed" cycle rather than "resumed ->
+ * ... -> stopped -> ... -> resumed" cycle. See
+ * com.android.server.wm.DisplayRotationCompatPolicy for context.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
+ 264301586L; // buganizer id
+
+ /**
* This change id is the gatekeeper for all treatments that force a given min aspect ratio.
* Enabling this change will allow the following min aspect ratio treatments to be applied:
* OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 5291d2b..7ccf07a 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -29,7 +29,6 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.app.ActivityThread;
import android.app.AppOpsManager;
-import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.ImageFormat;
@@ -47,7 +46,6 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemProperties;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RSIllegalArgumentException;
@@ -284,14 +282,6 @@
*/
public native static int getNumberOfCameras();
- private static final boolean sLandscapeToPortrait =
- SystemProperties.getBoolean(CameraManager.LANDSCAPE_TO_PORTRAIT_PROP, false);
-
- private static boolean shouldOverrideToPortrait() {
- return CompatChanges.isChangeEnabled(CameraManager.OVERRIDE_FRONT_CAMERA_APP_COMPAT)
- && sLandscapeToPortrait;
- }
-
/**
* Returns the information about a particular camera.
* If {@link #getNumberOfCameras()} returns N, the valid id is 0 to N-1.
@@ -301,7 +291,8 @@
* low-level failure).
*/
public static void getCameraInfo(int cameraId, CameraInfo cameraInfo) {
- boolean overrideToPortrait = shouldOverrideToPortrait();
+ boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait(
+ ActivityThread.currentApplication().getApplicationContext());
_getCameraInfo(cameraId, overrideToPortrait, cameraInfo);
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
@@ -498,7 +489,8 @@
mEventHandler = null;
}
- boolean overrideToPortrait = shouldOverrideToPortrait();
+ boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait(
+ ActivityThread.currentApplication().getApplicationContext());
return native_setup(new WeakReference<Camera>(this), cameraId,
ActivityThread.currentOpPackageName(), overrideToPortrait);
}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index be99f0f..5e2b40c 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -115,8 +115,14 @@
@ChangeId
@Overridable
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE)
- @TestApi
- public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L;
+ public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L;
+
+ /**
+ * Package-level opt in/out for the above.
+ * @hide
+ */
+ public static final String PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT =
+ "android.camera.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT";
/**
* System property for allowing the above
@@ -602,7 +608,7 @@
try {
Size displaySize = getDisplaySize();
- boolean overrideToPortrait = shouldOverrideToPortrait();
+ boolean overrideToPortrait = shouldOverrideToPortrait(mContext);
CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId,
mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait);
try {
@@ -722,7 +728,7 @@
"Camera service is currently unavailable");
}
- boolean overrideToPortrait = shouldOverrideToPortrait();
+ boolean overrideToPortrait = shouldOverrideToPortrait(mContext);
cameraUser = cameraService.connectDevice(callbacks, cameraId,
mContext.getOpPackageName(), mContext.getAttributionTag(), uid,
oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion,
@@ -1154,9 +1160,26 @@
return CameraManagerGlobal.get().getTorchStrengthLevel(cameraId);
}
- private static boolean shouldOverrideToPortrait() {
- return CompatChanges.isChangeEnabled(OVERRIDE_FRONT_CAMERA_APP_COMPAT)
- && CameraManagerGlobal.sLandscapeToPortrait;
+ /**
+ * @hide
+ */
+ public static boolean shouldOverrideToPortrait(@Nullable Context context) {
+ if (!CameraManagerGlobal.sLandscapeToPortrait) {
+ return false;
+ }
+
+ if (context != null) {
+ PackageManager packageManager = context.getPackageManager();
+
+ try {
+ return packageManager.getProperty(context.getOpPackageName(),
+ PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT).getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ // No such property
+ }
+ }
+
+ return CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT);
}
/**
@@ -2313,6 +2336,15 @@
final AvailabilityCallback callback = mCallbackMap.keyAt(i);
postSingleUpdate(callback, executor, id, null /*physicalId*/, status);
+
+ // Send the NOT_PRESENT state for unavailable physical cameras
+ if (isAvailable(status) && mUnavailablePhysicalDevices.containsKey(id)) {
+ ArrayList<String> unavailableIds = mUnavailablePhysicalDevices.get(id);
+ for (String unavailableId : unavailableIds) {
+ postSingleUpdate(callback, executor, id, unavailableId,
+ ICameraServiceListener.STATUS_NOT_PRESENT);
+ }
+ }
}
} // onStatusChangedLocked
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index a6c79b3..0c2468e 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -87,6 +87,7 @@
// TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
private ICameraDeviceUserWrapper mRemoteDevice;
+ private boolean mRemoteDeviceInit = false;
// Lock to synchronize cross-thread access to device public interface
final Object mInterfaceLock = new Object(); // access from this class and Session only!
@@ -338,6 +339,8 @@
mDeviceExecutor.execute(mCallOnOpened);
mDeviceExecutor.execute(mCallOnUnconfigured);
+
+ mRemoteDeviceInit = true;
}
}
@@ -1754,8 +1757,8 @@
}
synchronized(mInterfaceLock) {
- if (mRemoteDevice == null) {
- return; // Camera already closed
+ if (mRemoteDevice == null && mRemoteDeviceInit) {
+ return; // Camera already closed, user is not interested in errors anymore.
}
// Redirect device callback to the offline session in case we are in the middle
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index dba1a5e..6a667fe 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -251,6 +251,10 @@
@Nullable
private Boolean lastResult;
+ public FoldStateListener(Context context) {
+ this(context, folded -> {});
+ }
+
public FoldStateListener(Context context, Consumer<Boolean> listener) {
mFoldedDeviceStates = context.getResources().getIntArray(
com.android.internal.R.array.config_foldedDeviceStates);
@@ -266,5 +270,10 @@
mDelegate.accept(folded);
}
}
+
+ @Nullable
+ public Boolean getFolded() {
+ return lastResult;
+ }
}
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index e1d15de..125bdaf 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -74,6 +74,7 @@
String getUserAccount(int userId);
void setUserAccount(int userId, String accountName);
long getUserCreationTime(int userId);
+ int getUserSwitchability(int userId);
boolean isUserSwitcherEnabled(int mUserId);
boolean isRestricted(int userId);
boolean canHaveRestrictedProfile(int userId);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 414ea83..aa0ac31 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -58,7 +58,6 @@
import android.graphics.drawable.Drawable;
import android.location.LocationManager;
import android.provider.Settings;
-import android.telephony.TelephonyManager;
import android.util.AndroidException;
import android.util.ArraySet;
import android.util.Log;
@@ -1673,7 +1672,7 @@
public static final int SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED = 1 << 2;
/**
- * Result returned in {@link #getUserSwitchability()} indicating user swichability.
+ * Result returned in {@link #getUserSwitchability()} indicating user switchability.
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@@ -2040,25 +2039,16 @@
* @hide
*/
@Deprecated
- @RequiresPermission(allOf = {
- Manifest.permission.READ_PHONE_STATE,
- Manifest.permission.MANAGE_USERS}, // Can be INTERACT_ACROSS_USERS instead.
- conditional = true)
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@UserHandleAware
public boolean canSwitchUsers() {
- boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
- boolean isSystemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
- boolean inCall = false;
- TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
- if (telephonyManager != null) {
- inCall = telephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
+ try {
+ return mService.getUserSwitchability(mUserId) == SWITCHABILITY_STATUS_OK;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
}
- boolean isUserSwitchDisallowed = hasUserRestrictionForUser(DISALLOW_USER_SWITCH, mUserId);
- return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall
- && !isUserSwitchDisallowed;
}
/**
@@ -2092,34 +2082,14 @@
* @return A {@link UserSwitchabilityResult} flag indicating if the user is switchable.
* @hide
*/
- @RequiresPermission(allOf = {Manifest.permission.READ_PHONE_STATE,
- android.Manifest.permission.MANAGE_USERS,
- android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
public @UserSwitchabilityResult int getUserSwitchability(UserHandle userHandle) {
- final TelephonyManager tm =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-
- int flags = SWITCHABILITY_STATUS_OK;
- if (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
- flags |= SWITCHABILITY_STATUS_USER_IN_CALL;
+ try {
+ return mService.getUserSwitchability(userHandle.getIdentifier());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
}
- if (hasUserRestrictionForUser(DISALLOW_USER_SWITCH, userHandle)) {
- flags |= SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED;
- }
-
- // System User is always unlocked in Headless System User Mode, so ignore this flag
- if (!isHeadlessSystemUserMode()) {
- final boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
- final boolean systemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
-
- if (!allowUserSwitchingWhenSystemUserLocked && !systemUserUnlocked) {
- flags |= SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED;
- }
- }
-
- return flags;
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 94a6382..b21187a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9819,11 +9819,10 @@
"fingerprint_side_fps_auth_downtime";
/**
- * Whether or not a SFPS device is required to be interactive for auth to unlock the device.
+ * Whether or not a SFPS device is enabling the performant auth setting.
* @hide
*/
- public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED =
- "sfps_require_screen_on_to_auth_enabled";
+ public static final String SFPS_PERFORMANT_AUTH_ENABLED = "sfps_performant_auth_enabled";
/**
* Whether or not debugging is enabled.
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 4b25c88..182a497 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -52,7 +52,7 @@
/** @hide */
@StringDef (prefix = { "KEY_" }, value = {
KEY_CONTEXTUAL_ACTIONS, KEY_GROUP_KEY, KEY_IMPORTANCE, KEY_PEOPLE, KEY_SNOOZE_CRITERIA,
- KEY_TEXT_REPLIES, KEY_USER_SENTIMENT
+ KEY_TEXT_REPLIES, KEY_USER_SENTIMENT, KEY_IMPORTANCE_PROPOSAL
})
@Retention(RetentionPolicy.SOURCE)
public @interface Keys {}
@@ -122,6 +122,19 @@
public static final String KEY_IMPORTANCE = "key_importance";
/**
+ * Weaker than {@link #KEY_IMPORTANCE}, this adjustment suggests an importance rather than
+ * mandates an importance change.
+ *
+ * A notification listener can interpet this suggestion to show the user a prompt to change
+ * notification importance for the notification (or type, or app) moving forward.
+ *
+ * Data type: int, one of importance values e.g.
+ * {@link android.app.NotificationManager#IMPORTANCE_MIN}.
+ * @hide
+ */
+ public static final String KEY_IMPORTANCE_PROPOSAL = "key_importance_proposal";
+
+ /**
* Data type: float, a ranking score from 0 (lowest) to 1 (highest).
* Used to rank notifications inside that fall under the same classification (i.e. alerting,
* silenced).
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index ad2e9d5..dc4cb9f 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1711,6 +1711,8 @@
private ShortcutInfo mShortcutInfo;
private @RankingAdjustment int mRankingAdjustment;
private boolean mIsBubble;
+ // Notification assistant importance suggestion
+ private int mProposedImportance;
private static final int PARCEL_VERSION = 2;
@@ -1748,6 +1750,7 @@
out.writeParcelable(mShortcutInfo, flags);
out.writeInt(mRankingAdjustment);
out.writeBoolean(mIsBubble);
+ out.writeInt(mProposedImportance);
}
/** @hide */
@@ -1786,6 +1789,7 @@
mShortcutInfo = in.readParcelable(cl, android.content.pm.ShortcutInfo.class);
mRankingAdjustment = in.readInt();
mIsBubble = in.readBoolean();
+ mProposedImportance = in.readInt();
}
@@ -1878,6 +1882,22 @@
}
/**
+ * Returns the proposed importance provided by the {@link NotificationAssistantService}.
+ *
+ * This can be used to suggest that the user change the importance of this type of
+ * notification moving forward. A value of
+ * {@link NotificationManager#IMPORTANCE_UNSPECIFIED} means that the NAS has not recommended
+ * a change to the importance, and no UI should be shown to the user. See
+ * {@link Adjustment#KEY_IMPORTANCE_PROPOSAL}.
+ *
+ * @return the importance of the notification
+ * @hide
+ */
+ public @NotificationManager.Importance int getProposedImportance() {
+ return mProposedImportance;
+ }
+
+ /**
* If the system has overridden the group key, then this will be non-null, and this
* key should be used to bundle notifications.
*/
@@ -2041,7 +2061,7 @@
boolean noisy, ArrayList<Notification.Action> smartActions,
ArrayList<CharSequence> smartReplies, boolean canBubble,
boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo,
- int rankingAdjustment, boolean isBubble) {
+ int rankingAdjustment, boolean isBubble, int proposedImportance) {
mKey = key;
mRank = rank;
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -2067,6 +2087,7 @@
mShortcutInfo = shortcutInfo;
mRankingAdjustment = rankingAdjustment;
mIsBubble = isBubble;
+ mProposedImportance = proposedImportance;
}
/**
@@ -2107,7 +2128,8 @@
other.mIsConversation,
other.mShortcutInfo,
other.mRankingAdjustment,
- other.mIsBubble);
+ other.mIsBubble,
+ other.mProposedImportance);
}
/**
@@ -2166,7 +2188,8 @@
&& Objects.equals((mShortcutInfo == null ? 0 : mShortcutInfo.getId()),
(other.mShortcutInfo == null ? 0 : other.mShortcutInfo.getId()))
&& Objects.equals(mRankingAdjustment, other.mRankingAdjustment)
- && Objects.equals(mIsBubble, other.mIsBubble);
+ && Objects.equals(mIsBubble, other.mIsBubble)
+ && Objects.equals(mProposedImportance, other.mProposedImportance);
}
}
diff --git a/core/java/android/text/TextShaper.java b/core/java/android/text/TextShaper.java
index a1d6cc8..6da0b63 100644
--- a/core/java/android/text/TextShaper.java
+++ b/core/java/android/text/TextShaper.java
@@ -173,7 +173,7 @@
private TextShaper() {}
/**
- * An consumer interface for accepting text shape result.
+ * A consumer interface for accepting text shape result.
*/
public interface GlyphsConsumer {
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index ed9cb00..abc4926 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -853,6 +853,143 @@
"android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
/**
+ * Activity level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the activity should be excluded from the
+ * camera compatibility force rotation treatment.
+ *
+ * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
+ * orientation of the device and set opposite to natural orientation for a landscape app
+ * window. Mismatch between them can lead to camera issues like sideways or stretched
+ * viewfinder since this is one of the strongest assumptions that apps make when they implement
+ * camera previews. Since app and natural display orientations aren't guaranteed to match, the
+ * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
+ * camera and is removed once camera is closed.
+ *
+ * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
+ * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
+ * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
+ * for more details).
+ *
+ * <p>With this property set to {@code true} or unset, the system may apply the force rotation
+ * treatment to fixed orientation activities. Device manufacturers can exclude packages from the
+ * treatment using their discretion to improve display compatibility.
+ *
+ * <p>With this property set to {@code false}, the system will not apply the force rotation
+ * treatment.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <activity>
+ * <property
+ * android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"
+ * android:value="true|false"/>
+ * </activity>
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION =
+ "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
+
+ /**
+ * Activity level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the activity should be excluded
+ * from the activity "refresh" after the camera compatibility force rotation treatment.
+ *
+ * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
+ * orientation of the device and set opposite to natural orientation for a landscape app
+ * window. Mismatch between them can lead to camera issues like sideways or stretched
+ * viewfinder since this is one of the strongest assumptions that apps make when they implement
+ * camera previews. Since app and natural display orientations aren't guaranteed to match, the
+ * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
+ * camera and is removed once camera is closed.
+ *
+ * <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed ->
+ * ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle
+ * (if overridden, see {@link #PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE} for context).
+ * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
+ * camera preview and can lead to sideways or stretching issues persisting even after force
+ * rotation.
+ *
+ * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
+ * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
+ * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
+ * for more details).
+ *
+ * <p>With this property set to {@code true} or unset, the system may "refresh" activity after
+ * the force rotation treatment. Device manufacturers can exclude packages from the "refresh"
+ * using their discretion to improve display compatibility.
+ *
+ * <p>With this property set to {@code false}, the system will not "refresh" activity after the
+ * force rotation treatment.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <activity>
+ * <property
+ * android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH"
+ * android:value="true|false"/>
+ * </activity>
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH =
+ "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
+
+ /**
+ * Activity level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the activity should be or shouldn't be
+ * "refreshed" after the camera compatibility force rotation treatment using "paused ->
+ * resumed" cycle rather than "stopped -> resumed".
+ *
+ * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
+ * orientation of the device and set opposite to natural orientation for a landscape app
+ * window. Mismatch between them can lead to camera issues like sideways or stretched
+ * viewfinder since this is one of the strongest assumptions that apps make when they implement
+ * camera previews. Since app and natural display orientations aren't guaranteed to match, the
+ * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
+ * camera and is removed once camera is closed.
+ *
+ * <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed ->
+ * ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle
+ * (if overridden by device manufacturers or using this property). This allows to clear cached
+ * values in apps (e.g., display or camera rotation) that influence camera preview and can lead
+ * to sideways or stretching issues persisting even after force rotation.
+ *
+ * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
+ * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
+ * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
+ * for more details).
+ *
+ * <p>Device manufacturers can override packages to "refresh" via "resumed -> paused -> resumed"
+ * cycle using their discretion to improve display compatibility.
+ *
+ * <p>With this property set to {@code true}, the system will "refresh" activity after the
+ * force rotation treatment using "resumed -> paused -> resumed" cycle.
+ *
+ * <p>With this property set to {@code false}, the system will not "refresh" activity after the
+ * force rotation treatment using "resumed -> paused -> resumed" cycle even if the device
+ * manufacturer adds the corresponding override.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <activity>
+ * <property
+ * android:name="android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE"
+ * android:value="true|false"/>
+ * </activity>
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
+ "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
+
+ /**
* @hide
*/
public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index d067d4b..497f066 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -66,8 +66,7 @@
import java.util.function.Consumer;
/**
- * <p>The {@link ContentCaptureManager} provides additional ways for for apps to
- * integrate with the content capture subsystem.
+ * <p>Provides additional ways for apps to integrate with the content capture subsystem.
*
* <p>Content capture provides real-time, continuous capture of application activity, display and
* events to an intelligence service that is provided by the Android system. The intelligence
diff --git a/core/java/android/webkit/WebResourceError.java b/core/java/android/webkit/WebResourceError.java
index 11f1b6f1..4c87489 100644
--- a/core/java/android/webkit/WebResourceError.java
+++ b/core/java/android/webkit/WebResourceError.java
@@ -19,7 +19,7 @@
import android.annotation.SystemApi;
/**
- * Encapsulates information about errors occured during loading of web resources. See
+ * Encapsulates information about errors that occurred during loading of web resources. See
* {@link WebViewClient#onReceivedError(WebView, WebResourceRequest, WebResourceError) WebViewClient.onReceivedError(WebView, WebResourceRequest, WebResourceError)}
*/
public abstract class WebResourceError {
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index 1024e2e..4a4f561 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -18,10 +18,8 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
-import android.view.RemoteAnimationTarget;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -52,8 +50,6 @@
@SwipeEdge
private final int mSwipeEdge;
- @Nullable
- private final RemoteAnimationTarget mDepartingAnimationTarget;
/**
* Creates a new {@link BackEvent} instance.
@@ -62,16 +58,12 @@
* @param touchY Absolute Y location of the touch point of this event.
* @param progress Value between 0 and 1 on how far along the back gesture is.
* @param swipeEdge Indicates which edge the swipe starts from.
- * @param departingAnimationTarget The remote animation target of the departing application
- * window.
*/
- public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge,
- @Nullable RemoteAnimationTarget departingAnimationTarget) {
+ public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge) {
mTouchX = touchX;
mTouchY = touchY;
mProgress = progress;
mSwipeEdge = swipeEdge;
- mDepartingAnimationTarget = departingAnimationTarget;
}
private BackEvent(@NonNull Parcel in) {
@@ -79,7 +71,6 @@
mTouchY = in.readFloat();
mProgress = in.readFloat();
mSwipeEdge = in.readInt();
- mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
}
public static final Creator<BackEvent> CREATOR = new Creator<BackEvent>() {
@@ -105,7 +96,6 @@
dest.writeFloat(mTouchY);
dest.writeFloat(mProgress);
dest.writeInt(mSwipeEdge);
- dest.writeTypedObject(mDepartingAnimationTarget, flags);
}
/**
@@ -136,16 +126,6 @@
return mSwipeEdge;
}
- /**
- * Returns the {@link RemoteAnimationTarget} of the top departing application window,
- * or {@code null} if the top window should not be moved for the current type of back
- * destination.
- */
- @Nullable
- public RemoteAnimationTarget getDepartingAnimationTarget() {
- return mDepartingAnimationTarget;
- }
-
@Override
public String toString() {
return "BackEvent{"
@@ -153,7 +133,6 @@
+ ", mTouchY=" + mTouchY
+ ", mProgress=" + mProgress
+ ", mSwipeEdge" + mSwipeEdge
- + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+ "}";
}
}
diff --git a/core/java/android/window/BackMotionEvent.aidl b/core/java/android/window/BackMotionEvent.aidl
new file mode 100644
index 0000000..7c675c3
--- /dev/null
+++ b/core/java/android/window/BackMotionEvent.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/**
+ * @hide
+ */
+parcelable BackMotionEvent;
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
new file mode 100644
index 0000000..8012a1c
--- /dev/null
+++ b/core/java/android/window/BackMotionEvent.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.RemoteAnimationTarget;
+
+/**
+ * Object used to report back gesture progress. Holds information about a {@link BackEvent} plus
+ * any {@link RemoteAnimationTarget} the gesture manipulates.
+ *
+ * @see BackEvent
+ * @hide
+ */
+public final class BackMotionEvent implements Parcelable {
+ private final float mTouchX;
+ private final float mTouchY;
+ private final float mProgress;
+
+ @BackEvent.SwipeEdge
+ private final int mSwipeEdge;
+ @Nullable
+ private final RemoteAnimationTarget mDepartingAnimationTarget;
+
+ /**
+ * Creates a new {@link BackMotionEvent} instance.
+ *
+ * @param touchX Absolute X location of the touch point of this event.
+ * @param touchY Absolute Y location of the touch point of this event.
+ * @param progress Value between 0 and 1 on how far along the back gesture is.
+ * @param swipeEdge Indicates which edge the swipe starts from.
+ * @param departingAnimationTarget The remote animation target of the departing
+ * application window.
+ */
+ public BackMotionEvent(float touchX, float touchY, float progress,
+ @BackEvent.SwipeEdge int swipeEdge,
+ @Nullable RemoteAnimationTarget departingAnimationTarget) {
+ mTouchX = touchX;
+ mTouchY = touchY;
+ mProgress = progress;
+ mSwipeEdge = swipeEdge;
+ mDepartingAnimationTarget = departingAnimationTarget;
+ }
+
+ private BackMotionEvent(@NonNull Parcel in) {
+ mTouchX = in.readFloat();
+ mTouchY = in.readFloat();
+ mProgress = in.readFloat();
+ mSwipeEdge = in.readInt();
+ mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
+ }
+
+ @NonNull
+ public static final Creator<BackMotionEvent> CREATOR = new Creator<BackMotionEvent>() {
+ @Override
+ public BackMotionEvent createFromParcel(Parcel in) {
+ return new BackMotionEvent(in);
+ }
+
+ @Override
+ public BackMotionEvent[] newArray(int size) {
+ return new BackMotionEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeFloat(mTouchX);
+ dest.writeFloat(mTouchY);
+ dest.writeFloat(mProgress);
+ dest.writeInt(mSwipeEdge);
+ dest.writeTypedObject(mDepartingAnimationTarget, flags);
+ }
+
+ /**
+ * Returns the progress of a {@link BackEvent}.
+ *
+ * @see BackEvent#getProgress()
+ */
+ @FloatRange(from = 0, to = 1)
+ public float getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Returns the absolute X location of the touch point.
+ */
+ public float getTouchX() {
+ return mTouchX;
+ }
+
+ /**
+ * Returns the absolute Y location of the touch point.
+ */
+ public float getTouchY() {
+ return mTouchY;
+ }
+
+ /**
+ * Returns the screen edge that the swipe starts from.
+ */
+ @BackEvent.SwipeEdge
+ public int getSwipeEdge() {
+ return mSwipeEdge;
+ }
+
+ /**
+ * Returns the {@link RemoteAnimationTarget} of the top departing application window,
+ * or {@code null} if the top window should not be moved for the current type of back
+ * destination.
+ */
+ @Nullable
+ public RemoteAnimationTarget getDepartingAnimationTarget() {
+ return mDepartingAnimationTarget;
+ }
+
+ @Override
+ public String toString() {
+ return "BackMotionEvent{"
+ + "mTouchX=" + mTouchX
+ + ", mTouchY=" + mTouchY
+ + ", mProgress=" + mProgress
+ + ", mSwipeEdge" + mSwipeEdge
+ + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+ + "}";
+ }
+}
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index dd4385c..38c52e7 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -40,7 +40,7 @@
private final SpringAnimation mSpring;
private ProgressCallback mCallback;
private float mProgress = 0;
- private BackEvent mLastBackEvent;
+ private BackMotionEvent mLastBackEvent;
private boolean mStarted = false;
private void setProgress(float progress) {
@@ -82,9 +82,9 @@
/**
* Sets a new target position for the back progress.
*
- * @param event the {@link BackEvent} containing the latest target progress.
+ * @param event the {@link BackMotionEvent} containing the latest target progress.
*/
- public void onBackProgressed(BackEvent event) {
+ public void onBackProgressed(BackMotionEvent event) {
if (!mStarted) {
return;
}
@@ -95,11 +95,11 @@
/**
* Starts the back progress animation.
*
- * @param event the {@link BackEvent} that started the gesture.
+ * @param event the {@link BackMotionEvent} that started the gesture.
* @param callback the back callback to invoke for the gesture. It will receive back progress
* dispatches as the progress animation updates.
*/
- public void onBackStarted(BackEvent event, ProgressCallback callback) {
+ public void onBackStarted(BackMotionEvent event, ProgressCallback callback) {
reset();
mLastBackEvent = event;
mCallback = callback;
@@ -129,8 +129,7 @@
}
mCallback.onProgressUpdate(
new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(),
- progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge(),
- mLastBackEvent.getDepartingAnimationTarget()));
+ progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge()));
}
}
diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl
index 6af8ddd..159c0e8 100644
--- a/core/java/android/window/IOnBackInvokedCallback.aidl
+++ b/core/java/android/window/IOnBackInvokedCallback.aidl
@@ -17,7 +17,7 @@
package android.window;
-import android.window.BackEvent;
+import android.window.BackMotionEvent;
/**
* Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager
@@ -30,18 +30,19 @@
* Called when a back gesture has been started, or back button has been pressed down.
* Wraps {@link OnBackInvokedCallback#onBackStarted(BackEvent)}.
*
- * @param backEvent The {@link BackEvent} containing information about the touch or button press.
+ * @param backMotionEvent The {@link BackMotionEvent} containing information about the touch
+ * or button press.
*/
- void onBackStarted(in BackEvent backEvent);
+ void onBackStarted(in BackMotionEvent backMotionEvent);
/**
* Called on back gesture progress.
* Wraps {@link OnBackInvokedCallback#onBackProgressed(BackEvent)}.
*
- * @param backEvent The {@link BackEvent} containing information about the latest touch point
- * and the progress that the back animation should seek to.
+ * @param backMotionEvent The {@link BackMotionEvent} containing information about the latest
+ * touch point and the progress that the back animation should seek to.
*/
- void onBackProgressed(in BackEvent backEvent);
+ void onBackProgressed(in BackMotionEvent backMotionEvent);
/**
* Called when a back gesture or back button press has been cancelled.
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index e6bb1f6..0032b9c 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -40,7 +40,8 @@
void unregisterTaskOrganizer(ITaskOrganizer organizer);
/** Creates a persistent root task in WM for a particular windowing-mode. */
- void createRootTask(int displayId, int windowingMode, IBinder launchCookie);
+ void createRootTask(int displayId, int windowingMode, IBinder launchCookie,
+ boolean removeWithTaskOrganizer);
/** Deletes a persistent root task in WM */
boolean deleteRootTask(in WindowContainerToken task);
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index c9ddf92..203d79a 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -71,20 +71,42 @@
*
* This is needed in case we need to launch a placeholder Activity to split below a transparent
* always-expand Activity.
+ *
+ * This should not be used with {@link #mPairedActivityToken}.
*/
@Nullable
private final IBinder mPairedPrimaryFragmentToken;
+ /**
+ * The Activity token to place the new TaskFragment on top of.
+ * When it is set, the new TaskFragment will be positioned right above the target Activity.
+ * Otherwise, the new TaskFragment will be positioned on the top of the Task by default.
+ *
+ * This is needed in case we need to place an Activity into TaskFragment to launch placeholder
+ * below a transparent always-expand Activity, or when there is another Intent being started in
+ * a TaskFragment above.
+ *
+ * This should not be used with {@link #mPairedPrimaryFragmentToken}.
+ */
+ @Nullable
+ private final IBinder mPairedActivityToken;
+
private TaskFragmentCreationParams(
@NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect initialBounds,
- @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken) {
+ @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
+ @Nullable IBinder pairedActivityToken) {
+ if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
+ throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
+ + " pairedActivityToken should not be set at the same time.");
+ }
mOrganizer = organizer;
mFragmentToken = fragmentToken;
mOwnerToken = ownerToken;
mInitialBounds.set(initialBounds);
mWindowingMode = windowingMode;
mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
+ mPairedActivityToken = pairedActivityToken;
}
@NonNull
@@ -121,6 +143,15 @@
return mPairedPrimaryFragmentToken;
}
+ /**
+ * TODO(b/232476698): remove the hide with adding CTS for this in next release.
+ * @hide
+ */
+ @Nullable
+ public IBinder getPairedActivityToken() {
+ return mPairedActivityToken;
+ }
+
private TaskFragmentCreationParams(Parcel in) {
mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
mFragmentToken = in.readStrongBinder();
@@ -128,6 +159,7 @@
mInitialBounds.readFromParcel(in);
mWindowingMode = in.readInt();
mPairedPrimaryFragmentToken = in.readStrongBinder();
+ mPairedActivityToken = in.readStrongBinder();
}
/** @hide */
@@ -139,6 +171,7 @@
mInitialBounds.writeToParcel(dest, flags);
dest.writeInt(mWindowingMode);
dest.writeStrongBinder(mPairedPrimaryFragmentToken);
+ dest.writeStrongBinder(mPairedActivityToken);
}
@NonNull
@@ -164,6 +197,7 @@
+ " initialBounds=" + mInitialBounds
+ " windowingMode=" + mWindowingMode
+ " pairedFragmentToken=" + mPairedPrimaryFragmentToken
+ + " pairedActivityToken=" + mPairedActivityToken
+ "}";
}
@@ -194,6 +228,9 @@
@Nullable
private IBinder mPairedPrimaryFragmentToken;
+ @Nullable
+ private IBinder mPairedActivityToken;
+
public Builder(@NonNull TaskFragmentOrganizerToken organizer,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
mOrganizer = organizer;
@@ -224,6 +261,8 @@
* This is needed in case we need to launch a placeholder Activity to split below a
* transparent always-expand Activity.
*
+ * This should not be used with {@link #setPairedActivityToken}.
+ *
* TODO(b/232476698): remove the hide with adding CTS for this in next release.
* @hide
*/
@@ -233,11 +272,32 @@
return this;
}
+ /**
+ * Sets the Activity token to place the new TaskFragment on top of.
+ * When it is set, the new TaskFragment will be positioned right above the target Activity.
+ * Otherwise, the new TaskFragment will be positioned on the top of the Task by default.
+ *
+ * This is needed in case we need to place an Activity into TaskFragment to launch
+ * placeholder below a transparent always-expand Activity, or when there is another Intent
+ * being started in a TaskFragment above.
+ *
+ * This should not be used with {@link #setPairedPrimaryFragmentToken}.
+ *
+ * TODO(b/232476698): remove the hide with adding CTS for this in next release.
+ * @hide
+ */
+ @NonNull
+ public Builder setPairedActivityToken(@Nullable IBinder activityToken) {
+ mPairedActivityToken = activityToken;
+ return this;
+ }
+
/** Constructs the options to create TaskFragment with. */
@NonNull
public TaskFragmentCreationParams build() {
return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
- mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken);
+ mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken,
+ mPairedActivityToken);
}
}
}
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index bffd4e4..02878f8 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -152,15 +152,31 @@
* @param windowingMode Windowing mode to put the root task in.
* @param launchCookie Launch cookie to associate with the task so that is can be identified
* when the {@link ITaskOrganizer#onTaskAppeared} callback is called.
+ * @param removeWithTaskOrganizer True if this task should be removed when organizer destroyed.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie,
+ boolean removeWithTaskOrganizer) {
+ try {
+ mTaskOrganizerController.createRootTask(displayId, windowingMode, launchCookie,
+ removeWithTaskOrganizer);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a persistent root task in WM for a particular windowing-mode.
+ * @param displayId The display to create the root task on.
+ * @param windowingMode Windowing mode to put the root task in.
+ * @param launchCookie Launch cookie to associate with the task so that is can be identified
+ * when the {@link ITaskOrganizer#onTaskAppeared} callback is called.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
@Nullable
public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie) {
- try {
- mTaskOrganizerController.createRootTask(displayId, windowingMode, launchCookie);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ createRootTask(displayId, windowingMode, launchCookie, false /* removeWithTaskOrganizer */);
}
/** Deletes a persistent root task in WM */
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index fda39c1..dd9483a 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -229,19 +229,21 @@
}
@Override
- public void onBackStarted(BackEvent backEvent) {
+ public void onBackStarted(BackMotionEvent backEvent) {
Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
mProgressAnimator.onBackStarted(backEvent, event ->
callback.onBackProgressed(event));
- callback.onBackStarted(backEvent);
+ callback.onBackStarted(new BackEvent(
+ backEvent.getTouchX(), backEvent.getTouchY(),
+ backEvent.getProgress(), backEvent.getSwipeEdge()));
}
});
}
@Override
- public void onBackProgressed(BackEvent backEvent) {
+ public void onBackProgressed(BackMotionEvent backEvent) {
Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 1fcfe7d..011232f 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2953,12 +2953,24 @@
private boolean shouldShowStickyContentPreviewNoOrientationCheck() {
return shouldShowTabs()
- && mMultiProfilePagerAdapter.getListAdapterForUserHandle(
- UserHandle.of(UserHandle.myUserId())).getCount() > 0
+ && (mMultiProfilePagerAdapter.getListAdapterForUserHandle(
+ UserHandle.of(UserHandle.myUserId())).getCount() > 0
+ || shouldShowContentPreviewWhenEmpty())
&& shouldShowContentPreview();
}
/**
+ * This method could be used to override the default behavior when we hide the preview area
+ * when the current tab doesn't have any items.
+ *
+ * @return true if we want to show the content preview area even if the tab for the current
+ * user is empty
+ */
+ protected boolean shouldShowContentPreviewWhenEmpty() {
+ return false;
+ }
+
+ /**
* @return true if we want to show the content preview area
*/
protected boolean shouldShowContentPreview() {
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index f8b764b..19e4ba4 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -209,7 +209,7 @@
* <p>Can only be used if there is a work profile.
* <p>Possible values can be either {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}.
*/
- static final String EXTRA_SELECTED_PROFILE =
+ protected static final String EXTRA_SELECTED_PROFILE =
"com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE";
/**
@@ -224,8 +224,8 @@
static final String EXTRA_CALLING_USER =
"com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER";
- static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL;
- static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK;
+ protected static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL;
+ protected static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK;
private BroadcastReceiver mWorkProfileStateReceiver;
private UserHandle mHeaderCreatorUser;
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index f370ebd..9d6b29e 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -17,6 +17,7 @@
package android.window;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
@@ -60,8 +61,8 @@
private OnBackAnimationCallback mCallback1;
@Mock
private OnBackAnimationCallback mCallback2;
- @Mock
- private BackEvent mBackEvent;
+ private final BackMotionEvent mBackEvent = new BackMotionEvent(
+ 0, 0, 0, BackEvent.EDGE_LEFT, null);
@Before
public void setUp() throws Exception {
@@ -89,12 +90,12 @@
captor.capture());
captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback1).onBackStarted(mBackEvent);
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
verifyZeroInteractions(mCallback2);
captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback2).onBackStarted(mBackEvent);
+ verify(mCallback2).onBackStarted(any(BackEvent.class));
verifyNoMoreInteractions(mCallback1);
}
@@ -114,7 +115,7 @@
assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY);
captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback1).onBackStarted(mBackEvent);
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
}
@Test
@@ -152,6 +153,6 @@
verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback2).onBackStarted(mBackEvent);
+ verify(mCallback2).onBackStarted(any(BackEvent.class));
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 54edd9e..666b472 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -48,7 +48,7 @@
// TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
- return 1;
+ return 2;
}
@NonNull
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 87fa63d..00e13c9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -191,10 +191,25 @@
*/
void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
+ createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode,
+ null /* pairedActivityToken */);
+ }
+
+ /**
+ * @param ownerToken The token of the activity that creates this task fragment. It does not
+ * have to be a child of this task fragment, but must belong to the same task.
+ * @param pairedActivityToken The token of the activity that will be reparented to this task
+ * fragment. When it is not {@code null}, the task fragment will be
+ * positioned right above it.
+ */
+ void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
+ @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode,
+ @Nullable IBinder pairedActivityToken) {
final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
getOrganizerToken(), fragmentToken, ownerToken)
.setInitialBounds(bounds)
.setWindowingMode(windowingMode)
+ .setPairedActivityToken(pairedActivityToken)
.build();
createTaskFragment(wct, fragmentOptions);
}
@@ -216,8 +231,10 @@
private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
@WindowingMode int windowingMode, @NonNull Activity activity) {
- createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
- wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
+ final IBinder reparentActivityToken = activity.getActivityToken();
+ createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode,
+ reparentActivityToken);
+ wct.reparentActivityToTaskFragment(fragmentToken, reparentActivityToken);
}
void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 5e771f9..8b3a471 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1499,7 +1499,7 @@
* Returns the active split that has the provided containers as primary and secondary or as
* secondary and primary, if available.
*/
- @VisibleForTesting
+ @GuardedBy("mLock")
@Nullable
SplitContainer getActiveSplitForContainers(
@NonNull TaskFragmentContainer firstContainer,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index c6d9592..668a7d5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -268,10 +268,11 @@
container = mController.newContainer(activity, taskId);
final int windowingMode = mController.getTaskContainer(taskId)
.getWindowingModeForSplitTaskFragment(bounds);
- createTaskFragment(wct, container.getTaskFragmentToken(), activity.getActivityToken(),
- bounds, windowingMode);
+ final IBinder reparentActivityToken = activity.getActivityToken();
+ createTaskFragment(wct, container.getTaskFragmentToken(), reparentActivityToken,
+ bounds, windowingMode, reparentActivityToken);
wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(),
- activity.getActivityToken());
+ reparentActivityToken);
} else {
resizeTaskFragmentIfRegistered(wct, container, bounds);
final int windowingMode = mController.getTaskContainer(taskId)
@@ -554,9 +555,9 @@
final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
mController.getSplitAttributesCalculator();
final SplitAttributes defaultSplitAttributes = rule.getDefaultSplitAttributes();
- final boolean isDefaultMinSizeSatisfied = rule.checkParentMetrics(taskWindowMetrics);
+ final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics);
if (calculator == null) {
- if (!isDefaultMinSizeSatisfied) {
+ if (!areDefaultConstraintsSatisfied) {
return EXPAND_CONTAINERS_ATTRIBUTES;
}
return sanitizeSplitAttributes(taskProperties, defaultSplitAttributes,
@@ -567,7 +568,7 @@
taskConfiguration.windowConfiguration);
final SplitAttributesCalculatorParams params = new SplitAttributesCalculatorParams(
taskWindowMetrics, taskConfiguration, windowLayoutInfo, defaultSplitAttributes,
- isDefaultMinSizeSatisfied, rule.getTag());
+ areDefaultConstraintsSatisfied, rule.getTag());
final SplitAttributes splitAttributes = calculator.apply(params);
return sanitizeSplitAttributes(taskProperties, splitAttributes, minDimensionsPair);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 076856c..17814c6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -141,12 +141,26 @@
mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer;
if (pairedPrimaryContainer != null) {
+ // The TaskFragment will be positioned right above the paired container.
if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
throw new IllegalArgumentException(
"pairedPrimaryContainer must be in the same Task");
}
final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
taskContainer.mContainers.add(primaryIndex + 1, this);
+ } else if (pendingAppearedActivity != null) {
+ // The TaskFragment will be positioned right above the pending appeared Activity. If any
+ // existing TaskFragment is empty with pending Intent, it is likely that the Activity of
+ // the pending Intent hasn't been created yet, so the new Activity should be below the
+ // empty TaskFragment.
+ int i = taskContainer.mContainers.size() - 1;
+ for (; i >= 0; i--) {
+ final TaskFragmentContainer container = taskContainer.mContainers.get(i);
+ if (!container.isEmpty() || container.getPendingAppearedIntent() == null) {
+ break;
+ }
+ }
+ taskContainer.mContainers.add(i + 1, this);
} else {
taskContainer.mContainers.add(this);
}
@@ -500,6 +514,8 @@
}
if (!shouldFinishDependent) {
+ // Always finish the placeholder when the primary is finished.
+ finishPlaceholderIfAny(wct, presenter);
return;
}
@@ -526,6 +542,28 @@
mActivitiesToFinishOnExit.clear();
}
+ @GuardedBy("mController.mLock")
+ private void finishPlaceholderIfAny(@NonNull WindowContainerTransaction wct,
+ @NonNull SplitPresenter presenter) {
+ final List<TaskFragmentContainer> containersToRemove = new ArrayList<>();
+ for (TaskFragmentContainer container : mContainersToFinishOnExit) {
+ if (container.mIsFinished) {
+ continue;
+ }
+ final SplitContainer splitContainer = mController.getActiveSplitForContainers(
+ this, container);
+ if (splitContainer != null && splitContainer.isPlaceholderContainer()
+ && splitContainer.getSecondaryContainer() == container) {
+ // Remove the placeholder secondary TaskFragment.
+ containersToRemove.add(container);
+ }
+ }
+ mContainersToFinishOnExit.removeAll(containersToRemove);
+ for (TaskFragmentContainer container : containersToRemove) {
+ container.finish(false /* shouldFinishDependent */, presenter, wct, mController);
+ }
+ }
+
boolean isFinished() {
return mIsFinished;
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 7d9d8b0..78b85e6 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -154,17 +154,52 @@
null /* pendingAppearedIntent */, taskContainer, mController,
null /* pairedPrimaryContainer */);
doReturn(container1).when(mController).getContainerWithActivity(mActivity);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
// The activity is requested to be reparented, so don't finish it.
- container0.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
+ container0.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController);
verify(mTransaction, never()).finishActivity(any());
- verify(mPresenter).deleteTaskFragment(wct, container0.getTaskFragmentToken());
+ verify(mPresenter).deleteTaskFragment(mTransaction, container0.getTaskFragmentToken());
verify(mController).removeContainer(container0);
}
@Test
+ public void testFinish_alwaysFinishPlaceholder() {
+ // Register container1 as a placeholder
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
+ final TaskFragmentInfo info0 = createMockTaskFragmentInfo(container0, mActivity);
+ container0.setInfo(mTransaction, info0);
+ final Activity placeholderActivity = createMockActivity();
+ final TaskFragmentContainer container1 = new TaskFragmentContainer(placeholderActivity,
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
+ final TaskFragmentInfo info1 = createMockTaskFragmentInfo(container1, placeholderActivity);
+ container1.setInfo(mTransaction, info1);
+ final SplitAttributes splitAttributes = new SplitAttributes.Builder().build();
+ final SplitPlaceholderRule rule = new SplitPlaceholderRule.Builder(new Intent(),
+ mActivity::equals, (java.util.function.Predicate) i -> false,
+ (java.util.function.Predicate) w -> true)
+ .setDefaultSplitAttributes(splitAttributes)
+ .build();
+ mController.registerSplit(mTransaction, container0, mActivity, container1, rule,
+ splitAttributes);
+
+ // The placeholder TaskFragment should be finished even if the primary is finished with
+ // shouldFinishDependent = false.
+ container0.finish(false /* shouldFinishDependent */, mPresenter, mTransaction, mController);
+
+ assertTrue(container0.isFinished());
+ assertTrue(container1.isFinished());
+ verify(mPresenter).deleteTaskFragment(mTransaction, container0.getTaskFragmentToken());
+ verify(mPresenter).deleteTaskFragment(mTransaction, container1.getTaskFragmentToken());
+ verify(mController).removeContainer(container0);
+ verify(mController).removeContainer(container1);
+ }
+
+ @Test
public void testSetInfo() {
final TaskContainer taskContainer = createTestTaskContainer();
// Pending activity should be cleared when it has appeared on server side.
@@ -493,8 +528,6 @@
final TaskFragmentContainer tf1 = new TaskFragmentContainer(
null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
null /* pairedPrimaryTaskFragment */);
- taskContainer.mContainers.add(tf0);
- taskContainer.mContainers.add(tf1);
// When tf2 is created with using tf0 as pairedPrimaryContainer, tf2 should be inserted
// right above tf0.
@@ -506,6 +539,26 @@
}
@Test
+ public void testNewContainerWithPairedPendingAppearedActivity() {
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer tf0 = new TaskFragmentContainer(
+ createMockActivity(), null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryTaskFragment */);
+ final TaskFragmentContainer tf1 = new TaskFragmentContainer(
+ null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
+ null /* pairedPrimaryTaskFragment */);
+
+ // When tf2 is created with pendingAppearedActivity, tf2 should be inserted below any
+ // TaskFragment without any Activity.
+ final TaskFragmentContainer tf2 = new TaskFragmentContainer(
+ createMockActivity(), null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryTaskFragment */);
+ assertEquals(0, taskContainer.indexOf(tf0));
+ assertEquals(1, taskContainer.indexOf(tf2));
+ assertEquals(2, taskContainer.indexOf(tf1));
+ }
+
+ @Test
public void testIsVisible() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 367e3b9..5de5365 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
index 0bcaa53..b7ff96e 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
@@ -18,7 +18,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:tint="@color/decor_button_dark_color">
<path
android:fillColor="@android:color/white" android:pathData="M6,21V19H18V21Z"/>
</vector>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index e58e785..97a9fed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -256,12 +256,30 @@
}
}
+ /**
+ * Creates a persistent root task in WM for a particular windowing-mode.
+ * @param displayId The display to create the root task on.
+ * @param windowingMode Windowing mode to put the root task in.
+ * @param listener The listener to get the created task callback.
+ */
public void createRootTask(int displayId, int windowingMode, TaskListener listener) {
- ProtoLog.v(WM_SHELL_TASK_ORG, "createRootTask() displayId=%d winMode=%d listener=%s",
+ createRootTask(displayId, windowingMode, listener, false /* removeWithTaskOrganizer */);
+ }
+
+ /**
+ * Creates a persistent root task in WM for a particular windowing-mode.
+ * @param displayId The display to create the root task on.
+ * @param windowingMode Windowing mode to put the root task in.
+ * @param listener The listener to get the created task callback.
+ * @param removeWithTaskOrganizer True if this task should be removed when organizer destroyed.
+ */
+ public void createRootTask(int displayId, int windowingMode, TaskListener listener,
+ boolean removeWithTaskOrganizer) {
+ ProtoLog.v(WM_SHELL_TASK_ORG, "createRootTask() displayId=%d winMode=%d listener=%s" ,
displayId, windowingMode, listener.toString());
final IBinder cookie = new Binder();
setPendingLaunchCookieListener(cookie, listener);
- super.createRootTask(displayId, windowingMode, cookie);
+ super.createRootTask(displayId, windowingMode, cookie, removeWithTaskOrganizer);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index cbcd949..2363092 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -51,6 +51,7 @@
import android.view.SurfaceControl;
import android.window.BackAnimationAdaptor;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IBackAnimationRunner;
import android.window.IBackNaviAnimationController;
@@ -173,11 +174,11 @@
boolean consumed = false;
if (mWaitingAnimation && mOnBackCallback != null) {
if (mTriggerBack) {
- final BackEvent backFinish = mTouchTracker.createProgressEvent(1);
+ final BackMotionEvent backFinish = mTouchTracker.createProgressEvent(1);
dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
dispatchOnBackInvoked(mOnBackCallback);
} else {
- final BackEvent backFinish = mTouchTracker.createProgressEvent(0);
+ final BackMotionEvent backFinish = mTouchTracker.createProgressEvent(0);
dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
dispatchOnBackCancelled(mOnBackCallback);
}
@@ -480,7 +481,7 @@
if (!mBackGestureStarted || mBackNavigationInfo == null) {
return;
}
- final BackEvent backEvent = mTouchTracker.createProgressEvent();
+ final BackMotionEvent backEvent = mTouchTracker.createProgressEvent();
if (USE_TRANSITION && mBackAnimationController != null && mAnimationTarget != null) {
dispatchOnBackProgressed(mBackToLauncherCallback, backEvent);
} else if (mEnableAnimations.get()) {
@@ -573,7 +574,7 @@
}
private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
- BackEvent backEvent) {
+ BackMotionEvent backEvent) {
if (callback == null) {
return;
}
@@ -611,7 +612,7 @@
}
private void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
- BackEvent backEvent) {
+ BackMotionEvent backEvent) {
if (callback == null) {
return;
}
@@ -730,7 +731,7 @@
}
dispatchOnBackStarted(mBackToLauncherCallback,
mTouchTracker.createStartEvent(mAnimationTarget));
- final BackEvent backInit = mTouchTracker.createProgressEvent();
+ final BackMotionEvent backInit = mTouchTracker.createProgressEvent();
if (!mCachingBackDispatcher.consume()) {
dispatchOnBackProgressed(mBackToLauncherCallback, backInit);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index ccfac65..695ef4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -19,6 +19,7 @@
import android.os.SystemProperties;
import android.view.RemoteAnimationTarget;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
/**
* Helper class to record the touch location for gesture and generate back events.
@@ -82,11 +83,11 @@
mSwipeEdge = BackEvent.EDGE_LEFT;
}
- BackEvent createStartEvent(RemoteAnimationTarget target) {
- return new BackEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
+ BackMotionEvent createStartEvent(RemoteAnimationTarget target) {
+ return new BackMotionEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
}
- BackEvent createProgressEvent() {
+ BackMotionEvent createProgressEvent() {
float progressThreshold = PROGRESS_THRESHOLD >= 0
? PROGRESS_THRESHOLD : mProgressThreshold;
progressThreshold = progressThreshold == 0 ? 1 : progressThreshold;
@@ -109,8 +110,8 @@
return createProgressEvent(progress);
}
- BackEvent createProgressEvent(float progress) {
- return new BackEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
+ BackMotionEvent createProgressEvent(float progress) {
+ return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
}
public void setProgressThreshold(float progressThreshold) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index dd8afff..71e15c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -973,21 +973,59 @@
}
/**
- * Adds and expands bubble for a specific intent. These bubbles are <b>not</b> backed by a n
- * otification and remain until the user dismisses the bubble or bubble stack. Only one intent
- * bubble is supported at a time.
+ * This method has different behavior depending on:
+ * - if an app bubble exists
+ * - if an app bubble is expanded
+ *
+ * If no app bubble exists, this will add and expand a bubble with the provided intent. The
+ * intent must be explicit (i.e. include a package name or fully qualified component class name)
+ * and the activity for it should be resizable.
+ *
+ * If an app bubble exists, this will toggle the visibility of it, i.e. if the app bubble is
+ * expanded, calling this method will collapse it. If the app bubble is not expanded, calling
+ * this method will expand it.
+ *
+ * These bubbles are <b>not</b> backed by a notification and remain until the user dismisses
+ * the bubble or bubble stack.
+ *
+ * Some notes:
+ * - Only one app bubble is supported at a time
+ * - Calling this method with a different intent than the existing app bubble will do nothing
*
* @param intent the intent to display in the bubble expanded view.
*/
- public void showAppBubble(Intent intent) {
- if (intent == null || intent.getPackage() == null) return;
+ public void showOrHideAppBubble(Intent intent) {
+ if (intent == null || intent.getPackage() == null) {
+ Log.w(TAG, "App bubble failed to show, invalid intent: " + intent
+ + ((intent != null) ? " with package: " + intent.getPackage() : " "));
+ return;
+ }
PackageManager packageManager = getPackageManagerForUser(mContext, mCurrentUserId);
if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return;
- Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor);
- b.setShouldAutoExpand(true);
- inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
+ Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE);
+ if (existingAppBubble != null) {
+ BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
+ if (isStackExpanded()) {
+ if (selectedBubble != null && KEY_APP_BUBBLE.equals(selectedBubble.getKey())) {
+ // App bubble is expanded, lets collapse
+ collapseStack();
+ } else {
+ // App bubble is not selected, select it
+ mBubbleData.setSelectedBubble(existingAppBubble);
+ }
+ } else {
+ // App bubble is not selected, select it & expand
+ mBubbleData.setSelectedBubble(existingAppBubble);
+ mBubbleData.setExpanded(true);
+ }
+ } else {
+ // App bubble does not exist, lets add and expand it
+ Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor);
+ b.setShouldAutoExpand(true);
+ inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
+ }
}
/**
@@ -1697,9 +1735,9 @@
}
@Override
- public void showAppBubble(Intent intent) {
+ public void showOrHideAppBubble(Intent intent) {
mMainExecutor.execute(() -> {
- BubbleController.this.showAppBubble(intent);
+ BubbleController.this.showOrHideAppBubble(intent);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index af31391..6230d22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -17,6 +17,7 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -684,7 +685,8 @@
if (bubble.getPendingIntentCanceled()
|| !(reason == Bubbles.DISMISS_AGED
|| reason == Bubbles.DISMISS_USER_GESTURE
- || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
+ || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)
+ || KEY_APP_BUBBLE.equals(bubble.getKey())) {
return;
}
if (DEBUG_BUBBLE_DATA) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 465d1ab..df43257 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -109,13 +109,28 @@
void expandStackAndSelectBubble(Bubble bubble);
/**
- * Adds and expands bubble that is not notification based, but instead based on an intent from
- * the app. The intent must be explicit (i.e. include a package name or fully qualified
- * component class name) and the activity for it should be resizable.
+ * This method has different behavior depending on:
+ * - if an app bubble exists
+ * - if an app bubble is expanded
*
- * @param intent the intent to populate the bubble.
+ * If no app bubble exists, this will add and expand a bubble with the provided intent. The
+ * intent must be explicit (i.e. include a package name or fully qualified component class name)
+ * and the activity for it should be resizable.
+ *
+ * If an app bubble exists, this will toggle the visibility of it, i.e. if the app bubble is
+ * expanded, calling this method will collapse it. If the app bubble is not expanded, calling
+ * this method will expand it.
+ *
+ * These bubbles are <b>not</b> backed by a notification and remain until the user dismisses
+ * the bubble or bubble stack.
+ *
+ * Some notes:
+ * - Only one app bubble is supported at a time
+ * - Calling this method with a different intent than the existing app bubble will do nothing
+ *
+ * @param intent the intent to display in the bubble expanded view.
*/
- void showAppBubble(Intent intent);
+ void showOrHideAppBubble(Intent intent);
/**
* @return a bubble that matches the provided shortcutId, if one exists.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index f5f3573..63b03ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -251,7 +251,8 @@
* Show apps on desktop
*/
void showDesktopApps() {
- WindowContainerTransaction wct = bringDesktopAppsToFront();
+ // Bring apps to front, ignoring their visibility status to always ensure they are on top.
+ WindowContainerTransaction wct = bringDesktopAppsToFront(true /* ignoreVisibility */);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */);
@@ -261,7 +262,7 @@
}
@NonNull
- private WindowContainerTransaction bringDesktopAppsToFront() {
+ private WindowContainerTransaction bringDesktopAppsToFront(boolean force) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
@@ -278,12 +279,14 @@
return wct;
}
- final boolean allActiveTasksAreVisible = taskInfos.stream()
- .allMatch(info -> mDesktopModeTaskRepository.isVisibleTask(info.taskId));
- if (allActiveTasksAreVisible) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
- "bringDesktopAppsToFront: active tasks are already in front, skipping.");
- return wct;
+ if (!force) {
+ final boolean allActiveTasksAreVisible = taskInfos.stream()
+ .allMatch(info -> mDesktopModeTaskRepository.isVisibleTask(info.taskId));
+ if (allActiveTasksAreVisible) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "bringDesktopAppsToFront: active tasks are already in front, skipping.");
+ return wct;
+ }
}
ProtoLog.d(WM_SHELL_DESKTOP_MODE,
"bringDesktopAppsToFront: reordering all active tasks to the front");
@@ -354,7 +357,7 @@
if (wct == null) {
wct = new WindowContainerTransaction();
}
- wct.merge(bringDesktopAppsToFront(), true /* transfer */);
+ wct.merge(bringDesktopAppsToFront(false /* ignoreVisibility */), true /* transfer */);
wct.reorder(request.getTriggerTask().token, true /* onTop */);
return wct;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 3341470..9165f70 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -84,8 +84,7 @@
fun showDesktopApps() {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps")
val wct = WindowContainerTransaction()
-
- bringDesktopAppsToFront(wct)
+ bringDesktopAppsToFront(wct, force = true)
// Execute transaction if there are pending operations
if (!wct.isEmpty) {
@@ -150,11 +149,11 @@
?: WINDOWING_MODE_UNDEFINED
}
- private fun bringDesktopAppsToFront(wct: WindowContainerTransaction) {
+ private fun bringDesktopAppsToFront(wct: WindowContainerTransaction, force: Boolean = false) {
val activeTasks = desktopModeTaskRepository.getActiveTasks()
// Skip if all tasks are already visible
- if (activeTasks.isNotEmpty() && activeTasks.all(desktopModeTaskRepository::isVisibleTask)) {
+ if (!force && activeTasks.all(desktopModeTaskRepository::isVisibleTask)) {
ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"bringDesktopAppsToFront: active tasks are already in front, skipping."
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 e6c7e10..83158ff 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
@@ -662,8 +662,8 @@
}
// Please file a bug to handle the unexpected transition type.
- throw new IllegalStateException("Entering PIP with unexpected transition type="
- + transitTypeToString(transitType));
+ android.util.Slog.e(TAG, "Found new PIP in transition with mis-matched type="
+ + transitTypeToString(transitType), new Throwable());
}
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 281ea53..431bd7b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -333,6 +333,9 @@
mTmpDestinationRectF.set(destinationBounds);
mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
final SurfaceControl surfaceControl = getSurfaceControl();
+ if (surfaceControl == null) {
+ return;
+ }
final SurfaceControl.Transaction menuTx =
mSurfaceControlTransactionFactory.getTransaction();
menuTx.setMatrix(surfaceControl, mMoveTransform, mTmpTransform);
@@ -359,6 +362,9 @@
}
final SurfaceControl surfaceControl = getSurfaceControl();
+ if (surfaceControl == null) {
+ return;
+ }
final SurfaceControl.Transaction menuTx =
mSurfaceControlTransactionFactory.getTransaction();
menuTx.setCrop(surfaceControl, destinationBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 8ddc3c04..1488469 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -605,9 +605,19 @@
float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
+ if (taskId2 == INVALID_TASK_ID) {
+ // Launching a solo task.
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
+ options1 = activityOptions.toBundle();
+ addActivityOptions(options1, null /* launchTarget */);
+ wct.startTask(taskId1, options1);
+ mSyncQueue.queue(wct);
+ return;
+ }
+
addActivityOptions(options1, mSideStage);
wct.startTask(taskId1, options1);
-
startWithLegacyTransition(wct, taskId2, options2, splitPosition, splitRatio, adapter,
instanceId);
}
@@ -632,9 +642,19 @@
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
+ if (taskId == INVALID_TASK_ID) {
+ // Launching a solo task.
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
+ options1 = activityOptions.toBundle();
+ addActivityOptions(options1, null /* launchTarget */);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
+ mSyncQueue.queue(wct);
+ return;
+ }
+
addActivityOptions(options1, mSideStage);
wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
-
startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
instanceId);
}
@@ -696,6 +716,34 @@
mShouldUpdateRecents = false;
mIsSplitEntering = true;
+ setSideStagePosition(sidePosition, wct);
+ if (!mMainStage.isActive()) {
+ mMainStage.activate(wct, false /* reparent */);
+ }
+
+ if (mainOptions == null) mainOptions = new Bundle();
+ addActivityOptions(mainOptions, mMainStage);
+ mainOptions = wrapAsSplitRemoteAnimation(adapter, mainOptions);
+
+ updateWindowBounds(mSplitLayout, wct);
+ if (mainTaskId == INVALID_TASK_ID) {
+ wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions);
+ } else {
+ wct.startTask(mainTaskId, mainOptions);
+ }
+
+ wct.reorder(mRootTaskInfo.token, true);
+ wct.setForceTranslucent(mRootTaskInfo.token, false);
+
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> {
+ setDividerVisibility(true, t);
+ });
+
+ setEnterInstanceId(instanceId);
+ }
+
+ private Bundle wrapAsSplitRemoteAnimation(RemoteAnimationAdapter adapter, Bundle options) {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
if (isSplitScreenVisible()) {
mMainStage.evictAllChildren(evictWct);
@@ -739,37 +787,9 @@
};
RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
-
- if (mainOptions == null) {
- mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
- } else {
- ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
- mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
- mainOptions = mainActivityOptions.toBundle();
- }
-
- setSideStagePosition(sidePosition, wct);
- if (!mMainStage.isActive()) {
- mMainStage.activate(wct, false /* reparent */);
- }
-
- if (mainOptions == null) mainOptions = new Bundle();
- addActivityOptions(mainOptions, mMainStage);
- updateWindowBounds(mSplitLayout, wct);
- if (mainTaskId == INVALID_TASK_ID) {
- wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions);
- } else {
- wct.startTask(mainTaskId, mainOptions);
- }
- wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
-
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- setDividerVisibility(true, t);
- });
-
- setEnterInstanceId(instanceId);
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+ return activityOptions.toBundle();
}
private void setEnterInstanceId(InstanceId instanceId) {
@@ -1228,8 +1248,10 @@
return SPLIT_POSITION_UNDEFINED;
}
- private void addActivityOptions(Bundle opts, StageTaskListener stage) {
- opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
+ private void addActivityOptions(Bundle opts, @Nullable StageTaskListener launchTarget) {
+ if (launchTarget != null) {
+ opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, launchTarget.mRootTaskInfo.token);
+ }
// Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split
// will be canceled.
opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 2e328b0..2754496 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -53,6 +53,7 @@
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IBackNaviAnimationController;
import android.window.IOnBackInvokedCallback;
@@ -246,10 +247,11 @@
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+ ArgumentCaptor<BackMotionEvent> backEventCaptor =
+ ArgumentCaptor.forClass(BackMotionEvent.class);
verify(mIOnBackInvokedCallback).onBackStarted(backEventCaptor.capture());
assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
- verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackEvent.class));
+ verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackMotionEvent.class));
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
@@ -271,17 +273,18 @@
RemoteAnimationTarget animationTarget = createAnimationTarget();
IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class);
- ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+ ArgumentCaptor<BackMotionEvent> backEventCaptor =
+ ArgumentCaptor.forClass(BackMotionEvent.class);
createNavigationInfo(animationTarget, null, null,
BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback, false);
triggerBackGesture();
- verify(appCallback, never()).onBackStarted(any(BackEvent.class));
+ verify(appCallback, never()).onBackStarted(any(BackMotionEvent.class));
verify(appCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(appCallback, times(1)).onBackInvoked();
- verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackEvent.class));
+ verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackMotionEvent.class));
verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(mIOnBackInvokedCallback, never()).onBackInvoked();
}
@@ -314,7 +317,7 @@
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class));
}
@Test
@@ -333,7 +336,7 @@
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class));
}
@@ -349,7 +352,7 @@
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class));
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
index 3aefc3f..ba9c159 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import org.junit.Before;
import org.junit.Test;
@@ -38,7 +39,7 @@
@Test
public void generatesProgress_onStart() {
mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
- BackEvent event = mTouchTracker.createStartEvent(null);
+ BackMotionEvent event = mTouchTracker.createStartEvent(null);
assertEquals(event.getProgress(), 0f, 0f);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index e6711ac..8b025cd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.bubbles;
+import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
+
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -32,6 +34,7 @@
import android.app.Notification;
import android.app.PendingIntent;
+import android.content.Intent;
import android.content.LocusId;
import android.graphics.drawable.Icon;
import android.os.Bundle;
@@ -94,6 +97,7 @@
private Bubble mBubbleInterruptive;
private Bubble mBubbleDismissed;
private Bubble mBubbleLocusId;
+ private Bubble mAppBubble;
private BubbleData mBubbleData;
private TestableBubblePositioner mPositioner;
@@ -178,6 +182,11 @@
mBubbleMetadataFlagListener,
mPendingIntentCanceledListener,
mMainExecutor);
+
+ Intent appBubbleIntent = new Intent(mContext, BubblesTestActivity.class);
+ appBubbleIntent.setPackage(mContext.getPackageName());
+ mAppBubble = new Bubble(appBubbleIntent, new UserHandle(1), mMainExecutor);
+
mPositioner = new TestableBubblePositioner(mContext,
mock(WindowManager.class));
mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner,
@@ -1089,6 +1098,18 @@
assertOverflowChangedTo(ImmutableList.of());
}
+ @Test
+ public void test_removeAppBubble_skipsOverflow() {
+ mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/,
+ false /* showInShade */);
+ assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isEqualTo(mAppBubble);
+
+ mBubbleData.dismissBubbleWithKey(KEY_APP_BUBBLE, Bubbles.DISMISS_USER_GESTURE);
+
+ assertThat(mBubbleData.getOverflowBubbleWithKey(KEY_APP_BUBBLE)).isNull();
+ assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull();
+ }
+
private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 08af3d3..35cc168 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -279,7 +279,7 @@
}
@Test
- public void testShowDesktopApps_appsAlreadyVisible_doesNothing() {
+ public void testShowDesktopApps_appsAlreadyVisible_bringsToFront() {
final RunningTaskInfo task1 = createFreeformTask();
mDesktopModeTaskRepository.addActiveTask(task1.taskId);
mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
@@ -294,8 +294,17 @@
mController.showDesktopApps();
final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
- // No reordering needed.
- assertThat(wct.getHierarchyOps()).isEmpty();
+ // Check wct has reorder calls
+ assertThat(wct.getHierarchyOps()).hasSize(2);
+ // Task 1 appeared first, must be first reorder to top.
+ HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
+
+ // Task 2 appeared last, must be last reorder to top.
+ HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 9a92879..4011d08 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -150,8 +150,8 @@
}
@Test
- fun showDesktopApps_appsAlreadyVisible_doesNothing() {
- setUpHomeTask()
+ fun showDesktopApps_appsAlreadyVisible_bringsToFront() {
+ val homeTask = setUpHomeTask()
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskVisible(task1)
@@ -159,7 +159,12 @@
controller.showDesktopApps()
- verifyWCTNotExecuted()
+ val wct = getLatestWct()
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
}
@Test
@@ -207,6 +212,23 @@
}
@Test
+ fun moveToDesktop_otherFreeformTasksBroughtToFront() {
+ val homeTask = setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ val fullscreenTask = setUpFullscreenTask()
+ markTaskHidden(freeformTask)
+
+ controller.moveToDesktop(fullscreenTask)
+
+ with(getLatestWct()) {
+ assertThat(hierarchyOps).hasSize(3)
+ assertReorderSequence(homeTask, freeformTask, fullscreenTask)
+ assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+ }
+
+ @Test
fun moveToFullscreen() {
val task = setUpFreeformTask()
controller.moveToFullscreen(task)
@@ -406,3 +428,9 @@
assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
assertThat(op.container).isEqualTo(task.token.asBinder())
}
+
+private fun WindowContainerTransaction.assertReorderSequence(vararg tasks: RunningTaskInfo) {
+ for (i in tasks.indices) {
+ assertReorderAt(i, tasks[i])
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index c829bc3..d6586db 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -184,6 +184,15 @@
*/
public abstract String getId();
+ /**
+ * Checks if device is suggested device from application
+ *
+ * @return true if device is suggested device
+ */
+ public boolean isSuggestedDevice() {
+ return false;
+ }
+
void setConnectedRecord() {
mConnectedRecord++;
ConnectionRecordManager.getInstance().setConnectionRecord(mContext, getId(),
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 1b0b6b4..211030a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -123,7 +123,7 @@
Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME,
- Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
+ Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 4fa490f..0539f09 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -178,7 +178,7 @@
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR);
- VALIDATORS.put(Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.SFPS_PERFORMANT_AUTH_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 810dd33..6800917 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -411,7 +411,6 @@
<service android:name=".screenshot.ScreenshotCrossProfileService"
android:permission="com.android.systemui.permission.SELF"
- android:process=":screenshot_cross_profile"
android:exported="false" />
<service android:name=".screenrecord.RecordingService" />
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index fe349f2..8f70dcc 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -37,6 +37,8 @@
import android.view.WindowManager
import android.view.animation.Interpolator
import android.view.animation.PathInterpolator
+import androidx.annotation.BinderThread
+import androidx.annotation.UiThread
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
import kotlin.math.roundToInt
@@ -226,7 +228,7 @@
// If we expect an animation, post a timeout to cancel it in case the remote animation is
// never started.
if (willAnimate) {
- runner.postTimeout()
+ runner.delegate.postTimeout()
// Hide the keyguard using the launch animation instead of the default unlock animation.
if (hideKeyguardWithAnimation) {
@@ -389,14 +391,51 @@
fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {}
}
- class Runner(
+ @VisibleForTesting
+ inner class Runner(
+ controller: Controller,
+ callback: Callback,
+ /** The animator to use to animate the window launch. */
+ launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
+ /** Listener for animation lifecycle events. */
+ listener: Listener? = null
+ ) : IRemoteAnimationRunner.Stub() {
+ private val context = controller.launchContainer.context
+ internal val delegate: AnimationDelegate
+
+ init {
+ delegate = AnimationDelegate(controller, callback, launchAnimator, listener)
+ }
+
+ @BinderThread
+ override fun onAnimationStart(
+ transit: Int,
+ apps: Array<out RemoteAnimationTarget>?,
+ wallpapers: Array<out RemoteAnimationTarget>?,
+ nonApps: Array<out RemoteAnimationTarget>?,
+ finishedCallback: IRemoteAnimationFinishedCallback?
+ ) {
+ context.mainExecutor.execute {
+ delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback)
+ }
+ }
+
+ @BinderThread
+ override fun onAnimationCancelled(isKeyguardOccluded: Boolean) {
+ context.mainExecutor.execute { delegate.onAnimationCancelled(isKeyguardOccluded) }
+ }
+ }
+
+ class AnimationDelegate
+ @JvmOverloads
+ constructor(
private val controller: Controller,
private val callback: Callback,
/** The animator to use to animate the window launch. */
private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
/** Listener for animation lifecycle events. */
private val listener: Listener? = null
- ) : IRemoteAnimationRunner.Stub() {
+ ) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> {
private val launchContainer = controller.launchContainer
private val context = launchContainer.context
private val transactionApplierView =
@@ -419,6 +458,7 @@
// posting it.
private var onTimeout = Runnable { onAnimationTimedOut() }
+ @UiThread
internal fun postTimeout() {
launchContainer.postDelayed(onTimeout, LAUNCH_TIMEOUT)
}
@@ -427,19 +467,20 @@
launchContainer.removeCallbacks(onTimeout)
}
+ @UiThread
override fun onAnimationStart(
@WindowManager.TransitionOldType transit: Int,
apps: Array<out RemoteAnimationTarget>?,
wallpapers: Array<out RemoteAnimationTarget>?,
nonApps: Array<out RemoteAnimationTarget>?,
- iCallback: IRemoteAnimationFinishedCallback?
+ callback: IRemoteAnimationFinishedCallback?
) {
removeTimeout()
// The animation was started too late and we already notified the controller that it
// timed out.
if (timedOut) {
- iCallback?.invoke()
+ callback?.invoke()
return
}
@@ -449,7 +490,7 @@
return
}
- context.mainExecutor.execute { startAnimation(apps, nonApps, iCallback) }
+ startAnimation(apps, nonApps, callback)
}
private fun startAnimation(
@@ -687,6 +728,7 @@
controller.onLaunchAnimationCancelled()
}
+ @UiThread
override fun onAnimationCancelled(isKeyguardOccluded: Boolean) {
if (timedOut) {
return
@@ -695,10 +737,9 @@
Log.i(TAG, "Remote animation was cancelled")
cancelled = true
removeTimeout()
- context.mainExecutor.execute {
- animation?.cancel()
- controller.onLaunchAnimationCancelled(newKeyguardOccludedState = isKeyguardOccluded)
- }
+
+ animation?.cancel()
+ controller.onLaunchAnimationCancelled(newKeyguardOccludedState = isKeyguardOccluded)
}
private fun IRemoteAnimationFinishedCallback.invoke() {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 54aa351..a450d3a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -366,7 +366,7 @@
val dialog = animatedDialog.dialog
// Don't animate if the dialog is not showing or if we are locked and going to show the
- // bouncer.
+ // primary bouncer.
if (
!dialog.isShowing ||
(!callback.isUnlocked() && !callback.isShowingAlternateAuthOnUnlock())
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
new file mode 100644
index 0000000..337408b
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
@@ -0,0 +1,30 @@
+package com.android.systemui.animation
+
+import android.annotation.UiThread
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.RemoteAnimationTarget
+import android.view.WindowManager
+
+/**
+ * A component capable of running remote animations.
+ *
+ * Expands the IRemoteAnimationRunner API by allowing for different types of more specialized
+ * callbacks.
+ */
+interface RemoteAnimationDelegate<in T : IRemoteAnimationFinishedCallback> {
+ /**
+ * Called on the UI thread when the animation targets are received. Sets up and kicks off the
+ * animation.
+ */
+ @UiThread
+ fun onAnimationStart(
+ @WindowManager.TransitionOldType transit: Int,
+ apps: Array<out RemoteAnimationTarget>?,
+ wallpapers: Array<out RemoteAnimationTarget>?,
+ nonApps: Array<out RemoteAnimationTarget>?,
+ callback: T?
+ )
+
+ /** Called on the UI thread when a signal is received to cancel the animation. */
+ @UiThread fun onAnimationCancelled(isKeyguardOccluded: Boolean)
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 462b90a..86bd5f2 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -54,7 +54,6 @@
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : TextView(context, attrs, defStyleAttr, defStyleRes) {
- var tag: String = "UnnamedClockView"
var logBuffer: LogBuffer? = null
private val time = Calendar.getInstance()
@@ -132,7 +131,7 @@
override fun onAttachedToWindow() {
super.onAttachedToWindow()
- logBuffer?.log(tag, DEBUG, "onAttachedToWindow")
+ logBuffer?.log(TAG, DEBUG, "onAttachedToWindow")
refreshFormat()
}
@@ -148,7 +147,7 @@
time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis()
contentDescription = DateFormat.format(descFormat, time)
val formattedText = DateFormat.format(format, time)
- logBuffer?.log(tag, DEBUG,
+ logBuffer?.log(TAG, DEBUG,
{ str1 = formattedText?.toString() },
{ "refreshTime: new formattedText=$str1" }
)
@@ -157,7 +156,7 @@
// relayout if the text didn't actually change.
if (!TextUtils.equals(text, formattedText)) {
text = formattedText
- logBuffer?.log(tag, DEBUG,
+ logBuffer?.log(TAG, DEBUG,
{ str1 = formattedText?.toString() },
{ "refreshTime: done setting new time text to: $str1" }
)
@@ -167,17 +166,17 @@
// without being notified TextInterpolator being notified.
if (layout != null) {
textAnimator?.updateLayout(layout)
- logBuffer?.log(tag, DEBUG, "refreshTime: done updating textAnimator layout")
+ logBuffer?.log(TAG, DEBUG, "refreshTime: done updating textAnimator layout")
}
requestLayout()
- logBuffer?.log(tag, DEBUG, "refreshTime: after requestLayout")
+ logBuffer?.log(TAG, DEBUG, "refreshTime: after requestLayout")
}
}
fun onTimeZoneChanged(timeZone: TimeZone?) {
time.timeZone = timeZone
refreshFormat()
- logBuffer?.log(tag, DEBUG,
+ logBuffer?.log(TAG, DEBUG,
{ str1 = timeZone?.toString() },
{ "onTimeZoneChanged newTimeZone=$str1" }
)
@@ -194,7 +193,7 @@
} else {
animator.updateLayout(layout)
}
- logBuffer?.log(tag, DEBUG, "onMeasure")
+ logBuffer?.log(TAG, DEBUG, "onMeasure")
}
override fun onDraw(canvas: Canvas) {
@@ -206,12 +205,12 @@
} else {
super.onDraw(canvas)
}
- logBuffer?.log(tag, DEBUG, "onDraw lastDraw")
+ logBuffer?.log(TAG, DEBUG, "onDraw")
}
override fun invalidate() {
super.invalidate()
- logBuffer?.log(tag, DEBUG, "invalidate")
+ logBuffer?.log(TAG, DEBUG, "invalidate")
}
override fun onTextChanged(
@@ -221,7 +220,7 @@
lengthAfter: Int
) {
super.onTextChanged(text, start, lengthBefore, lengthAfter)
- logBuffer?.log(tag, DEBUG,
+ logBuffer?.log(TAG, DEBUG,
{ str1 = text.toString() },
{ "onTextChanged text=$str1" }
)
@@ -238,7 +237,7 @@
}
fun animateColorChange() {
- logBuffer?.log(tag, DEBUG, "animateColorChange")
+ logBuffer?.log(TAG, DEBUG, "animateColorChange")
setTextStyle(
weight = lockScreenWeight,
textSize = -1f,
@@ -260,7 +259,7 @@
}
fun animateAppearOnLockscreen() {
- logBuffer?.log(tag, DEBUG, "animateAppearOnLockscreen")
+ logBuffer?.log(TAG, DEBUG, "animateAppearOnLockscreen")
setTextStyle(
weight = dozingWeight,
textSize = -1f,
@@ -285,7 +284,7 @@
if (isAnimationEnabled && textAnimator == null) {
return
}
- logBuffer?.log(tag, DEBUG, "animateFoldAppear")
+ logBuffer?.log(TAG, DEBUG, "animateFoldAppear")
setTextStyle(
weight = lockScreenWeightInternal,
textSize = -1f,
@@ -312,7 +311,7 @@
// Skip charge animation if dozing animation is already playing.
return
}
- logBuffer?.log(tag, DEBUG, "animateCharge")
+ logBuffer?.log(TAG, DEBUG, "animateCharge")
val startAnimPhase2 = Runnable {
setTextStyle(
weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -336,7 +335,7 @@
}
fun animateDoze(isDozing: Boolean, animate: Boolean) {
- logBuffer?.log(tag, DEBUG, "animateDoze")
+ logBuffer?.log(TAG, DEBUG, "animateDoze")
setTextStyle(
weight = if (isDozing) dozingWeight else lockScreenWeight,
textSize = -1f,
@@ -455,7 +454,7 @@
isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
else -> DOUBLE_LINE_FORMAT_12_HOUR
}
- logBuffer?.log(tag, DEBUG,
+ logBuffer?.log(TAG, DEBUG,
{ str1 = format?.toString() },
{ "refreshFormat format=$str1" }
)
@@ -466,6 +465,7 @@
fun dump(pw: PrintWriter) {
pw.println("$this")
+ pw.println(" alpha=$alpha")
pw.println(" measuredWidth=$measuredWidth")
pw.println(" measuredHeight=$measuredHeight")
pw.println(" singleLineInternal=$isSingleLineInternal")
@@ -626,7 +626,7 @@
}
companion object {
- private val TAG = AnimatableClockView::class.simpleName
+ private val TAG = AnimatableClockView::class.simpleName!!
const val ANIMATION_DURATION_FOLD_TO_AOD: Int = 600
private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index e138ef8..7645dec 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -88,13 +88,6 @@
events.onTimeTick()
}
- override fun setLogBuffer(logBuffer: LogBuffer) {
- smallClock.view.tag = "smallClockView"
- largeClock.view.tag = "largeClockView"
- smallClock.view.logBuffer = logBuffer
- largeClock.view.logBuffer = logBuffer
- }
-
open inner class DefaultClockFaceController(
override val view: AnimatableClockView,
) : ClockFaceController {
@@ -104,6 +97,12 @@
private var isRegionDark = false
protected var targetRegion: Rect? = null
+ override var logBuffer: LogBuffer?
+ get() = view.logBuffer
+ set(value) {
+ view.logBuffer = value
+ }
+
init {
view.setColors(currentColor, currentColor)
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 66e44b9..a2a0709 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -71,9 +71,6 @@
/** Optional method for dumping debug information */
fun dump(pw: PrintWriter) {}
-
- /** Optional method for debug logging */
- fun setLogBuffer(logBuffer: LogBuffer) {}
}
/** Interface for a specific clock face version rendered by the clock */
@@ -83,6 +80,9 @@
/** Events specific to this clock face */
val events: ClockFaceEvents
+
+ /** Some clocks may log debug information */
+ var logBuffer: LogBuffer?
}
/** Events that should call when various rendering parameters change */
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
index 6436dcb..e99b214 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
@@ -159,8 +159,13 @@
* bug report more actionable, so using the [log] with a messagePrinter to add more detail to
* every log may do more to improve overall logging than adding more logs with this method.
*/
- fun log(tag: String, level: LogLevel, @CompileTimeConstant message: String) =
- log(tag, level, { str1 = message }, { str1!! })
+ @JvmOverloads
+ fun log(
+ tag: String,
+ level: LogLevel,
+ @CompileTimeConstant message: String,
+ exception: Throwable? = null,
+ ) = log(tag, level, { str1 = message }, { str1!! }, exception)
/**
* You should call [log] instead of this method.
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index f96644f..030eaa6 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -16,6 +16,18 @@
public <init>();
}
+# Needed to ensure callback field references are kept in their respective
+# owning classes when the downstream callback registrars only store weak refs.
+# TODO(b/264686688): Handle these cases with more targeted annotations.
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+ private com.android.keyguard.KeyguardUpdateMonitorCallback *;
+ private com.android.systemui.privacy.PrivacyItemController$Callback *;
+ private com.android.systemui.settings.UserTracker$Callback *;
+ private com.android.systemui.statusbar.phone.StatusBarWindowCallback *;
+ private com.android.systemui.util.service.Observer$Callback *;
+ private com.android.systemui.util.service.ObservableServiceConnection$Callback *;
+}
+
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index 2b7bdc2..c772c96 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -27,7 +27,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
- androidprv:layout_maxWidth="@dimen/keyguard_security_width"
+ androidprv:layout_maxWidth="@dimen/biometric_auth_pattern_view_max_size"
android:layout_gravity="center_horizontal|bottom"
android:clipChildren="false"
android:clipToPadding="false">
diff --git a/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml b/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml
deleted file mode 100644
index a3c37e4..0000000
--- a/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/common/assets/res/any/dimens.xml
-**
-** Copyright 2013, 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.
-*/
--->
-<resources>
- <!-- Height of the sliding KeyguardSecurityContainer
- (includes 2x keyguard_security_view_top_margin) -->
- <dimen name="keyguard_security_height">550dp</dimen>
-</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml
index 1dc61c5..b7a1bb4 100644
--- a/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml
@@ -17,10 +17,5 @@
*/
-->
<resources>
-
- <!-- Height of the sliding KeyguardSecurityContainer
- (includes 2x keyguard_security_view_top_margin) -->
- <dimen name="keyguard_security_height">470dp</dimen>
-
<dimen name="widget_big_font_size">100dp</dimen>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index c5ffdc0..6cc5b9d 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -29,9 +29,6 @@
(includes 2x keyguard_security_view_top_margin) -->
<dimen name="keyguard_security_height">420dp</dimen>
- <!-- Max Height of the sliding KeyguardSecurityContainer
- (includes 2x keyguard_security_view_top_margin) -->
-
<!-- pin/password field max height -->
<dimen name="keyguard_password_height">80dp</dimen>
diff --git a/packages/SystemUI/res/drawable/overlay_badge_background.xml b/packages/SystemUI/res/drawable/overlay_badge_background.xml
index 857632e..53122c1 100644
--- a/packages/SystemUI/res/drawable/overlay_badge_background.xml
+++ b/packages/SystemUI/res/drawable/overlay_badge_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2020 The Android Open Source Project
+ ~ Copyright (C) 2022 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -14,8 +14,11 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="oval">
- <solid android:color="?androidprv:attr/colorSurface"/>
-</shape>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+ <path
+ android:pathData="M0,0M48,48"/>
+</vector>
diff --git a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
index a3dd334..3505a3e 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
@@ -71,8 +71,8 @@
<com.android.internal.widget.LockPatternView
android:id="@+id/lockPattern"
android:layout_gravity="center"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ android:layout_width="@dimen/biometric_auth_pattern_view_size"
+ android:layout_height="@dimen/biometric_auth_pattern_view_size"/>
<TextView
android:id="@+id/error"
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index 4af9970..147ea82 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -67,8 +67,8 @@
<com.android.internal.widget.LockPatternView
android:id="@+id/lockPattern"
android:layout_gravity="center"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ android:layout_width="@dimen/biometric_auth_pattern_view_size"
+ android:layout_height="@dimen/biometric_auth_pattern_view_size"/>
<TextView
android:id="@+id/error"
diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml
index bc97e51..8cf4f4d 100644
--- a/packages/SystemUI/res/layout/chipbar.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -23,6 +23,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content">
+ <!-- Extra marginBottom to give room for the drop shadow. -->
<LinearLayout
android:id="@+id/chipbar_inner"
android:orientation="horizontal"
@@ -33,6 +34,8 @@
android:layout_marginTop="20dp"
android:layout_marginStart="@dimen/notification_side_paddings"
android:layout_marginEnd="@dimen/notification_side_paddings"
+ android:translationZ="4dp"
+ android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:gravity="center_vertical"
android:alpha="0.0"
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 9134f96..eec3b11 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -32,26 +32,26 @@
android:elevation="4dp"
android:background="@drawable/action_chip_container_background"
android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
- app:layout_constraintBottom_toBottomOf="@+id/actions_container"
+ android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/actions_container"
- app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
+ app:layout_constraintEnd_toEndOf="@+id/actions_container"
+ app:layout_constraintBottom_toBottomOf="parent"/>
<HorizontalScrollView
android:id="@+id/actions_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
- android:paddingEnd="@dimen/overlay_action_container_padding_right"
+ android:paddingEnd="@dimen/overlay_action_container_padding_end"
android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
android:elevation="4dp"
android:scrollbars="none"
- android:layout_marginBottom="4dp"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintWidth_percent="1.0"
app:layout_constraintWidth_max="wrap"
- app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/preview_border"
- app:layout_constraintEnd_toEndOf="parent">
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/actions_container_background">
<LinearLayout
android:id="@+id/actions"
android:layout_width="wrap_content"
@@ -69,44 +69,30 @@
android:id="@+id/preview_border"
android:layout_width="0dp"
android:layout_height="0dp"
- android:layout_marginStart="@dimen/overlay_offset_x"
- android:layout_marginBottom="12dp"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
+ android:layout_marginStart="@dimen/overlay_preview_container_margin"
+ android:layout_marginTop="@dimen/overlay_border_width_neg"
+ android:layout_marginEnd="@dimen/overlay_border_width_neg"
+ android:layout_marginBottom="@dimen/overlay_preview_container_margin"
android:elevation="7dp"
- app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
- app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
- android:background="@drawable/overlay_border"/>
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/clipboard_preview_end"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierMargin="@dimen/overlay_border_width"
- app:barrierDirection="end"
- app:constraint_referenced_ids="clipboard_preview"/>
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/clipboard_preview_top"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierDirection="top"
- app:barrierMargin="@dimen/overlay_border_width_neg"
- app:constraint_referenced_ids="clipboard_preview"/>
+ android:background="@drawable/overlay_border"
+ app:layout_constraintStart_toStartOf="@id/actions_container_background"
+ app:layout_constraintTop_toTopOf="@id/clipboard_preview"
+ app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
+ app:layout_constraintBottom_toBottomOf="@id/actions_container_background"/>
<FrameLayout
android:id="@+id/clipboard_preview"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/overlay_border_width"
+ android:layout_marginBottom="@dimen/overlay_border_width"
+ android:layout_gravity="center"
android:elevation="7dp"
android:background="@drawable/overlay_preview_background"
android:clipChildren="true"
android:clipToOutline="true"
android:clipToPadding="true"
- android:layout_width="@dimen/clipboard_preview_size"
- android:layout_margin="@dimen/overlay_border_width"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- app:layout_constraintHorizontal_bias="0"
- app:layout_constraintBottom_toBottomOf="@id/preview_border"
app:layout_constraintStart_toStartOf="@id/preview_border"
- app:layout_constraintEnd_toEndOf="@id/preview_border"
- app:layout_constraintTop_toTopOf="@id/preview_border">
+ app:layout_constraintBottom_toBottomOf="@id/preview_border">
<TextView android:id="@+id/text_preview"
android:textFontWeight="500"
android:padding="8dp"
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index a565988..d689828 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -148,9 +148,4 @@
<include layout="@layout/ongoing_privacy_chip"/>
</FrameLayout>
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:id="@+id/space"
- />
</com.android.systemui.util.NoRemeasureMotionLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 9add32c..885e5e2 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -57,6 +57,7 @@
android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
+ android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop"
android:src="@drawable/ic_alarm"
android:tint="@android:color/white"
android:visibility="gone"
@@ -67,6 +68,7 @@
android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
+ android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop"
android:src="@drawable/ic_qs_dnd_on"
android:tint="@android:color/white"
android:visibility="gone"
@@ -77,6 +79,7 @@
android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
+ android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop"
android:src="@drawable/ic_signal_wifi_off"
android:visibility="gone"
android:contentDescription="@string/dream_overlay_status_bar_wifi_off" />
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index e4e0bd4..496eb6e 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -27,26 +27,26 @@
android:elevation="4dp"
android:background="@drawable/action_chip_container_background"
android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
- app:layout_constraintBottom_toBottomOf="@+id/actions_container"
+ android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/actions_container"
- app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
+ app:layout_constraintEnd_toEndOf="@+id/actions_container"
+ app:layout_constraintBottom_toTopOf="@id/screenshot_message_container"/>
<HorizontalScrollView
android:id="@+id/actions_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginBottom="4dp"
- android:paddingEnd="@dimen/overlay_action_container_padding_right"
+ android:paddingEnd="@dimen/overlay_action_container_padding_end"
android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
android:elevation="4dp"
android:scrollbars="none"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintWidth_percent="1.0"
app:layout_constraintWidth_max="wrap"
- app:layout_constraintBottom_toTopOf="@id/screenshot_message_container"
app:layout_constraintStart_toEndOf="@+id/screenshot_preview_border"
- app:layout_constraintEnd_toEndOf="parent">
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/actions_container_background">
<LinearLayout
android:id="@+id/screenshot_actions"
android:layout_width="wrap_content"
@@ -64,35 +64,24 @@
android:id="@+id/screenshot_preview_border"
android:layout_width="0dp"
android:layout_height="0dp"
- android:layout_marginStart="@dimen/overlay_offset_x"
- android:layout_marginBottom="12dp"
+ android:layout_marginStart="@dimen/overlay_preview_container_margin"
+ android:layout_marginTop="@dimen/overlay_border_width_neg"
+ android:layout_marginEnd="@dimen/overlay_border_width_neg"
+ android:layout_marginBottom="@dimen/overlay_preview_container_margin"
android:elevation="7dp"
android:alpha="0"
android:background="@drawable/overlay_border"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintBottom_toTopOf="@id/screenshot_message_container"
- app:layout_constraintEnd_toEndOf="@id/screenshot_preview_end"
- app:layout_constraintTop_toTopOf="@id/screenshot_preview_top"/>
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/screenshot_preview_end"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierMargin="@dimen/overlay_border_width"
- app:barrierDirection="end"
- app:constraint_referenced_ids="screenshot_preview"/>
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/screenshot_preview_top"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierDirection="top"
- app:barrierMargin="@dimen/overlay_border_width_neg"
- app:constraint_referenced_ids="screenshot_preview"/>
+ app:layout_constraintStart_toStartOf="@id/actions_container_background"
+ app:layout_constraintTop_toTopOf="@id/screenshot_preview"
+ app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
+ app:layout_constraintBottom_toBottomOf="@id/actions_container_background"/>
<ImageView
android:id="@+id/screenshot_preview"
android:visibility="invisible"
android:layout_width="@dimen/overlay_x_scale"
- android:layout_margin="@dimen/overlay_border_width"
android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/overlay_border_width"
+ android:layout_marginBottom="@dimen/overlay_border_width"
android:layout_gravity="center"
android:elevation="7dp"
android:contentDescription="@string/screenshot_edit_description"
@@ -100,20 +89,14 @@
android:background="@drawable/overlay_preview_background"
android:adjustViewBounds="true"
android:clickable="true"
- app:layout_constraintHorizontal_bias="0"
- app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
app:layout_constraintStart_toStartOf="@id/screenshot_preview_border"
- app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"
- app:layout_constraintTop_toTopOf="@id/screenshot_preview_border"/>
+ app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"/>
<ImageView
android:id="@+id/screenshot_badge"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:padding="4dp"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
android:visibility="gone"
- android:background="@drawable/overlay_badge_background"
android:elevation="8dp"
- android:src="@drawable/overlay_cancel"
app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"/>
<FrameLayout
@@ -150,7 +133,7 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
android:layout_marginVertical="4dp"
- android:paddingHorizontal="@dimen/overlay_action_container_padding_right"
+ android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
android:elevation="4dp"
android:background="@drawable/action_chip_container_background"
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
index fa9d739..7eaed43 100644
--- a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
@@ -46,7 +46,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:flow_horizontalBias="0.5"
app:flow_verticalAlign="center"
- app:flow_wrapMode="chain"
+ app:flow_wrapMode="chain2"
app:flow_horizontalGap="@dimen/user_switcher_fullscreen_horizontal_gap"
app:flow_verticalGap="44dp"
app:flow_horizontalStyle="packed"/>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 49ef330..fff2544 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -40,6 +40,10 @@
<dimen name="biometric_dialog_button_negative_max_width">140dp</dimen>
<dimen name="biometric_dialog_button_positive_max_width">116dp</dimen>
+ <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+ <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+ <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+
<dimen name="global_actions_power_dialog_item_height">130dp</dimen>
<dimen name="global_actions_power_dialog_item_bottom_margin">35dp</dimen>
diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml
index aefd998..a0e721e 100644
--- a/packages/SystemUI/res/values-land/styles.xml
+++ b/packages/SystemUI/res/values-land/styles.xml
@@ -29,11 +29,11 @@
<style name="AuthCredentialPatternContainerStyle">
<item name="android:gravity">center</item>
- <item name="android:maxHeight">320dp</item>
- <item name="android:maxWidth">320dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
- <item name="android:paddingHorizontal">60dp</item>
+ <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:paddingHorizontal">32dp</item>
<item name="android:paddingVertical">20dp</item>
</style>
diff --git a/packages/SystemUI/res/values-sw360dp/dimens.xml b/packages/SystemUI/res/values-sw360dp/dimens.xml
index 65ca70b..03365b3 100644
--- a/packages/SystemUI/res/values-sw360dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw360dp/dimens.xml
@@ -25,5 +25,8 @@
<!-- Home Controls -->
<dimen name="global_actions_side_margin">12dp</dimen>
+
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">298dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw392dp-land/dimens.xml b/packages/SystemUI/res/values-sw392dp-land/dimens.xml
new file mode 100644
index 0000000..1e26a69
--- /dev/null
+++ b/packages/SystemUI/res/values-sw392dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+ <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+ <dimen name="biometric_auth_pattern_view_max_size">248dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw392dp/dimens.xml b/packages/SystemUI/res/values-sw392dp/dimens.xml
index 78279ca..96af3c1 100644
--- a/packages/SystemUI/res/values-sw392dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw392dp/dimens.xml
@@ -24,5 +24,8 @@
<!-- Home Controls -->
<dimen name="global_actions_side_margin">16dp</dimen>
+
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">298dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw410dp-land/dimens.xml b/packages/SystemUI/res/values-sw410dp-land/dimens.xml
new file mode 100644
index 0000000..c4d9b9b
--- /dev/null
+++ b/packages/SystemUI/res/values-sw410dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+ <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+ <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw410dp/dimens.xml b/packages/SystemUI/res/values-sw410dp/dimens.xml
index 7da47e5..ff6e005 100644
--- a/packages/SystemUI/res/values-sw410dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw410dp/dimens.xml
@@ -27,4 +27,6 @@
<dimen name="global_actions_grid_item_side_margin">12dp</dimen>
<dimen name="global_actions_grid_item_height">72dp</dimen>
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/styles.xml b/packages/SystemUI/res/values-sw600dp-land/styles.xml
index 8148d3d..c535c64 100644
--- a/packages/SystemUI/res/values-sw600dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/styles.xml
@@ -18,10 +18,10 @@
<style name="AuthCredentialPatternContainerStyle">
<item name="android:gravity">center</item>
- <item name="android:maxHeight">420dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
+ <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
<item name="android:paddingHorizontal">120dp</item>
<item name="android:paddingVertical">40dp</item>
</style>
diff --git a/packages/SystemUI/res/values-sw600dp-port/styles.xml b/packages/SystemUI/res/values-sw600dp-port/styles.xml
index 771de08..32eefa7 100644
--- a/packages/SystemUI/res/values-sw600dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/styles.xml
@@ -26,10 +26,10 @@
<style name="AuthCredentialPatternContainerStyle">
<item name="android:gravity">center</item>
- <item name="android:maxHeight">420dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
+ <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
<item name="android:paddingHorizontal">180dp</item>
<item name="android:paddingVertical">80dp</item>
</style>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 599bf30..9bc0dde 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -92,4 +92,6 @@
<dimen name="lockscreen_shade_status_bar_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
<dimen name="lockscreen_shade_keyguard_transition_distance">@dimen/lockscreen_shade_media_transition_distance</dimen>
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/styles.xml b/packages/SystemUI/res/values-sw720dp-land/styles.xml
index f9ed67d..6a70ebd 100644
--- a/packages/SystemUI/res/values-sw720dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/styles.xml
@@ -18,10 +18,10 @@
<style name="AuthCredentialPatternContainerStyle">
<item name="android:gravity">center</item>
- <item name="android:maxHeight">420dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
+ <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
<item name="android:paddingHorizontal">120dp</item>
<item name="android:paddingVertical">40dp</item>
</style>
diff --git a/packages/SystemUI/res/values-sw720dp-port/styles.xml b/packages/SystemUI/res/values-sw720dp-port/styles.xml
index 78d299c..0a46e08 100644
--- a/packages/SystemUI/res/values-sw720dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/styles.xml
@@ -26,10 +26,10 @@
<style name="AuthCredentialPatternContainerStyle">
<item name="android:gravity">center</item>
- <item name="android:maxHeight">420dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
+ <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
<item name="android:paddingHorizontal">240dp</item>
<item name="android:paddingVertical">120dp</item>
</style>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index 0705017..927059a 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -22,5 +22,8 @@
<dimen name="controls_padding_horizontal">75dp</dimen>
<dimen name="large_screen_shade_header_height">56dp</dimen>
+
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw800dp/dimens.xml b/packages/SystemUI/res/values-sw800dp/dimens.xml
new file mode 100644
index 0000000..0d82217
--- /dev/null
+++ b/packages/SystemUI/res/values-sw800dp/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources>
+
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7d72598..e8a5e7e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -437,6 +437,11 @@
This name is in the ComponentName flattened format (package/class) -->
<string name="config_screenshotEditor" translatable="false"></string>
+ <!-- ComponentName for the file browsing app that the system would expect to be used in work
+ profile. The icon for this app will be shown to the user when informing them that a
+ screenshot has been saved to work profile. If blank, a default icon will be shown. -->
+ <string name="config_sceenshotWorkProfileFilesApp" translatable="false"></string>
+
<!-- Remote copy default activity. Must handle REMOTE_COPY_ACTION intents.
This name is in the ComponentName flattened format (package/class) -->
<string name="config_remoteCopyPackage" translatable="false"></string>
@@ -663,6 +668,16 @@
<item>17</item> <!-- WAKE_REASON_BIOMETRIC -->
</integer-array>
+ <!-- Whether to support posture listening for face auth, default is 0(DEVICE_POSTURE_UNKNOWN)
+ means systemui will try listening on all postures.
+ 0 : DEVICE_POSTURE_UNKNOWN
+ 1 : DEVICE_POSTURE_CLOSED
+ 2 : DEVICE_POSTURE_HALF_OPENED
+ 3 : DEVICE_POSTURE_OPENED
+ 4 : DEVICE_POSTURE_FLIPPED
+ -->
+ <integer name="config_face_auth_supported_posture">0</integer>
+
<!-- Whether the communal service should be enabled -->
<bool name="config_communalServiceEnabled">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ecb6560..f5b25f3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -334,15 +334,22 @@
<dimen name="overlay_action_chip_spacing">8dp</dimen>
<dimen name="overlay_action_chip_text_size">14sp</dimen>
<dimen name="overlay_offset_x">16dp</dimen>
+ <!-- Used for both start and bottom margin of the preview, relative to the action container -->
+ <dimen name="overlay_preview_container_margin">8dp</dimen>
<dimen name="overlay_action_container_margin_horizontal">8dp</dimen>
+ <dimen name="overlay_action_container_margin_bottom">4dp</dimen>
<dimen name="overlay_bg_protection_height">242dp</dimen>
<dimen name="overlay_action_container_corner_radius">18dp</dimen>
<dimen name="overlay_action_container_padding_vertical">4dp</dimen>
<dimen name="overlay_action_container_padding_right">8dp</dimen>
+ <dimen name="overlay_action_container_padding_end">8dp</dimen>
<dimen name="overlay_dismiss_button_tappable_size">48dp</dimen>
<dimen name="overlay_dismiss_button_margin">8dp</dimen>
+ <!-- must be kept aligned with overlay_border_width_neg, below;
+ overlay_border_width = overlay_border_width_neg * -1 -->
<dimen name="overlay_border_width">4dp</dimen>
- <!-- need a negative margin for some of the constraints. should be overlay_border_width * -1 -->
+ <!-- some constraints use a negative margin. must be aligned with overlay_border_width, above;
+ overlay_border_width_neg = overlay_border_width * -1 -->
<dimen name="overlay_border_width_neg">-4dp</dimen>
<dimen name="clipboard_preview_size">@dimen/overlay_x_scale</dimen>
@@ -966,6 +973,10 @@
<!-- Biometric Auth Credential values -->
<dimen name="biometric_auth_icon_size">48dp</dimen>
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+ <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+
<!-- Starting text size in sp of batteryLevel for wireless charging animation -->
<item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">
0
@@ -1030,8 +1041,6 @@
<dimen name="ongoing_appops_dialog_side_padding">16dp</dimen>
- <!-- Size of the RAT type for CellularTile -->
-
<!-- Size of media cards in the QSPanel carousel -->
<dimen name="qs_media_padding">16dp</dimen>
<dimen name="qs_media_album_radius">14dp</dimen>
@@ -1279,6 +1288,12 @@
<!-- OCCLUDED -> LOCKSCREEN transition: Amount to shift lockscreen content on entering -->
<dimen name="occluded_to_lockscreen_transition_lockscreen_translation_y">40dp</dimen>
+ <!-- LOCKSCREEN -> DREAMING transition: Amount to shift lockscreen content on entering -->
+ <dimen name="lockscreen_to_dreaming_transition_lockscreen_translation_y">-40dp</dimen>
+
+ <!-- LOCKSCREEN -> OCCLUDED transition: Amount to shift lockscreen content on entering -->
+ <dimen name="lockscreen_to_occluded_transition_lockscreen_translation_y">-40dp</dimen>
+
<!-- The amount of vertical offset for the keyguard during the full shade transition. -->
<dimen name="lockscreen_shade_keyguard_transition_vertical_offset">0dp</dimen>
@@ -1617,6 +1632,8 @@
<dimen name="dream_overlay_status_bar_ambient_text_shadow_dx">0.5dp</dimen>
<dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen>
<dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen>
+ <dimen name="dream_overlay_icon_inset_dimen">0dp</dimen>
+ <dimen name="dream_overlay_status_bar_marginTop">22dp</dimen>
<!-- Default device corner radius, used for assist UI -->
<dimen name="config_rounded_mask_size">0px</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 977adde..22d4c6d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -240,7 +240,9 @@
<!-- Content description for the right boundary of the screenshot being cropped, with the current position as a percentage. [CHAR LIMIT=NONE] -->
<string name="screenshot_right_boundary_pct">Right boundary <xliff:g id="percent" example="50">%1$d</xliff:g> percent</string>
<!-- Notification displayed when a screenshot is saved in a work profile. [CHAR LIMIT=NONE] -->
- <string name="screenshot_work_profile_notification" translatable="false">Work screenshots are saved in the work <xliff:g id="app" example="Files">%1$s</xliff:g> app</string>
+ <string name="screenshot_work_profile_notification">Work screenshots are saved in the <xliff:g id="app" example="Work Files">%1$s</xliff:g> app</string>
+ <!-- Default name referring to the app on the device that lets the user browse stored files. [CHAR LIMIT=NONE] -->
+ <string name="screenshot_default_files_app_name">Files</string>
<!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
<string name="screenrecord_name">Screen Recorder</string>
@@ -2364,6 +2366,8 @@
<string name="media_transfer_failed">Something went wrong. Try again.</string>
<!-- Text to indicate that a media transfer is currently in-progress, aka loading. [CHAR LIMIT=NONE] -->
<string name="media_transfer_loading">Loading</string>
+ <!-- Default name of the device. [CHAR LIMIT=30] -->
+ <string name="media_ttt_default_device_type">tablet</string>
<!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] -->
<string name="controls_error_timeout">Inactive, check app</string>
@@ -2412,6 +2416,8 @@
<string name="media_output_dialog_volume_percentage"><xliff:g id="percentage" example="10">%1$d</xliff:g>%%</string>
<!-- Title for Speakers and Displays group. [CHAR LIMIT=NONE] -->
<string name="media_output_group_title_speakers_and_displays">Speakers & Displays</string>
+ <!-- Title for Suggested Devices group. [CHAR LIMIT=NONE] -->
+ <string name="media_output_group_title_suggested_device">Suggested Devices</string>
<!-- Media Output Broadcast Dialog -->
<!-- Title for Broadcast First Notify Dialog [CHAR LIMIT=60] -->
@@ -2766,6 +2772,9 @@
<!-- Text for education page content description for unfolded animation. [CHAR_LIMIT=NONE] -->
<string name="rear_display_accessibility_unfolded_animation">Foldable device being flipped around</string>
- <!-- Title for notification of low stylus battery. [CHAR_LIMIT=NONE] -->
- <string name="stylus_battery_low">Stylus battery low</string>
+ <!-- Title for notification of low stylus battery with percentage. "percentage" is
+ the value of the battery capacity remaining [CHAR LIMIT=none]-->
+ <string name="stylus_battery_low_percentage"><xliff:g id="percentage" example="16%">%s</xliff:g> battery remaining</string>
+ <!-- Subtitle for the notification sent when a stylus battery is low. [CHAR LIMIT=none]-->
+ <string name="stylus_battery_low_subtitle">Connect your stylus to a charger</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index b11b6d6..9846fc2 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -251,11 +251,12 @@
<style name="AuthCredentialPatternContainerStyle">
<item name="android:gravity">center</item>
- <item name="android:maxHeight">420dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
- <item name="android:padding">20dp</item>
+ <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:paddingHorizontal">32dp</item>
+ <item name="android:paddingVertical">20dp</item>
</style>
<style name="AuthCredentialPinPasswordContainerStyle">
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index eca2b2a..d97031f 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -56,13 +56,9 @@
<Layout
android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constrainedWidth="true"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/space"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/carrier_group"
- app:layout_constraintHorizontal_bias="0"
- app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
</Constraint>
@@ -87,39 +83,27 @@
<Constraint
android:id="@+id/statusIcons">
<Layout
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constraintStart_toEndOf="@id/space"
+ app:layout_constraintWidth_default="wrap"
+ app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
app:layout_constraintTop_toTopOf="@id/date"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintBottom_toBottomOf="@id/date"
/>
</Constraint>
<Constraint
android:id="@+id/batteryRemainingIcon">
<Layout
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constraintWidth_default="wrap"
app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constraintStart_toEndOf="@id/statusIcons"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/date"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintHorizontal_bias="1"
- app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintBottom_toBottomOf="@id/date"
/>
</Constraint>
-
- <Constraint
- android:id="@id/space">
- <Layout
- android:layout_width="0dp"
- android:layout_height="0dp"
- app:layout_constraintStart_toEndOf="@id/date"
- app:layout_constraintEnd_toStartOf="@id/statusIcons"
- />
- </Constraint>
</ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
index 25d2721..9b73cc3 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
@@ -48,48 +48,28 @@
val drawableInsetSize: Int
try {
val keyShadowBlur =
- attributes.getDimensionPixelSize(R.styleable.DoubleShadowTextView_keyShadowBlur, 0)
+ attributes.getDimension(R.styleable.DoubleShadowTextView_keyShadowBlur, 0f)
val keyShadowOffsetX =
- attributes.getDimensionPixelSize(
- R.styleable.DoubleShadowTextView_keyShadowOffsetX,
- 0
- )
+ attributes.getDimension(R.styleable.DoubleShadowTextView_keyShadowOffsetX, 0f)
val keyShadowOffsetY =
- attributes.getDimensionPixelSize(
- R.styleable.DoubleShadowTextView_keyShadowOffsetY,
- 0
- )
+ attributes.getDimension(R.styleable.DoubleShadowTextView_keyShadowOffsetY, 0f)
val keyShadowAlpha =
attributes.getFloat(R.styleable.DoubleShadowTextView_keyShadowAlpha, 0f)
mKeyShadowInfo =
- ShadowInfo(
- keyShadowBlur.toFloat(),
- keyShadowOffsetX.toFloat(),
- keyShadowOffsetY.toFloat(),
- keyShadowAlpha
- )
+ ShadowInfo(keyShadowBlur, keyShadowOffsetX, keyShadowOffsetY, keyShadowAlpha)
val ambientShadowBlur =
- attributes.getDimensionPixelSize(
- R.styleable.DoubleShadowTextView_ambientShadowBlur,
- 0
- )
+ attributes.getDimension(R.styleable.DoubleShadowTextView_ambientShadowBlur, 0f)
val ambientShadowOffsetX =
- attributes.getDimensionPixelSize(
- R.styleable.DoubleShadowTextView_ambientShadowOffsetX,
- 0
- )
+ attributes.getDimension(R.styleable.DoubleShadowTextView_ambientShadowOffsetX, 0f)
val ambientShadowOffsetY =
- attributes.getDimensionPixelSize(
- R.styleable.DoubleShadowTextView_ambientShadowOffsetY,
- 0
- )
+ attributes.getDimension(R.styleable.DoubleShadowTextView_ambientShadowOffsetY, 0f)
val ambientShadowAlpha =
attributes.getFloat(R.styleable.DoubleShadowTextView_ambientShadowAlpha, 0f)
mAmbientShadowInfo =
ShadowInfo(
- ambientShadowBlur.toFloat(),
- ambientShadowOffsetX.toFloat(),
- ambientShadowOffsetY.toFloat(),
+ ambientShadowBlur,
+ ambientShadowOffsetX,
+ ambientShadowOffsetY,
ambientShadowAlpha
)
drawableSize =
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 8f38e58..a45ce42 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -38,9 +38,11 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.log.dagger.KeyguardClockLog
+import com.android.systemui.log.dagger.KeyguardSmallClockLog
+import com.android.systemui.log.dagger.KeyguardLargeClockLog
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
@@ -73,16 +75,18 @@
private val context: Context,
@Main private val mainExecutor: Executor,
@Background private val bgExecutor: Executor,
- @KeyguardClockLog private val logBuffer: LogBuffer?,
+ @KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?,
+ @KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?,
private val featureFlags: FeatureFlags
) {
var clock: ClockController? = null
set(value) {
field = value
if (value != null) {
- if (logBuffer != null) {
- value.setLogBuffer(logBuffer)
- }
+ smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
+ value.smallClock.logBuffer = smallLogBuffer
+ largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
+ value.largeClock.logBuffer = largeLogBuffer
value.initialize(resources, dozeAmount, 0f)
updateRegionSamplers(value)
@@ -325,4 +329,8 @@
}
}
}
+
+ companion object {
+ private val TAG = ClockEventController::class.simpleName!!
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
index 5bb9367..e0cf7b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
@@ -50,6 +50,7 @@
import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_VISIBILITY_CHANGED
import com.android.keyguard.InternalFaceAuthReasons.NON_STRONG_BIOMETRIC_ALLOWED_CHANGED
import com.android.keyguard.InternalFaceAuthReasons.OCCLUDING_APP_REQUESTED
+import com.android.keyguard.InternalFaceAuthReasons.POSTURE_CHANGED
import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN
import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN
import com.android.keyguard.InternalFaceAuthReasons.RETRY_AFTER_HW_UNAVAILABLE
@@ -126,6 +127,7 @@
const val STRONG_AUTH_ALLOWED_CHANGED = "Face auth stopped because strong auth allowed changed"
const val NON_STRONG_BIOMETRIC_ALLOWED_CHANGED =
"Face auth stopped because non strong biometric allowed changed"
+ const val POSTURE_CHANGED = "Face auth started/stopped due to device posture changed."
}
/**
@@ -173,6 +175,7 @@
return PowerManager.wakeReasonToString(extraInfo)
}
},
+ @UiEvent(doc = POSTURE_CHANGED) FACE_AUTH_UPDATED_POSTURE_CHANGED(1265, POSTURE_CHANGED),
@Deprecated(
"Not a face auth trigger.",
ReplaceWith(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 62babad..4acbb0a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -7,7 +7,6 @@
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -20,11 +19,15 @@
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.ClockController;
+import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.plugins.log.LogLevel;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import kotlin.Unit;
+
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
*/
@@ -87,6 +90,7 @@
private int mClockSwitchYAmount;
@VisibleForTesting boolean mChildrenAreLaidOut = false;
@VisibleForTesting boolean mAnimateOnLayout = true;
+ private LogBuffer mLogBuffer = null;
public KeyguardClockSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -113,6 +117,14 @@
onDensityOrFontScaleChanged();
}
+ public void setLogBuffer(LogBuffer logBuffer) {
+ mLogBuffer = logBuffer;
+ }
+
+ public LogBuffer getLogBuffer() {
+ return mLogBuffer;
+ }
+
void setClock(ClockController clock, int statusBarState) {
mClock = clock;
@@ -121,12 +133,16 @@
mLargeClockFrame.removeAllViews();
if (clock == null) {
- Log.e(TAG, "No clock being shown");
+ if (mLogBuffer != null) {
+ mLogBuffer.log(TAG, LogLevel.ERROR, "No clock being shown");
+ }
return;
}
// Attach small and big clock views to hierarchy.
- Log.i(TAG, "Attached new clock views to switch");
+ if (mLogBuffer != null) {
+ mLogBuffer.log(TAG, LogLevel.INFO, "Attached new clock views to switch");
+ }
mSmallClockFrame.addView(clock.getSmallClock().getView());
mLargeClockFrame.addView(clock.getLargeClock().getView());
updateClockTargetRegions();
@@ -152,8 +168,18 @@
}
private void updateClockViews(boolean useLargeClock, boolean animate) {
- Log.i(TAG, "updateClockViews; useLargeClock=" + useLargeClock + "; animate=" + animate
- + "; mChildrenAreLaidOut=" + mChildrenAreLaidOut);
+ if (mLogBuffer != null) {
+ mLogBuffer.log(TAG, LogLevel.DEBUG, (msg) -> {
+ msg.setBool1(useLargeClock);
+ msg.setBool2(animate);
+ msg.setBool3(mChildrenAreLaidOut);
+ return Unit.INSTANCE;
+ }, (msg) -> "updateClockViews"
+ + "; useLargeClock=" + msg.getBool1()
+ + "; animate=" + msg.getBool2()
+ + "; mChildrenAreLaidOut=" + msg.getBool3());
+ }
+
if (mClockInAnim != null) mClockInAnim.cancel();
if (mClockOutAnim != null) mClockOutAnim.cancel();
if (mStatusAreaAnim != null) mStatusAreaAnim.cancel();
@@ -183,6 +209,7 @@
if (!animate) {
out.setAlpha(0f);
+ out.setVisibility(INVISIBLE);
in.setAlpha(1f);
in.setVisibility(VISIBLE);
mStatusArea.setTranslationY(statusAreaYTranslation);
@@ -198,7 +225,10 @@
direction * -mClockSwitchYAmount));
mClockOutAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
- mClockOutAnim = null;
+ if (mClockOutAnim == animation) {
+ out.setVisibility(INVISIBLE);
+ mClockOutAnim = null;
+ }
}
});
@@ -212,7 +242,9 @@
mClockInAnim.setStartDelay(CLOCK_OUT_MILLIS / 2);
mClockInAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
- mClockInAnim = null;
+ if (mClockInAnim == animation) {
+ mClockInAnim = null;
+ }
}
});
@@ -225,7 +257,9 @@
mStatusAreaAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
mStatusAreaAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
- mStatusAreaAnim = null;
+ if (mStatusAreaAnim == animation) {
+ mStatusAreaAnim = null;
+ }
}
});
mStatusAreaAnim.start();
@@ -269,7 +303,9 @@
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardClockSwitch:");
pw.println(" mSmallClockFrame: " + mSmallClockFrame);
+ pw.println(" mSmallClockFrame.alpha: " + mSmallClockFrame.getAlpha());
pw.println(" mLargeClockFrame: " + mLargeClockFrame);
+ pw.println(" mLargeClockFrame.alpha: " + mLargeClockFrame.getAlpha());
pw.println(" mStatusArea: " + mStatusArea);
pw.println(" mDisplayedClockSize: " + mDisplayedClockSize);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 788f120..88ce2a7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -38,8 +38,11 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.log.dagger.KeyguardClockLog;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
+import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
@@ -62,6 +65,8 @@
*/
public class KeyguardClockSwitchController extends ViewController<KeyguardClockSwitch>
implements Dumpable {
+ private static final String TAG = "KeyguardClockSwitchController";
+
private final StatusBarStateController mStatusBarStateController;
private final ClockRegistry mClockRegistry;
private final KeyguardSliceViewController mKeyguardSliceViewController;
@@ -70,6 +75,7 @@
private final SecureSettings mSecureSettings;
private final DumpManager mDumpManager;
private final ClockEventController mClockEventController;
+ private final LogBuffer mLogBuffer;
private FrameLayout mSmallClockFrame; // top aligned clock
private FrameLayout mLargeClockFrame; // centered clock
@@ -119,7 +125,8 @@
SecureSettings secureSettings,
@Main Executor uiExecutor,
DumpManager dumpManager,
- ClockEventController clockEventController) {
+ ClockEventController clockEventController,
+ @KeyguardClockLog LogBuffer logBuffer) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
mClockRegistry = clockRegistry;
@@ -131,6 +138,8 @@
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mDumpManager = dumpManager;
mClockEventController = clockEventController;
+ mLogBuffer = logBuffer;
+ mView.setLogBuffer(mLogBuffer);
mClockChangedListener = () -> {
setClock(mClockRegistry.createCurrentClock());
@@ -337,10 +346,6 @@
int clockHeight = clock.getLargeClock().getView().getHeight();
return frameHeight / 2 + clockHeight / 2 + mKeyguardLargeClockTopMargin / -2;
} else {
- // This is only called if we've never shown the large clock as the frame is inflated
- // with 'gone', but then the visibility is never set when it is animated away by
- // KeyguardClockSwitch, instead it is removed from the view hierarchy.
- // TODO(b/261755021): Cleanup Large Frame Visibility
int clockHeight = clock.getSmallClock().getView().getHeight();
return clockHeight + statusBarHeaderHeight + mKeyguardSmallClockTopMargin;
}
@@ -358,15 +363,11 @@
if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
return clock.getLargeClock().getView().getHeight();
} else {
- // Is not called except in certain edge cases, see comment in getClockBottom
- // TODO(b/261755021): Cleanup Large Frame Visibility
return clock.getSmallClock().getView().getHeight();
}
}
boolean isClockTopAligned() {
- // Returns false except certain edge cases, see comment in getClockBottom
- // TODO(b/261755021): Cleanup Large Frame Visibility
return mLargeClockFrame.getVisibility() != View.VISIBLE;
}
@@ -378,6 +379,10 @@
}
private void setClock(ClockController clock) {
+ if (clock != null && mLogBuffer != null) {
+ mLogBuffer.log(TAG, LogLevel.INFO, "New Clock");
+ }
+
mClockEventController.setClock(clock);
mView.setClock(clock, mStatusBarStateController.getState());
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
index deead19..1a06b5f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
@@ -39,6 +39,7 @@
var keyguardGoingAway: Boolean = false,
var listeningForFaceAssistant: Boolean = false,
var occludingAppRequestingFaceAuth: Boolean = false,
+ val postureAllowsListening: Boolean = false,
var primaryUser: Boolean = false,
var secureCameraLaunched: Boolean = false,
var supportsDetect: Boolean = false,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 93027c1..204f09e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -63,11 +63,13 @@
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_FACE_AUTHENTICATED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_KEYGUARD_INIT;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_POSTURE_CHANGED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING;
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
import android.annotation.AnyThread;
import android.annotation.MainThread;
@@ -141,6 +143,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -154,6 +157,7 @@
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.Assert;
import com.android.systemui.util.settings.SecureSettings;
@@ -170,6 +174,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.Executor;
@@ -345,18 +350,17 @@
private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
mCallbacks = Lists.newArrayList();
private ContentObserver mDeviceProvisionedObserver;
- private ContentObserver mSfpsRequireScreenOnToAuthPrefObserver;
private final ContentObserver mTimeFormatChangeObserver;
private boolean mSwitchingUser;
private boolean mDeviceInteractive;
- private boolean mSfpsRequireScreenOnToAuthPrefEnabled;
private final SubscriptionManager mSubscriptionManager;
private final TelephonyListenerManager mTelephonyListenerManager;
private final TrustManager mTrustManager;
private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
+ private final DevicePostureController mPostureController;
private final BroadcastDispatcher mBroadcastDispatcher;
private final SecureSettings mSecureSettings;
private final InteractionJankMonitor mInteractionJankMonitor;
@@ -374,6 +378,9 @@
private final FaceManager mFaceManager;
private final LockPatternUtils mLockPatternUtils;
private final boolean mWakeOnFingerprintAcquiredStart;
+ @VisibleForTesting
+ @DevicePostureController.DevicePostureInt
+ protected int mConfigFaceAuthSupportedPosture;
private KeyguardBypassController mKeyguardBypassController;
private List<SubscriptionInfo> mSubscriptionInfo;
@@ -384,6 +391,8 @@
private boolean mLogoutEnabled;
private boolean mIsFaceEnrolled;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private int mPostureState = DEVICE_POSTURE_UNKNOWN;
+ private FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider;
/**
* Short delay before restarting fingerprint authentication after a successful try. This should
@@ -711,8 +720,18 @@
*/
public void setKeyguardGoingAway(boolean goingAway) {
mKeyguardGoingAway = goingAway;
- // This is set specifically to stop face authentication from running.
- updateBiometricListeningState(BIOMETRIC_ACTION_STOP, FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY);
+ if (mKeyguardGoingAway) {
+ updateFaceListeningState(BIOMETRIC_ACTION_STOP,
+ FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY);
+ }
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+ }
+
+ /**
+ * Whether keyguard is going away due to screen off or device entry.
+ */
+ public boolean isKeyguardGoingAway() {
+ return mKeyguardGoingAway;
}
/**
@@ -1784,6 +1803,17 @@
};
@VisibleForTesting
+ final DevicePostureController.Callback mPostureCallback =
+ new DevicePostureController.Callback() {
+ @Override
+ public void onPostureChanged(int posture) {
+ mPostureState = posture;
+ updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+ FACE_AUTH_UPDATED_POSTURE_CHANGED);
+ }
+ };
+
+ @VisibleForTesting
CancellationSignal mFingerprintCancelSignal;
@VisibleForTesting
CancellationSignal mFaceCancelSignal;
@@ -1943,9 +1973,9 @@
cb.onFinishedGoingToSleep(arg1);
}
}
- // This is set specifically to stop face authentication from running.
- updateBiometricListeningState(BIOMETRIC_ACTION_STOP,
+ updateFaceListeningState(BIOMETRIC_ACTION_STOP,
FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP);
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
private void handleScreenTurnedOff() {
@@ -2048,7 +2078,9 @@
@Nullable FaceManager faceManager,
@Nullable FingerprintManager fingerprintManager,
@Nullable BiometricManager biometricManager,
- FaceWakeUpTriggersConfig faceWakeUpTriggersConfig) {
+ FaceWakeUpTriggersConfig faceWakeUpTriggersConfig,
+ DevicePostureController devicePostureController,
+ Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider) {
mContext = context;
mSubscriptionManager = subscriptionManager;
mUserTracker = userTracker;
@@ -2077,6 +2109,7 @@
mDreamManager = dreamManager;
mTelephonyManager = telephonyManager;
mDevicePolicyManager = devicePolicyManager;
+ mPostureController = devicePostureController;
mPackageManager = packageManager;
mFpm = fingerprintManager;
mFaceManager = faceManager;
@@ -2088,6 +2121,8 @@
R.array.config_face_acquire_device_entry_ignorelist))
.boxed()
.collect(Collectors.toSet());
+ mConfigFaceAuthSupportedPosture = mContext.getResources().getInteger(
+ R.integer.config_face_auth_supported_posture);
mFaceWakeUpTriggersConfig = faceWakeUpTriggersConfig;
mHandler = new Handler(mainLooper) {
@@ -2278,6 +2313,9 @@
FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED));
}
});
+ if (mConfigFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
+ mPostureController.addCallback(mPostureCallback);
+ }
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_ON_KEYGUARD_INIT);
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
@@ -2311,30 +2349,7 @@
Settings.System.getUriFor(Settings.System.TIME_12_24),
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
- updateSfpsRequireScreenOnToAuthPref();
- mSfpsRequireScreenOnToAuthPrefObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateSfpsRequireScreenOnToAuthPref();
- }
- };
-
- mContext.getContentResolver().registerContentObserver(
- mSecureSettings.getUriFor(
- Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED),
- false,
- mSfpsRequireScreenOnToAuthPrefObserver,
- getCurrentUser());
- }
-
- protected void updateSfpsRequireScreenOnToAuthPref() {
- final int defaultSfpsRequireScreenOnToAuthValue =
- mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0;
- mSfpsRequireScreenOnToAuthPrefEnabled = mSecureSettings.getIntForUser(
- Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
- defaultSfpsRequireScreenOnToAuthValue,
- getCurrentUser()) != 0;
+ mFingerprintInteractiveToAuthProvider = interactiveToAuthProvider.orElse(null);
}
private void initializeSimState() {
@@ -2729,8 +2744,11 @@
boolean shouldListenSideFpsState = true;
if (isSideFps) {
+ final boolean interactiveToAuthEnabled =
+ mFingerprintInteractiveToAuthProvider != null &&
+ mFingerprintInteractiveToAuthProvider.isEnabled(getCurrentUser());
shouldListenSideFpsState =
- mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true;
+ interactiveToAuthEnabled ? isDeviceInteractive() && !mGoingToSleep : true;
}
boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
@@ -2742,7 +2760,7 @@
user,
shouldListen,
biometricEnabledForUser,
- mPrimaryBouncerIsOrWillBeShowing,
+ mPrimaryBouncerIsOrWillBeShowing,
userCanSkipBouncer,
mCredentialAttempted,
mDeviceInteractive,
@@ -2802,6 +2820,9 @@
final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
final boolean isUdfpsFingerDown = mAuthController.isUdfpsFingerDown();
+ final boolean isPostureAllowedForFaceAuth =
+ mConfigFaceAuthSupportedPosture == 0 /* DEVICE_POSTURE_UNKNOWN */ ? true
+ : (mPostureState == mConfigFaceAuthSupportedPosture);
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
@@ -2818,7 +2839,8 @@
&& faceAuthAllowedOrDetectionIsNeeded && mIsPrimaryUser
&& (!mSecureCameraLaunched || mOccludingAppRequestingFace)
&& faceAndFpNotAuthenticated
- && !mGoingToSleep;
+ && !mGoingToSleep
+ && isPostureAllowedForFaceAuth;
// Aggregate relevant fields for debug logging.
logListenerModelData(
@@ -2838,6 +2860,7 @@
mKeyguardGoingAway,
shouldListenForFaceAssistant,
mOccludingAppRequestingFace,
+ isPostureAllowedForFaceAuth,
mIsPrimaryUser,
mSecureCameraLaunched,
supportsDetect,
@@ -2923,7 +2946,7 @@
getKeyguardSessionId(),
faceAuthUiEvent.getExtraInfo()
);
-
+ mLogger.logFaceUnlockPossible(unlockPossible);
if (unlockPossible) {
mFaceCancelSignal = new CancellationSignal();
@@ -3845,11 +3868,6 @@
mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);
}
- if (mSfpsRequireScreenOnToAuthPrefObserver != null) {
- mContext.getContentResolver().unregisterContentObserver(
- mSfpsRequireScreenOnToAuthPrefObserver);
- }
-
try {
ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
} catch (RemoteException e) {
@@ -3926,8 +3944,14 @@
} else if (isSfpsSupported()) {
pw.println(" sfpsEnrolled=" + isSfpsEnrolled());
pw.println(" shouldListenForSfps=" + shouldListenForFingerprint(false));
- pw.println(" mSfpsRequireScreenOnToAuthPrefEnabled="
- + mSfpsRequireScreenOnToAuthPrefEnabled);
+ if (isSfpsEnrolled()) {
+ final boolean interactiveToAuthEnabled =
+ mFingerprintInteractiveToAuthProvider != null &&
+ mFingerprintInteractiveToAuthProvider
+ .isEnabled(getCurrentUser());
+ pw.println(" interactiveToAuthEnabled="
+ + interactiveToAuthEnabled);
+ }
}
new DumpsysTableLogger(
"KeyguardFingerprintListen",
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index b106fec..2c7eceb 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -17,36 +17,46 @@
package com.android.keyguard.logging
import com.android.systemui.log.dagger.KeyguardLog
-import com.android.systemui.plugins.log.ConstantStringsLogger
-import com.android.systemui.plugins.log.ConstantStringsLoggerImpl
import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.ERROR
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.plugins.log.LogLevel
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
-private const val TAG = "KeyguardLog"
+private const val BIO_TAG = "KeyguardLog"
/**
* Generic logger for keyguard that's wrapping [LogBuffer]. This class should be used for adding
* temporary logs or logs for smaller classes when creating whole new [LogBuffer] wrapper might be
* an overkill.
*/
-class KeyguardLogger @Inject constructor(@KeyguardLog val buffer: LogBuffer) :
- ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
+class KeyguardLogger
+@Inject
+constructor(
+ @KeyguardLog val buffer: LogBuffer,
+) {
+ @JvmOverloads
+ fun log(
+ tag: String,
+ level: LogLevel,
+ @CompileTimeConstant msg: String,
+ ex: Throwable? = null,
+ ) = buffer.log(tag, level, msg, ex)
- fun logException(ex: Exception, @CompileTimeConstant logMsg: String) {
- buffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
- }
-
- fun v(msg: String, arg: Any) {
- buffer.log(TAG, VERBOSE, { str1 = arg.toString() }, { "$msg: $str1" })
- }
-
- fun i(msg: String, arg: Any) {
- buffer.log(TAG, INFO, { str1 = arg.toString() }, { "$msg: $str1" })
+ fun log(
+ tag: String,
+ level: LogLevel,
+ @CompileTimeConstant msg: String,
+ arg: Any,
+ ) {
+ buffer.log(
+ tag,
+ level,
+ {
+ str1 = msg
+ str2 = arg.toString()
+ },
+ { "$str1: $str2" }
+ )
}
@JvmOverloads
@@ -56,8 +66,8 @@
msg: String? = null
) {
buffer.log(
- TAG,
- DEBUG,
+ BIO_TAG,
+ LogLevel.DEBUG,
{
str1 = context
str2 = "$msgId"
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 21d3b24..5b42455 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -132,6 +132,12 @@
logBuffer.log(TAG, DEBUG, { int1 = faceRunningState }, { "faceRunningState: $int1" })
}
+ fun logFaceUnlockPossible(isFaceUnlockPossible: Boolean) {
+ logBuffer.log(TAG, DEBUG,
+ { bool1 = isFaceUnlockPossible },
+ {"isUnlockWithFacePossible: $bool1"})
+ }
+
fun logFingerprintAuthForWrongUser(authUserId: Int) {
logBuffer.log(TAG, DEBUG,
{ int1 = authUserId },
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 0fc9ef9..632fcdc 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -22,8 +22,6 @@
import android.os.HandlerThread;
import android.util.Log;
-import androidx.annotation.Nullable;
-
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dagger.WMComponent;
@@ -55,7 +53,6 @@
mContext = context;
}
- @Nullable
protected abstract GlobalRootComponent.Builder getGlobalRootComponentBuilder();
/**
@@ -72,11 +69,6 @@
* Starts the initialization process. This stands up the Dagger graph.
*/
public void init(boolean fromTest) throws ExecutionException, InterruptedException {
- GlobalRootComponent.Builder globalBuilder = getGlobalRootComponentBuilder();
- if (globalBuilder == null) {
- return;
- }
-
mRootComponent = getGlobalRootComponentBuilder()
.context(mContext)
.instrumentationTest(fromTest)
@@ -127,7 +119,6 @@
.setBackAnimation(Optional.ofNullable(null))
.setDesktopMode(Optional.ofNullable(null));
}
-
mSysUIComponent = builder.build();
if (initializeComponents) {
mSysUIComponent.init();
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
index 55c095b..8aa3040 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui
-import android.app.Application
import android.content.Context
import com.android.systemui.dagger.DaggerReferenceGlobalRootComponent
import com.android.systemui.dagger.GlobalRootComponent
@@ -25,17 +24,7 @@
* {@link SystemUIInitializer} that stands up AOSP SystemUI.
*/
class SystemUIInitializerImpl(context: Context) : SystemUIInitializer(context) {
-
- override fun getGlobalRootComponentBuilder(): GlobalRootComponent.Builder? {
- return when (Application.getProcessName()) {
- SCREENSHOT_CROSS_PROFILE_PROCESS -> null
- else -> DaggerReferenceGlobalRootComponent.builder()
- }
- }
-
- companion object {
- private const val SYSTEMUI_PROCESS = "com.android.systemui"
- private const val SCREENSHOT_CROSS_PROFILE_PROCESS =
- "$SYSTEMUI_PROCESS:screenshot_cross_profile"
+ override fun getGlobalRootComponentBuilder(): GlobalRootComponent.Builder {
+ return DaggerReferenceGlobalRootComponent.builder()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 092339a..2dc0cd3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -18,6 +18,7 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;
@@ -76,6 +77,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.data.repository.BiometricType;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.VibratorHelper;
@@ -85,8 +87,10 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -150,6 +154,7 @@
@Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps;
@Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
+ @NonNull private final Map<Integer, Boolean> mFpEnrolledForUser = new HashMap<>();
@NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
@NonNull private final SparseBooleanArray mSfpsEnrolledForUser;
@NonNull private final SensorPrivacyManager mSensorPrivacyManager;
@@ -161,7 +166,6 @@
private final @Background DelayableExecutor mBackgroundExecutor;
private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
-
private final VibratorHelper mVibratorHelper;
private void vibrateSuccess(int modality) {
@@ -331,27 +335,35 @@
mExecution.assertIsMainThread();
Log.d(TAG, "handleEnrollmentsChanged, userId: " + userId + ", sensorId: " + sensorId
+ ", hasEnrollments: " + hasEnrollments);
- if (mUdfpsProps == null) {
- Log.d(TAG, "handleEnrollmentsChanged, mUdfpsProps is null");
- } else {
- for (FingerprintSensorPropertiesInternal prop : mUdfpsProps) {
+ BiometricType sensorBiometricType = BiometricType.UNKNOWN;
+ if (mFpProps != null) {
+ for (FingerprintSensorPropertiesInternal prop: mFpProps) {
if (prop.sensorId == sensorId) {
- mUdfpsEnrolledForUser.put(userId, hasEnrollments);
+ mFpEnrolledForUser.put(userId, hasEnrollments);
+ if (prop.isAnyUdfpsType()) {
+ sensorBiometricType = BiometricType.UNDER_DISPLAY_FINGERPRINT;
+ mUdfpsEnrolledForUser.put(userId, hasEnrollments);
+ } else if (prop.isAnySidefpsType()) {
+ sensorBiometricType = BiometricType.SIDE_FINGERPRINT;
+ mSfpsEnrolledForUser.put(userId, hasEnrollments);
+ } else if (prop.sensorType == TYPE_REAR) {
+ sensorBiometricType = BiometricType.REAR_FINGERPRINT;
+ }
+ break;
}
}
}
-
- if (mSidefpsProps == null) {
- Log.d(TAG, "handleEnrollmentsChanged, mSidefpsProps is null");
- } else {
- for (FingerprintSensorPropertiesInternal prop : mSidefpsProps) {
+ if (mFaceProps != null && sensorBiometricType == BiometricType.UNKNOWN) {
+ for (FaceSensorPropertiesInternal prop : mFaceProps) {
if (prop.sensorId == sensorId) {
- mSfpsEnrolledForUser.put(userId, hasEnrollments);
+ sensorBiometricType = BiometricType.FACE;
+ break;
}
}
}
for (Callback cb : mCallbacks) {
cb.onEnrollmentsChanged();
+ cb.onEnrollmentsChanged(sensorBiometricType, userId, hasEnrollments);
}
}
@@ -604,6 +616,11 @@
}
}
+ /** Get FP sensor properties */
+ public @Nullable List<FingerprintSensorPropertiesInternal> getFingerprintProperties() {
+ return mFpProps;
+ }
+
/**
* @return where the face sensor exists in pixels in the current device orientation. Returns
* null if no face sensor exists.
@@ -828,7 +845,7 @@
}
@Override
- public void setBiometicContextListener(IBiometricContextListener listener) {
+ public void setBiometricContextListener(IBiometricContextListener listener) {
mBiometricContextListener = listener;
notifyDozeChanged(mStatusBarStateController.isDozing(),
mWakefulnessLifecycle.getWakefulness());
@@ -1081,6 +1098,13 @@
return mSfpsEnrolledForUser.get(userId);
}
+ /**
+ * Whether the passed userId has enrolled at least one fingerprint.
+ */
+ public boolean isFingerprintEnrolled(int userId) {
+ return mFpEnrolledForUser.getOrDefault(userId, false);
+ }
+
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
mCurrentDialogArgs = args;
@@ -1263,6 +1287,16 @@
default void onEnrollmentsChanged() {}
/**
+ * Called when UDFPS enrollments have changed. This is called after boot and on changes to
+ * enrollment.
+ */
+ default void onEnrollmentsChanged(
+ @NonNull BiometricType biometricType,
+ int userId,
+ boolean hasEnrollments
+ ) {}
+
+ /**
* Called when the biometric prompt starts showing.
*/
default void onBiometricPromptShown() {}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProvider.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProvider.java
new file mode 100644
index 0000000..902bb18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProvider.java
@@ -0,0 +1,27 @@
+/*
+ * 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.biometrics;
+
+/** Provides the status of the interactive to auth feature. */
+public interface FingerprintInteractiveToAuthProvider {
+ /**
+ *
+ * @param userId the user Id.
+ * @return true if the InteractiveToAuthFeature is enabled, false if disabled.
+ */
+ boolean isEnabled(int userId);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 4130cf5..ef7dcb7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -190,11 +190,6 @@
open fun listenForTouchesOutsideView(): Boolean = false
/**
- * Called on touches outside of the view if listenForTouchesOutsideView returns true
- */
- open fun onTouchOutsideView() {}
-
- /**
* Called when a view should announce an accessibility event.
*/
open fun doAnnounceForAccessibility(str: String) {}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index f3136ba..cea1779 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -73,6 +73,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -149,6 +150,7 @@
@NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
@NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@Nullable private final TouchProcessor mTouchProcessor;
+ @NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@@ -232,12 +234,12 @@
mShadeExpansionStateManager, mKeyguardViewManager,
mKeyguardUpdateMonitor, mDialogManager, mDumpManager,
mLockscreenShadeTransitionController, mConfigurationController,
- mSystemClock, mKeyguardStateController,
+ mKeyguardStateController,
mUnlockedScreenOffAnimationController,
mUdfpsDisplayMode, requestId, reason, callback,
(view, event, fromUdfpsView) -> onTouch(requestId, event,
fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
- mPrimaryBouncerInteractor)));
+ mPrimaryBouncerInteractor, mAlternateBouncerInteractor)));
}
@Override
@@ -329,13 +331,13 @@
if (!mOverlayParams.equals(overlayParams)) {
mOverlayParams = overlayParams;
- final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateBouncer();
+ final boolean wasShowingAlternateBouncer = mAlternateBouncerInteractor.isVisibleState();
// When the bounds change it's always necessary to re-create the overlay's window with
// new LayoutParams. If the overlay needs to be shown, this will re-create and show the
// overlay with the updated LayoutParams. Otherwise, the overlay will remain hidden.
redrawOverlay();
- if (wasShowingAltAuth) {
+ if (wasShowingAlternateBouncer) {
mKeyguardViewManager.showBouncer(true);
}
}
@@ -543,9 +545,6 @@
final UdfpsView udfpsView = mOverlay.getOverlayView();
boolean handled = false;
switch (event.getActionMasked()) {
- case MotionEvent.ACTION_OUTSIDE:
- udfpsView.onTouchOutsideView();
- return true;
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_HOVER_ENTER:
Trace.beginSection("UdfpsController.onTouch.ACTION_DOWN");
@@ -719,7 +718,8 @@
@NonNull Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider,
@NonNull @BiometricsBackground Executor biometricsExecutor,
@NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
- @NonNull SinglePointerTouchProcessor singlePointerTouchProcessor) {
+ @NonNull SinglePointerTouchProcessor singlePointerTouchProcessor,
+ @NonNull AlternateBouncerInteractor alternateBouncerInteractor) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -759,6 +759,7 @@
mBiometricExecutor = biometricsExecutor;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
+ mAlternateBouncerInteractor = alternateBouncerInteractor;
mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
? singlePointerTouchProcessor : null;
@@ -853,9 +854,7 @@
onFingerUp(mOverlay.getRequestId(), oldView);
}
final boolean removed = mOverlay.hide();
- if (mKeyguardViewManager.isShowingAlternateBouncer()) {
- mKeyguardViewManager.hideAlternateBouncer(true);
- }
+ mKeyguardViewManager.hideAlternateBouncer(true);
Log.v(TAG, "hideUdfpsOverlay | removing window: " + removed);
} else {
Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 8db4927..a3c4985 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -50,6 +50,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
@@ -59,7 +60,6 @@
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.time.SystemClock
private const val TAG = "UdfpsControllerOverlay"
@@ -86,7 +86,6 @@
private val dumpManager: DumpManager,
private val transitionController: LockscreenShadeTransitionController,
private val configurationController: ConfigurationController,
- private val systemClock: SystemClock,
private val keyguardStateController: KeyguardStateController,
private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
@@ -97,7 +96,8 @@
private val activityLaunchAnimator: ActivityLaunchAnimator,
private val featureFlags: FeatureFlags,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
- private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
+ private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
) {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsView? = null
@@ -255,14 +255,14 @@
dumpManager,
transitionController,
configurationController,
- systemClock,
keyguardStateController,
unlockedScreenOffAnimationController,
dialogManager,
controller,
activityLaunchAnimator,
featureFlags,
- primaryBouncerInteractor
+ primaryBouncerInteractor,
+ alternateBouncerInteractor,
)
}
REASON_AUTH_BP -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 63144fc..583ee3a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -31,6 +31,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -42,13 +43,13 @@
import com.android.systemui.statusbar.phone.KeyguardBouncer
import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.AlternateBouncer
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.OccludingAppBiometricUI
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.time.SystemClock
import java.io.PrintWriter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -65,25 +66,27 @@
dumpManager: DumpManager,
private val lockScreenShadeTransitionController: LockscreenShadeTransitionController,
private val configurationController: ConfigurationController,
- private val systemClock: SystemClock,
private val keyguardStateController: KeyguardStateController,
private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
systemUIDialogManager: SystemUIDialogManager,
private val udfpsController: UdfpsController,
private val activityLaunchAnimator: ActivityLaunchAnimator,
featureFlags: FeatureFlags,
- private val primaryBouncerInteractor: PrimaryBouncerInteractor
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val alternateBouncerInteractor: AlternateBouncerInteractor,
) :
UdfpsAnimationViewController<UdfpsKeyguardView>(
view,
statusBarStateController,
shadeExpansionStateManager,
systemUIDialogManager,
- dumpManager
+ dumpManager,
) {
private val useExpandedOverlay: Boolean =
featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER)
+ private val isModernAlternateBouncerEnabled: Boolean =
+ featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)
private var showingUdfpsBouncer = false
private var udfpsRequested = false
private var qsExpansion = 0f
@@ -91,7 +94,6 @@
private var statusBarState = 0
private var transitionToFullShadeProgress = 0f
private var lastDozeAmount = 0f
- private var lastUdfpsBouncerShowTime: Long = -1
private var panelExpansionFraction = 0f
private var launchTransitionFadingAway = false
private var isLaunchingActivity = false
@@ -244,20 +246,8 @@
}
}
- private val mAlternateBouncer: AlternateBouncer =
- object : AlternateBouncer {
- override fun showAlternateBouncer(): Boolean {
- return showUdfpsBouncer(true)
- }
-
- override fun hideAlternateBouncer(): Boolean {
- return showUdfpsBouncer(false)
- }
-
- override fun isShowingAlternateBouncer(): Boolean {
- return showingUdfpsBouncer
- }
-
+ private val occludingAppBiometricUI: OccludingAppBiometricUI =
+ object : OccludingAppBiometricUI {
override fun requestUdfps(request: Boolean, color: Int) {
udfpsRequested = request
view.requestUdfps(request, color)
@@ -275,16 +265,19 @@
override fun onInit() {
super.onInit()
- keyguardViewManager.setAlternateBouncer(mAlternateBouncer)
+ keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI)
}
init {
- if (isModernBouncerEnabled) {
+ if (isModernBouncerEnabled || isModernAlternateBouncerEnabled) {
view.repeatWhenAttached {
// repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
// can make the view not visible; and we still want to listen for events
// that may make the view visible again.
- repeatOnLifecycle(Lifecycle.State.CREATED) { listenForBouncerExpansion(this) }
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ if (isModernBouncerEnabled) listenForBouncerExpansion(this)
+ if (isModernAlternateBouncerEnabled) listenForAlternateBouncerVisibility(this)
+ }
}
}
}
@@ -300,8 +293,18 @@
}
}
+ @VisibleForTesting
+ internal suspend fun listenForAlternateBouncerVisibility(scope: CoroutineScope): Job {
+ return scope.launch {
+ alternateBouncerInteractor.isVisible.collect { isVisible: Boolean ->
+ showUdfpsBouncer(isVisible)
+ }
+ }
+ }
+
public override fun onViewAttached() {
super.onViewAttached()
+ alternateBouncerInteractor.setAlternateBouncerUIAvailable(true)
val dozeAmount = statusBarStateController.dozeAmount
lastDozeAmount = dozeAmount
stateListener.onDozeAmountChanged(dozeAmount, dozeAmount)
@@ -326,7 +329,8 @@
view.updatePadding()
updateAlpha()
updatePauseAuth()
- keyguardViewManager.setAlternateBouncer(mAlternateBouncer)
+ keyguardViewManager.setLegacyAlternateBouncer(legacyAlternateBouncer)
+ keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI)
lockScreenShadeTransitionController.udfpsKeyguardViewController = this
activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
view.mUseExpandedOverlay = useExpandedOverlay
@@ -334,10 +338,12 @@
override fun onViewDetached() {
super.onViewDetached()
+ alternateBouncerInteractor.setAlternateBouncerUIAvailable(false)
faceDetectRunning = false
keyguardStateController.removeCallback(keyguardStateControllerCallback)
statusBarStateController.removeCallback(stateListener)
- keyguardViewManager.removeAlternateAuthInterceptor(mAlternateBouncer)
+ keyguardViewManager.removeLegacyAlternateBouncer(legacyAlternateBouncer)
+ keyguardViewManager.removeOccludingAppBiometricUI(occludingAppBiometricUI)
keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
configurationController.removeCallback(configurationListener)
shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
@@ -356,7 +362,16 @@
override fun dump(pw: PrintWriter, args: Array<String>) {
super.dump(pw, args)
pw.println("isModernBouncerEnabled=$isModernBouncerEnabled")
+ pw.println("isModernAlternateBouncerEnabled=$isModernAlternateBouncerEnabled")
pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer")
+ pw.println(
+ "altBouncerInteractor#isAlternateBouncerVisible=" +
+ "${alternateBouncerInteractor.isVisibleState()}"
+ )
+ pw.println(
+ "altBouncerInteractor#canShowAlternateBouncerForFingerprint=" +
+ "${alternateBouncerInteractor.canShowAlternateBouncerForFingerprint()}"
+ )
pw.println("faceDetectRunning=$faceDetectRunning")
pw.println("statusBarState=" + StatusBarState.toString(statusBarState))
pw.println("transitionToFullShadeProgress=$transitionToFullShadeProgress")
@@ -385,9 +400,6 @@
val udfpsAffordanceWasNotShowing = shouldPauseAuth()
showingUdfpsBouncer = show
if (showingUdfpsBouncer) {
- lastUdfpsBouncerShowTime = systemClock.uptimeMillis()
- }
- if (showingUdfpsBouncer) {
if (udfpsAffordanceWasNotShowing) {
view.animateInUdfpsBouncer(null)
}
@@ -452,7 +464,7 @@
return if (isModernBouncerEnabled) {
inputBouncerExpansion == 1f
} else {
- keyguardViewManager.isBouncerShowing && !keyguardViewManager.isShowingAlternateBouncer
+ keyguardViewManager.isBouncerShowing && !alternateBouncerInteractor.isVisibleState()
}
}
@@ -460,30 +472,6 @@
return true
}
- override fun onTouchOutsideView() {
- maybeShowInputBouncer()
- }
-
- /**
- * If we were previously showing the udfps bouncer, hide it and instead show the regular
- * (pin/pattern/password) bouncer.
- *
- * Does nothing if we weren't previously showing the UDFPS bouncer.
- */
- private fun maybeShowInputBouncer() {
- if (showingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
- keyguardViewManager.showPrimaryBouncer(true)
- }
- }
-
- /**
- * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside of the
- * udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password bouncer.
- */
- private fun hasUdfpsBouncerShownWithMinTime(): Boolean {
- return systemClock.uptimeMillis() - lastUdfpsBouncerShowTime > 200
- }
-
/**
* Set the progress we're currently transitioning to the full shade. 0.0f means we're not
* transitioning yet, while 1.0f means we've fully dragged down. For example, start swiping down
@@ -545,7 +533,7 @@
if (isModernBouncerEnabled) {
return
}
- val altBouncerShowing = keyguardViewManager.isShowingAlternateBouncer
+ val altBouncerShowing = alternateBouncerInteractor.isVisibleState()
if (altBouncerShowing || !keyguardViewManager.primaryBouncerIsOrWillBeShowing()) {
inputBouncerHiddenAmount = 1f
} else if (keyguardViewManager.isBouncerShowing) {
@@ -554,6 +542,21 @@
}
}
+ private val legacyAlternateBouncer: LegacyAlternateBouncer =
+ object : LegacyAlternateBouncer {
+ override fun showAlternateBouncer(): Boolean {
+ return showUdfpsBouncer(true)
+ }
+
+ override fun hideAlternateBouncer(): Boolean {
+ return showUdfpsBouncer(false)
+ }
+
+ override fun isShowingAlternateBouncer(): Boolean {
+ return showingUdfpsBouncer
+ }
+ }
+
companion object {
const val TAG = "UdfpsKeyguardViewController"
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index 4a8877e..e61c614 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -111,10 +111,6 @@
}
}
- fun onTouchOutsideView() {
- animationViewController?.onTouchOutsideView()
- }
-
override fun onAttachedToWindow() {
super.onAttachedToWindow()
Log.v(TAG, "onAttachedToWindow")
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
index 8572242..682d38a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
@@ -18,6 +18,7 @@
import android.graphics.Point
import android.graphics.Rect
+import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import kotlin.math.cos
import kotlin.math.pow
@@ -50,7 +51,8 @@
return result <= 1
}
- private fun calculateSensorPoints(sensorBounds: Rect): List<Point> {
+ @VisibleForTesting
+ fun calculateSensorPoints(sensorBounds: Rect): List<Point> {
val sensorX = sensorBounds.centerX()
val sensorY = sensorBounds.centerY()
val cornerOffset: Int = sensorBounds.width() / 4
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
index 338bf66..693f64a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
@@ -27,6 +27,8 @@
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
+private val SUPPORTED_ROTATIONS = setOf(Surface.ROTATION_90, Surface.ROTATION_270)
+
/**
* TODO(b/259140693): Consider using an object pool of TouchProcessorResult to avoid allocations.
*/
@@ -129,19 +131,27 @@
val nativeY = naturalTouch.y / overlayParams.scaleFactor
val nativeMinor: Float = getTouchMinor(pointerIndex) / overlayParams.scaleFactor
val nativeMajor: Float = getTouchMajor(pointerIndex) / overlayParams.scaleFactor
+ var nativeOrientation: Float = getOrientation(pointerIndex)
+ if (SUPPORTED_ROTATIONS.contains(overlayParams.rotation)) {
+ nativeOrientation = toRadVerticalFromRotated(nativeOrientation.toDouble()).toFloat()
+ }
return NormalizedTouchData(
pointerId = getPointerId(pointerIndex),
x = nativeX,
y = nativeY,
minor = nativeMinor,
major = nativeMajor,
- // TODO(b/259311354): touch orientation should be reported relative to Surface.ROTATION_O.
- orientation = getOrientation(pointerIndex),
+ orientation = nativeOrientation,
time = eventTime,
gestureStart = downTime,
)
}
+private fun toRadVerticalFromRotated(rad: Double): Double {
+ val piBound = ((rad % Math.PI) + Math.PI / 2) % Math.PI
+ return if (piBound < Math.PI / 2.0) piBound else piBound - Math.PI
+}
+
/**
* Returns the [MotionEvent.getRawX] and [MotionEvent.getRawY] of the given pointer as if the device
* is in the [Surface.ROTATION_0] orientation.
@@ -152,7 +162,7 @@
): PointF {
val touchPoint = PointF(getRawX(pointerIndex), getRawY(pointerIndex))
val rot = overlayParams.rotation
- if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
+ if (SUPPORTED_ROTATIONS.contains(rot)) {
RotationUtils.rotatePointF(
touchPoint,
RotationUtils.deltaRotation(rot, Surface.ROTATION_0),
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index e8e1f2e..e9ac840 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -176,7 +176,8 @@
private @Classifier.InteractionType int mPriorInteractionType = Classifier.GENERIC;
@Inject
- public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider,
+ public BrightLineFalsingManager(
+ FalsingDataProvider falsingDataProvider,
MetricsLogger metricsLogger,
@Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers,
SingleTapClassifier singleTapClassifier, LongTapClassifier longTapClassifier,
@@ -399,7 +400,9 @@
|| mDataProvider.isJustUnlockedWithFace()
|| mDataProvider.isDocked()
|| mAccessibilityManager.isTouchExplorationEnabled()
- || mDataProvider.isA11yAction();
+ || mDataProvider.isA11yAction()
+ || (mFeatureFlags.isEnabled(Flags.FALSING_OFF_FOR_UNFOLDED)
+ && !mDataProvider.isFolded());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 09ebeea..5f347c1 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -16,6 +16,7 @@
package com.android.systemui.classifier;
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
@@ -42,6 +43,7 @@
private final int mWidthPixels;
private final int mHeightPixels;
private BatteryController mBatteryController;
+ private final FoldStateListener mFoldStateListener;
private final DockManager mDockManager;
private final float mXdpi;
private final float mYdpi;
@@ -65,12 +67,14 @@
public FalsingDataProvider(
DisplayMetrics displayMetrics,
BatteryController batteryController,
+ FoldStateListener foldStateListener,
DockManager dockManager) {
mXdpi = displayMetrics.xdpi;
mYdpi = displayMetrics.ydpi;
mWidthPixels = displayMetrics.widthPixels;
mHeightPixels = displayMetrics.heightPixels;
mBatteryController = batteryController;
+ mFoldStateListener = foldStateListener;
mDockManager = dockManager;
FalsingClassifier.logInfo("xdpi, ydpi: " + getXdpi() + ", " + getYdpi());
@@ -376,6 +380,10 @@
return mBatteryController.isWirelessCharging() || mDockManager.isDocked();
}
+ public boolean isFolded() {
+ return Boolean.TRUE.equals(mFoldStateListener.getFolded());
+ }
+
/** Implement to be alerted abotu the beginning and ending of falsing tracking. */
public interface SessionListener {
/** Called when the lock screen is shown and falsing-tracking begins. */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index b8e6673..6d13740 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -31,6 +31,7 @@
import com.android.systemui.appops.dagger.AppOpsModule;
import com.android.systemui.assist.AssistModule;
import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
+import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
import com.android.systemui.biometrics.dagger.BiometricsModule;
import com.android.systemui.biometrics.dagger.UdfpsModule;
@@ -221,6 +222,9 @@
@BindsOptionalOf
abstract AlternateUdfpsTouchProvider optionalUdfpsTouchProvider();
+ @BindsOptionalOf
+ abstract FingerprintInteractiveToAuthProvider optionalFingerprintInteractiveToAuthProvider();
+
@SysUISingleton
@Binds
abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index f244cb0..96bce4c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -26,6 +27,9 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.systemui.R;
+import com.android.systemui.shared.shadow.DoubleShadowIconDrawable;
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo;
+import com.android.systemui.statusbar.AlphaOptimizedImageView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -60,8 +64,15 @@
public static final int STATUS_ICON_PRIORITY_MODE_ON = 6;
private final Map<Integer, View> mStatusIcons = new HashMap<>();
+ private Context mContext;
private ViewGroup mSystemStatusViewGroup;
private ViewGroup mExtraSystemStatusViewGroup;
+ private ShadowInfo mKeyShadowInfo;
+ private ShadowInfo mAmbientShadowInfo;
+ private int mDrawableSize;
+ private int mDrawableInsetSize;
+ private static final float KEY_SHADOW_ALPHA = 0.35f;
+ private static final float AMBIENT_SHADOW_ALPHA = 0.4f;
public DreamOverlayStatusBarView(Context context) {
this(context, null);
@@ -73,6 +84,7 @@
public DreamOverlayStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
+ mContext = context;
}
public DreamOverlayStatusBarView(
@@ -80,14 +92,36 @@
super(context, attrs, defStyleAttr, defStyleRes);
}
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ mKeyShadowInfo = createShadowInfo(
+ R.dimen.dream_overlay_status_bar_key_text_shadow_radius,
+ R.dimen.dream_overlay_status_bar_key_text_shadow_dx,
+ R.dimen.dream_overlay_status_bar_key_text_shadow_dy,
+ KEY_SHADOW_ALPHA
+ );
+
+ mAmbientShadowInfo = createShadowInfo(
+ R.dimen.dream_overlay_status_bar_ambient_text_shadow_radius,
+ R.dimen.dream_overlay_status_bar_ambient_text_shadow_dx,
+ R.dimen.dream_overlay_status_bar_ambient_text_shadow_dy,
+ AMBIENT_SHADOW_ALPHA
+ );
+
+ mDrawableSize = mContext
+ .getResources()
+ .getDimensionPixelSize(R.dimen.dream_overlay_status_bar_icon_size);
+ mDrawableInsetSize = mContext
+ .getResources()
+ .getDimensionPixelSize(R.dimen.dream_overlay_icon_inset_dimen);
+
mStatusIcons.put(STATUS_ICON_WIFI_UNAVAILABLE,
- fetchStatusIconForResId(R.id.dream_overlay_wifi_status));
+ addDoubleShadow(fetchStatusIconForResId(R.id.dream_overlay_wifi_status)));
mStatusIcons.put(STATUS_ICON_ALARM_SET,
- fetchStatusIconForResId(R.id.dream_overlay_alarm_set));
+ addDoubleShadow(fetchStatusIconForResId(R.id.dream_overlay_alarm_set)));
mStatusIcons.put(STATUS_ICON_CAMERA_DISABLED,
fetchStatusIconForResId(R.id.dream_overlay_camera_off));
mStatusIcons.put(STATUS_ICON_MIC_DISABLED,
@@ -97,7 +131,7 @@
mStatusIcons.put(STATUS_ICON_NOTIFICATIONS,
fetchStatusIconForResId(R.id.dream_overlay_notification_indicator));
mStatusIcons.put(STATUS_ICON_PRIORITY_MODE_ON,
- fetchStatusIconForResId(R.id.dream_overlay_priority_mode));
+ addDoubleShadow(fetchStatusIconForResId(R.id.dream_overlay_priority_mode)));
mSystemStatusViewGroup = findViewById(R.id.dream_overlay_system_status);
mExtraSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items);
@@ -137,4 +171,34 @@
}
return false;
}
+
+ private View addDoubleShadow(View icon) {
+ if (icon instanceof AlphaOptimizedImageView) {
+ AlphaOptimizedImageView i = (AlphaOptimizedImageView) icon;
+ Drawable drawableIcon = i.getDrawable();
+ i.setImageDrawable(new DoubleShadowIconDrawable(
+ mKeyShadowInfo,
+ mAmbientShadowInfo,
+ drawableIcon,
+ mDrawableSize,
+ mDrawableInsetSize
+ ));
+ }
+ return icon;
+ }
+
+ private ShadowInfo createShadowInfo(int blurId, int offsetXId, int offsetYId, float alpha) {
+ return new ShadowInfo(
+ fetchDimensionForResId(blurId),
+ fetchDimensionForResId(offsetXId),
+ fetchDimensionForResId(offsetYId),
+ alpha
+ );
+ }
+
+ private Float fetchDimensionForResId(int resId) {
+ return mContext
+ .getResources()
+ .getDimension(resId);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 20ae64c..c9a2a0b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -114,8 +114,6 @@
// ** Flag retired **
// public static final BooleanFlag KEYGUARD_LAYOUT =
// new BooleanFlag(200, true);
- // TODO(b/254512713): Tracking Bug
- @JvmField val LOCKSCREEN_ANIMATIONS = releasedFlag(201, "lockscreen_animations")
// TODO(b/254512750): Tracking Bug
val NEW_UNLOCK_SWIPE_ANIMATION = releasedFlag(202, "new_unlock_swipe_animation")
@@ -165,7 +163,7 @@
// TODO(b/255618149): Tracking Bug
@JvmField
val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
- unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = false)
+ unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = true)
/** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */
// TODO(b/256513609): Tracking Bug
@@ -180,6 +178,13 @@
@JvmField
val LIGHT_REVEAL_MIGRATION = unreleasedFlag(218, "light_reveal_migration", teamfood = false)
+ /**
+ * Whether to use the new alternate bouncer architecture, a refactor of and eventual replacement
+ * of the Alternate/Authentication Bouncer. No visual UI changes.
+ */
+ // TODO(b/260619425): Tracking Bug
+ @JvmField val MODERN_ALTERNATE_BOUNCER = unreleasedFlag(219, "modern_alternate_bouncer")
+
/** Flag to control the migration of face auth to modern architecture. */
// TODO(b/262838215): Tracking bug
@JvmField val FACE_AUTH_REFACTOR = unreleasedFlag(220, "face_auth_refactor")
@@ -202,6 +207,9 @@
val AUTO_PIN_CONFIRMATION =
unreleasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation")
+ // TODO(b/262859270): Tracking Bug
+ @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -255,10 +263,11 @@
// TODO(b/256614751): Tracking Bug
val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND =
- unreleasedFlag(608, "new_status_bar_mobile_icons_backend")
+ unreleasedFlag(608, "new_status_bar_mobile_icons_backend", teamfood = true)
// TODO(b/256613548): Tracking Bug
- val NEW_STATUS_BAR_WIFI_ICON_BACKEND = unreleasedFlag(609, "new_status_bar_wifi_icon_backend")
+ val NEW_STATUS_BAR_WIFI_ICON_BACKEND =
+ unreleasedFlag(609, "new_status_bar_wifi_icon_backend", teamfood = true)
// TODO(b/256623670): Tracking Bug
@JvmField
@@ -297,7 +306,7 @@
// 900 - media
// TODO(b/254512697): Tracking Bug
- val MEDIA_TAP_TO_TRANSFER = unreleasedFlag(900, "media_tap_to_transfer", teamfood = true)
+ val MEDIA_TAP_TO_TRANSFER = releasedFlag(900, "media_tap_to_transfer")
// TODO(b/254512502): Tracking Bug
val MEDIA_SESSION_ACTIONS = unreleasedFlag(901, "media_session_actions")
@@ -333,7 +342,8 @@
// TODO(b/254512758): Tracking Bug
@JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
- val SHOW_LOWLIGHT_ON_DIRECT_BOOT = unreleasedFlag(1003, "show_lowlight_on_direct_boot")
+ // TODO(b/265045965): Tracking Bug
+ val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
// 1100 - windowing
@Keep
@@ -431,9 +441,6 @@
unreleasedFlag(1206, "persist.wm.debug.predictive_back_bouncer_anim", teamfood = true)
// 1300 - screenshots
- // TODO(b/254512719): Tracking Bug
- @JvmField val SCREENSHOT_REQUEST_PROCESSOR = releasedFlag(1300, "screenshot_request_processor")
-
// TODO(b/254513155): Tracking Bug
@JvmField
val SCREENSHOT_WORK_PROFILE_POLICY =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e0def25..fe84ac5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -34,6 +34,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.LOCKSCREEN_ANIMATION_DURATION_MS;
+import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -122,6 +123,8 @@
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -507,6 +510,8 @@
private CentralSurfaces mCentralSurfaces;
+ private boolean mUnocclusionTransitionFlagEnabled = false;
+
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
new DeviceConfig.OnPropertiesChangedListener() {
@Override
@@ -958,8 +963,9 @@
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
- setOccluded(true /* isOccluded */, true /* animate */);
-
+ if (!mUnocclusionTransitionFlagEnabled) {
+ setOccluded(true /* isOccluded */, true /* animate */);
+ }
if (apps == null || apps.length == 0 || apps[0] == null) {
if (DEBUG) {
Log.d(TAG, "No apps provided to the OccludeByDream runner; "
@@ -1001,9 +1007,20 @@
applier.scheduleApply(paramsBuilder.build());
});
mOccludeByDreamAnimator.addListener(new AnimatorListenerAdapter() {
+ private boolean mIsCancelled = false;
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mIsCancelled = true;
+ }
+
@Override
public void onAnimationEnd(Animator animation) {
try {
+ if (!mIsCancelled && mUnocclusionTransitionFlagEnabled) {
+ // We're already on the main thread, don't queue this call
+ handleSetOccluded(true /* isOccluded */,
+ false /* animate */);
+ }
finishedCallback.onAnimationFinished();
mOccludeByDreamAnimator = null;
} catch (RemoteException e) {
@@ -1176,6 +1193,7 @@
ScreenOnCoordinator screenOnCoordinator,
InteractionJankMonitor interactionJankMonitor,
DreamOverlayStateController dreamOverlayStateController,
+ FeatureFlags featureFlags,
Lazy<ShadeController> shadeControllerLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
@@ -1230,9 +1248,9 @@
R.dimen.physical_power_button_center_screen_location_y);
mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mDreamOpenAnimationDuration = context.getResources().getInteger(
- com.android.internal.R.integer.config_dreamOpenAnimationDuration);
+ mDreamOpenAnimationDuration = (int) DREAMING_ANIMATION_DURATION_MS;
mDreamCloseAnimationDuration = (int) LOCKSCREEN_ANIMATION_DURATION_MS;
+ mUnocclusionTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION);
}
public void userActivity() {
@@ -1792,7 +1810,6 @@
Trace.beginSection("KeyguardViewMediator#setOccluded");
if (DEBUG) Log.d(TAG, "setOccluded " + isOccluded);
- mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD);
mHandler.removeMessages(SET_OCCLUDED);
Message msg = mHandler.obtainMessage(SET_OCCLUDED, isOccluded ? 1 : 0, animate ? 1 : 0);
mHandler.sendMessage(msg);
@@ -1825,6 +1842,8 @@
private void handleSetOccluded(boolean isOccluded, boolean animate) {
Trace.beginSection("KeyguardViewMediator#handleSetOccluded");
Log.d(TAG, "handleSetOccluded(" + isOccluded + ")");
+ mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD);
+
synchronized (KeyguardViewMediator.this) {
if (mHiding && isOccluded) {
// We're in the process of going away but WindowManager wants to show a
@@ -1907,13 +1926,23 @@
return;
}
- // if the keyguard is already showing, don't bother. check flags in both files
- // to account for the hiding animation which results in a delay and discrepancy
- // between flags
+ // If the keyguard is already showing, see if we don't need to bother re-showing it. Check
+ // flags in both files to account for the hiding animation which results in a delay and
+ // discrepancy between flags.
if (mShowing && mKeyguardStateController.isShowing()) {
- if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
- resetStateLocked();
- return;
+ if (mPM.isInteractive()) {
+ // It's already showing, and we're not trying to show it while the screen is off.
+ // We can simply reset all of the views.
+ if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
+ resetStateLocked();
+ return;
+ } else {
+ // We are trying to show the keyguard while the screen is off - this results from
+ // race conditions involving locking while unlocking. Don't short-circuit here and
+ // ensure the keyguard is fully re-shown.
+ Log.e(TAG,
+ "doKeyguard: already showing, but re-showing since we're not interactive");
+ }
}
// In split system user mode, we never unlock system user.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
index 017b65a..ffd8a02 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
@@ -33,6 +33,7 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.util.time.SystemClock;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -63,6 +64,7 @@
private final Context mContext;
private final DisplayMetrics mDisplayMetrics;
+ private final SystemClock mSystemClock;
@Nullable
private final IWallpaperManager mWallpaperManagerService;
@@ -71,6 +73,9 @@
private @PowerManager.WakeReason int mLastWakeReason = PowerManager.WAKE_REASON_UNKNOWN;
+ public static final long UNKNOWN_LAST_WAKE_TIME = -1;
+ private long mLastWakeTime = UNKNOWN_LAST_WAKE_TIME;
+
@Nullable
private Point mLastWakeOriginLocation = null;
@@ -84,10 +89,12 @@
public WakefulnessLifecycle(
Context context,
@Nullable IWallpaperManager wallpaperManagerService,
+ SystemClock systemClock,
DumpManager dumpManager) {
mContext = context;
mDisplayMetrics = context.getResources().getDisplayMetrics();
mWallpaperManagerService = wallpaperManagerService;
+ mSystemClock = systemClock;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
@@ -104,6 +111,14 @@
}
/**
+ * Returns the most recent time (in device uptimeMillis) the display woke up.
+ * Returns {@link UNKNOWN_LAST_WAKE_TIME} if there hasn't been a wakeup yet.
+ */
+ public long getLastWakeTime() {
+ return mLastWakeTime;
+ }
+
+ /**
* Returns the most recent reason the device went to sleep up. This is one of
* PowerManager.GO_TO_SLEEP_REASON_*.
*/
@@ -117,6 +132,7 @@
}
setWakefulness(WAKEFULNESS_WAKING);
mLastWakeReason = pmWakeReason;
+ mLastWakeTime = mSystemClock.uptimeMillis();
updateLastWakeOriginLocation();
if (mWallpaperManagerService != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 47ef0fa..98d3570 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -39,6 +39,7 @@
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -112,6 +113,7 @@
ScreenOnCoordinator screenOnCoordinator,
InteractionJankMonitor interactionJankMonitor,
DreamOverlayStateController dreamOverlayStateController,
+ FeatureFlags featureFlags,
Lazy<ShadeController> shadeController,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
@@ -142,6 +144,7 @@
screenOnCoordinator,
interactionJankMonitor,
dreamOverlayStateController,
+ featureFlags,
shadeController,
notificationShadeWindowController,
activityLaunchAnimator,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt
new file mode 100644
index 0000000..25d8f40
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
+import android.content.Context
+import android.content.IntentFilter
+import android.os.Looper
+import android.os.UserHandle
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformLatest
+
+/**
+ * Acts as source of truth for biometric features.
+ *
+ * Abstracts-away data sources and their schemas so the rest of the app doesn't need to worry about
+ * upstream changes.
+ */
+interface BiometricRepository {
+ /** Whether any fingerprints are enrolled for the current user. */
+ val isFingerprintEnrolled: StateFlow<Boolean>
+
+ /**
+ * Whether the current user is allowed to use a strong biometric for device entry based on
+ * Android Security policies. If false, the user may be able to use primary authentication for
+ * device entry.
+ */
+ val isStrongBiometricAllowed: StateFlow<Boolean>
+
+ /** Whether fingerprint feature is enabled for the current user by the DevicePolicy */
+ val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean>
+}
+
+@SysUISingleton
+class BiometricRepositoryImpl
+@Inject
+constructor(
+ context: Context,
+ lockPatternUtils: LockPatternUtils,
+ broadcastDispatcher: BroadcastDispatcher,
+ authController: AuthController,
+ userRepository: UserRepository,
+ devicePolicyManager: DevicePolicyManager,
+ @Application scope: CoroutineScope,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ @Main looper: Looper,
+) : BiometricRepository {
+
+ /** UserId of the current selected user. */
+ private val selectedUserId: Flow<Int> =
+ userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged()
+
+ override val isFingerprintEnrolled: StateFlow<Boolean> =
+ selectedUserId
+ .flatMapLatest { userId ->
+ conflatedCallbackFlow {
+ val callback =
+ object : AuthController.Callback {
+ override fun onEnrollmentsChanged(
+ sensorBiometricType: BiometricType,
+ userId: Int,
+ hasEnrollments: Boolean
+ ) {
+ if (sensorBiometricType.isFingerprint) {
+ trySendWithFailureLogging(
+ hasEnrollments,
+ TAG,
+ "update fpEnrollment"
+ )
+ }
+ }
+ }
+ authController.addCallback(callback)
+ awaitClose { authController.removeCallback(callback) }
+ }
+ }
+ .stateIn(
+ scope,
+ started = SharingStarted.Eagerly,
+ initialValue =
+ authController.isFingerprintEnrolled(userRepository.getSelectedUserInfo().id)
+ )
+
+ override val isStrongBiometricAllowed: StateFlow<Boolean> =
+ selectedUserId
+ .flatMapLatest { currUserId ->
+ conflatedCallbackFlow {
+ val callback =
+ object : LockPatternUtils.StrongAuthTracker(context, looper) {
+ override fun onStrongAuthRequiredChanged(userId: Int) {
+ if (currUserId != userId) {
+ return
+ }
+
+ trySendWithFailureLogging(
+ isBiometricAllowedForUser(true, currUserId),
+ TAG
+ )
+ }
+
+ override fun onIsNonStrongBiometricAllowedChanged(userId: Int) {
+ // no-op
+ }
+ }
+ lockPatternUtils.registerStrongAuthTracker(callback)
+ awaitClose { lockPatternUtils.unregisterStrongAuthTracker(callback) }
+ }
+ }
+ .stateIn(
+ scope,
+ started = SharingStarted.Eagerly,
+ initialValue =
+ lockPatternUtils.isBiometricAllowedForUser(
+ userRepository.getSelectedUserInfo().id
+ )
+ )
+
+ override val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
+ selectedUserId
+ .flatMapLatest { userId ->
+ broadcastDispatcher
+ .broadcastFlow(
+ filter = IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ user = UserHandle.ALL
+ )
+ .transformLatest {
+ emit(
+ (devicePolicyManager.getKeyguardDisabledFeatures(null, userId) and
+ DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) == 0
+ )
+ }
+ .flowOn(backgroundDispatcher)
+ .distinctUntilChanged()
+ }
+ .stateIn(
+ scope,
+ started = SharingStarted.Eagerly,
+ initialValue =
+ devicePolicyManager.getKeyguardDisabledFeatures(
+ null,
+ userRepository.getSelectedUserInfo().id
+ ) and DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT == 0
+ )
+
+ companion object {
+ private const val TAG = "BiometricsRepositoryImpl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricType.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricType.kt
new file mode 100644
index 0000000..93c9781
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricType.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+enum class BiometricType(val isFingerprint: Boolean) {
+ // An unsupported biometric type
+ UNKNOWN(false),
+
+ // Fingerprint sensor that is located on the back (opposite side of the display) of the device
+ REAR_FINGERPRINT(true),
+
+ // Fingerprint sensor that is located under the display
+ UNDER_DISPLAY_FINGERPRINT(true),
+
+ // Fingerprint sensor that is located on the side of the device, typically on the power button
+ SIDE_FINGERPRINT(true),
+ FACE(false),
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 90f3c7d..2e34e9a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -26,9 +26,11 @@
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
@@ -44,6 +46,7 @@
@Inject
constructor(
private val viewMediatorCallback: ViewMediatorCallback,
+ private val clock: SystemClock,
@Application private val applicationScope: CoroutineScope,
@BouncerLog private val buffer: TableLogBuffer,
) {
@@ -94,6 +97,14 @@
setUpLogging()
}
+ /** Values associated with the AlternateBouncer */
+ private val _isAlternateBouncerVisible = MutableStateFlow(false)
+ val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
+ var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
+ private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
+ val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
+ _isAlternateBouncerUIAvailable.asStateFlow()
+
fun setPrimaryScrimmed(isScrimmed: Boolean) {
_primaryBouncerScrimmed.value = isScrimmed
}
@@ -102,6 +113,19 @@
_primaryBouncerVisible.value = isVisible
}
+ fun setAlternateVisible(isVisible: Boolean) {
+ if (isVisible && !_isAlternateBouncerVisible.value) {
+ lastAlternateBouncerVisibleTime = clock.uptimeMillis()
+ } else if (!isVisible) {
+ lastAlternateBouncerVisibleTime = NOT_VISIBLE
+ }
+ _isAlternateBouncerVisible.value = isVisible
+ }
+
+ fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+ _isAlternateBouncerUIAvailable.value = isAvailable
+ }
+
fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
_primaryBouncerShow.value = keyguardBouncerModel
}
@@ -202,4 +226,8 @@
.logDiffsForTable(buffer, "", "ResourceUpdateRequests", false)
.launchIn(applicationScope)
}
+
+ companion object {
+ private const val NOT_VISIBLE = -1L
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index a4fd087..d99af90 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -40,6 +40,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
+import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
@@ -88,6 +89,9 @@
/** Observable for whether the bouncer is showing. */
val isBouncerShowing: Flow<Boolean>
+ /** Is the always-on display available to be used? */
+ val isAodAvailable: Flow<Boolean>
+
/**
* Observable for whether we are in doze state.
*
@@ -182,6 +186,7 @@
private val keyguardStateController: KeyguardStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dozeTransitionListener: DozeTransitionListener,
+ private val dozeParameters: DozeParameters,
private val authController: AuthController,
private val dreamOverlayCallbackController: DreamOverlayCallbackController,
) : KeyguardRepository {
@@ -220,6 +225,31 @@
}
.distinctUntilChanged()
+ override val isAodAvailable: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : DozeParameters.Callback {
+ override fun onAlwaysOnChange() {
+ trySendWithFailureLogging(
+ dozeParameters.getAlwaysOn(),
+ TAG,
+ "updated isAodAvailable"
+ )
+ }
+ }
+
+ dozeParameters.addCallback(callback)
+ // Adding the callback does not send an initial update.
+ trySendWithFailureLogging(
+ dozeParameters.getAlwaysOn(),
+ TAG,
+ "initial isAodAvailable"
+ )
+
+ awaitClose { dozeParameters.removeCallback(callback) }
+ }
+ .distinctUntilChanged()
+
override val isKeyguardOccluded: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 26f853f..4639597 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -30,4 +30,6 @@
@Binds
fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository
+
+ @Binds fun biometricRepository(impl: BiometricRepositoryImpl): BiometricRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 343c2dc..d14b66a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -135,11 +135,14 @@
Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
return null
}
- if (lastStep.transitionState != TransitionState.FINISHED) {
- Log.i(TAG, "Transition still active: $lastStep, canceling")
- }
+ val startingValue =
+ if (lastStep.transitionState != TransitionState.FINISHED) {
+ Log.i(TAG, "Transition still active: $lastStep, canceling")
+ lastStep.value
+ } else {
+ 0f
+ }
- val startingValue = 1f - lastStep.value
lastAnimator?.cancel()
lastAnimator = info.animator
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
new file mode 100644
index 0000000..28c0b28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.BiometricRepository
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */
+@SysUISingleton
+class AlternateBouncerInteractor
+@Inject
+constructor(
+ private val bouncerRepository: KeyguardBouncerRepository,
+ private val biometricRepository: BiometricRepository,
+ private val systemClock: SystemClock,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ featureFlags: FeatureFlags,
+) {
+ val isModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)
+ var legacyAlternateBouncer: LegacyAlternateBouncer? = null
+ var legacyAlternateBouncerVisibleTime: Long = NOT_VISIBLE
+
+ val isVisible: Flow<Boolean> = bouncerRepository.isAlternateBouncerVisible
+
+ /**
+ * Sets the correct bouncer states to show the alternate bouncer if it can show.
+ * @return whether alternateBouncer is visible
+ */
+ fun show(): Boolean {
+ return when {
+ isModernAlternateBouncerEnabled -> {
+ bouncerRepository.setAlternateVisible(canShowAlternateBouncerForFingerprint())
+ isVisibleState()
+ }
+ canShowAlternateBouncerForFingerprint() -> {
+ if (legacyAlternateBouncer?.showAlternateBouncer() == true) {
+ legacyAlternateBouncerVisibleTime = systemClock.uptimeMillis()
+ true
+ } else {
+ false
+ }
+ }
+ else -> false
+ }
+ }
+
+ /**
+ * Sets the correct bouncer states to hide the bouncer. Should only be called through
+ * StatusBarKeyguardViewManager until ScrimController is refactored to use
+ * alternateBouncerInteractor.
+ * @return true if the alternate bouncer was newly hidden, else false.
+ */
+ fun hide(): Boolean {
+ return if (isModernAlternateBouncerEnabled) {
+ val wasAlternateBouncerVisible = isVisibleState()
+ bouncerRepository.setAlternateVisible(false)
+ wasAlternateBouncerVisible && !isVisibleState()
+ } else {
+ legacyAlternateBouncer?.hideAlternateBouncer() ?: false
+ }
+ }
+
+ fun isVisibleState(): Boolean {
+ return if (isModernAlternateBouncerEnabled) {
+ bouncerRepository.isAlternateBouncerVisible.value
+ } else {
+ legacyAlternateBouncer?.isShowingAlternateBouncer ?: false
+ }
+ }
+
+ fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+ bouncerRepository.setAlternateBouncerUIAvailable(isAvailable)
+ }
+
+ fun canShowAlternateBouncerForFingerprint(): Boolean {
+ return if (isModernAlternateBouncerEnabled) {
+ bouncerRepository.isAlternateBouncerUIAvailable.value &&
+ biometricRepository.isFingerprintEnrolled.value &&
+ biometricRepository.isStrongBiometricAllowed.value &&
+ biometricRepository.isFingerprintEnabledByDevicePolicy.value
+ } else {
+ legacyAlternateBouncer != null &&
+ keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true)
+ }
+ }
+
+ /**
+ * Whether the alt bouncer has shown for a minimum time before allowing touches to dismiss the
+ * alternate bouncer and show the primary bouncer.
+ */
+ fun hasAlternateBouncerShownWithMinTime(): Boolean {
+ return if (isModernAlternateBouncerEnabled) {
+ (systemClock.uptimeMillis() - bouncerRepository.lastAlternateBouncerVisibleTime) >
+ MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS
+ } else {
+ systemClock.uptimeMillis() - legacyAlternateBouncerVisibleTime > 200
+ }
+ }
+
+ companion object {
+ private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L
+ private const val NOT_VISIBLE = -1L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index fd2d271..ce61f2f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -21,9 +21,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration
@@ -48,12 +48,11 @@
private fun listenForDozingToLockscreen() {
scope.launch {
- keyguardInteractor.dozeTransitionModel
+ keyguardInteractor.wakefulnessModel
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
- .collect { pair ->
- val (dozeTransitionModel, lastStartedTransition) = pair
+ .collect { (wakefulnessModel, lastStartedTransition) ->
if (
- isDozeOff(dozeTransitionModel.to) &&
+ isWakingOrStartingToWake(wakefulnessModel) &&
lastStartedTransition.to == KeyguardState.DOZING
) {
keyguardTransitionRepository.startTransition(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 553fafe..9203a9b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -26,7 +26,10 @@
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@SysUISingleton
@@ -40,7 +43,7 @@
) : TransitionInteractor(FromGoneTransitionInteractor::class.simpleName!!) {
override fun start() {
- listenForGoneToAod()
+ listenForGoneToAodOrDozing()
listenForGoneToDreaming()
}
@@ -56,7 +59,7 @@
name,
KeyguardState.GONE,
KeyguardState.DREAMING,
- getAnimator(),
+ getAnimator(TO_DREAMING_DURATION),
)
)
}
@@ -64,12 +67,18 @@
}
}
- private fun listenForGoneToAod() {
+ private fun listenForGoneToAodOrDozing() {
scope.launch {
keyguardInteractor.wakefulnessModel
- .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
- .collect { pair ->
- val (wakefulnessState, keyguardState) = pair
+ .sample(
+ combine(
+ keyguardTransitionInteractor.finishedKeyguardState,
+ keyguardInteractor.isAodAvailable,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (wakefulnessState, keyguardState, isAodAvailable) ->
if (
keyguardState == KeyguardState.GONE &&
wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
@@ -78,7 +87,11 @@
TransitionInfo(
name,
KeyguardState.GONE,
- KeyguardState.AOD,
+ if (isAodAvailable) {
+ KeyguardState.AOD
+ } else {
+ KeyguardState.DOZING
+ },
getAnimator(),
)
)
@@ -87,14 +100,15 @@
}
}
- private fun getAnimator(): ValueAnimator {
+ private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
- setDuration(TRANSITION_DURATION_MS)
+ setDuration(duration.inWholeMilliseconds)
}
}
companion object {
- private const val TRANSITION_DURATION_MS = 500L
+ private val DEFAULT_DURATION = 500.milliseconds
+ val TO_DREAMING_DURATION = 933.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 326acc9..64028ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -21,15 +21,17 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
@@ -51,7 +53,8 @@
override fun start() {
listenForLockscreenToGone()
listenForLockscreenToOccluded()
- listenForLockscreenToAod()
+ listenForLockscreenToCamera()
+ listenForLockscreenToAodOrDozing()
listenForLockscreenToBouncer()
listenForLockscreenToDreaming()
listenForLockscreenToBouncerDragging()
@@ -69,7 +72,7 @@
name,
KeyguardState.LOCKSCREEN,
KeyguardState.DREAMING,
- getAnimator(),
+ getAnimator(TO_DREAMING_DURATION),
)
)
}
@@ -184,17 +187,14 @@
),
::toTriple
)
- .collect { triple ->
- val (isOccluded, keyguardState, isDreaming) = triple
- // Occlusion signals come from the framework, and should interrupt any
- // existing transition
- if (isOccluded && !isDreaming) {
+ .collect { (isOccluded, keyguardState, isDreaming) ->
+ if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
keyguardState,
KeyguardState.OCCLUDED,
- getAnimator(),
+ getAnimator(TO_OCCLUDED_DURATION),
)
)
}
@@ -202,19 +202,59 @@
}
}
- private fun listenForLockscreenToAod() {
+ /** This signal may come in before the occlusion signal, and can provide a custom transition */
+ private fun listenForLockscreenToCamera() {
scope.launch {
- keyguardInteractor
- .dozeTransitionTo(DozeStateModel.DOZE_AOD)
+ keyguardInteractor.onCameraLaunchDetected
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
- .collect { pair ->
- val (dozeToAod, lastStartedStep) = pair
- if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+ .collect { (_, lastStartedStep) ->
+ // DREAMING/AOD/OFF may trigger on the first power button push, so include this
+ // state in order to cancel and correct the transition
+ if (
+ lastStartedStep.to == KeyguardState.LOCKSCREEN ||
+ lastStartedStep.to == KeyguardState.DREAMING ||
+ lastStartedStep.to == KeyguardState.DOZING ||
+ lastStartedStep.to == KeyguardState.AOD ||
+ lastStartedStep.to == KeyguardState.OFF
+ ) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.LOCKSCREEN,
- KeyguardState.AOD,
+ KeyguardState.OCCLUDED,
+ getAnimator(TO_OCCLUDED_DURATION),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun listenForLockscreenToAodOrDozing() {
+ scope.launch {
+ keyguardInteractor.wakefulnessModel
+ .sample(
+ combine(
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ keyguardInteractor.isAodAvailable,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (wakefulnessState, lastStartedStep, isAodAvailable) ->
+ if (
+ lastStartedStep.to == KeyguardState.LOCKSCREEN &&
+ wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.LOCKSCREEN,
+ if (isAodAvailable) {
+ KeyguardState.AOD
+ } else {
+ KeyguardState.DOZING
+ },
getAnimator(),
)
)
@@ -223,14 +263,16 @@
}
}
- private fun getAnimator(): ValueAnimator {
+ private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
- setDuration(TRANSITION_DURATION_MS)
+ setDuration(duration.inWholeMilliseconds)
}
}
companion object {
- private const val TRANSITION_DURATION_MS = 500L
+ private val DEFAULT_DURATION = 500.milliseconds
+ val TO_DREAMING_DURATION = 933.milliseconds
+ val TO_OCCLUDED_DURATION = 450.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 8878901..2dc8fee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -23,12 +23,14 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@SysUISingleton
@@ -44,6 +46,7 @@
override fun start() {
listenForOccludedToLockscreen()
listenForOccludedToDreaming()
+ listenForOccludedToAodOrDozing()
}
private fun listenForOccludedToDreaming() {
@@ -70,8 +73,7 @@
scope.launch {
keyguardInteractor.isKeyguardOccluded
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
- .collect { pair ->
- val (isOccluded, lastStartedKeyguardState) = pair
+ .collect { (isOccluded, lastStartedKeyguardState) ->
// Occlusion signals come from the framework, and should interrupt any
// existing transition
if (!isOccluded && lastStartedKeyguardState.to == KeyguardState.OCCLUDED) {
@@ -88,6 +90,39 @@
}
}
+ private fun listenForOccludedToAodOrDozing() {
+ scope.launch {
+ keyguardInteractor.wakefulnessModel
+ .sample(
+ combine(
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ keyguardInteractor.isAodAvailable,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (wakefulnessState, lastStartedStep, isAodAvailable) ->
+ if (
+ lastStartedStep.to == KeyguardState.OCCLUDED &&
+ wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.OCCLUDED,
+ if (isAodAvailable) {
+ KeyguardState.AOD
+ } else {
+ KeyguardState.DOZING
+ },
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 402c179..490d22e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -17,17 +17,24 @@
package com.android.systemui.keyguard.domain.interactor
+import android.app.StatusBarManager
import android.graphics.Point
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.CommandQueue.Callbacks
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
@@ -41,6 +48,7 @@
@Inject
constructor(
private val repository: KeyguardRepository,
+ private val commandQueue: CommandQueue,
) {
/**
* The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
@@ -49,6 +57,8 @@
val dozeAmount: Flow<Float> = repository.linearDozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
+ /** Whether Always-on Display mode is available. */
+ val isAodAvailable: Flow<Boolean> = repository.isAodAvailable
/** Doze transition information. */
val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel
/**
@@ -58,6 +68,23 @@
val isDreaming: Flow<Boolean> = repository.isDreaming
/** Whether the system is dreaming with an overlay active */
val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
+ /** Event for when the camera gesture is detected */
+ val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> = conflatedCallbackFlow {
+ val callback =
+ object : CommandQueue.Callbacks {
+ override fun onCameraLaunchGestureDetected(source: Int) {
+ trySendWithFailureLogging(
+ cameraLaunchSourceIntToModel(source),
+ TAG,
+ "updated onCameraLaunchGestureDetected"
+ )
+ }
+ }
+
+ commandQueue.addCallback(callback)
+
+ awaitClose { commandQueue.removeCallback(callback) }
+ }
/**
* Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
@@ -103,4 +130,21 @@
fun isKeyguardShowing(): Boolean {
return repository.isKeyguardShowing()
}
+
+ private fun cameraLaunchSourceIntToModel(value: Int): CameraLaunchSourceModel {
+ return when (value) {
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE -> CameraLaunchSourceModel.WIGGLE
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP ->
+ CameraLaunchSourceModel.POWER_DOUBLE_TAP
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER ->
+ CameraLaunchSourceModel.LIFT_TRIGGER
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE ->
+ CameraLaunchSourceModel.QUICK_AFFORDANCE
+ else -> throw IllegalArgumentException("Invalid CameraLaunchSourceModel value: $value")
+ }
+ }
+
+ companion object {
+ private const val TAG = "KeyguardInteractor"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index a2661d7..d4e23499 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -19,11 +19,14 @@
import com.android.keyguard.logging.KeyguardLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
+private val TAG = KeyguardTransitionAuditLogger::class.simpleName!!
+
/** Collect flows of interest for auditing keyguard transitions. */
@SysUISingleton
class KeyguardTransitionAuditLogger
@@ -37,35 +40,47 @@
fun start() {
scope.launch {
- keyguardInteractor.wakefulnessModel.collect { logger.v("WakefulnessModel", it) }
+ keyguardInteractor.wakefulnessModel.collect {
+ logger.log(TAG, VERBOSE, "WakefulnessModel", it)
+ }
}
scope.launch {
- keyguardInteractor.isBouncerShowing.collect { logger.v("Bouncer showing", it) }
+ keyguardInteractor.isBouncerShowing.collect {
+ logger.log(TAG, VERBOSE, "Bouncer showing", it)
+ }
}
- scope.launch { keyguardInteractor.isDozing.collect { logger.v("isDozing", it) } }
+ scope.launch {
+ keyguardInteractor.isDozing.collect { logger.log(TAG, VERBOSE, "isDozing", it) }
+ }
- scope.launch { keyguardInteractor.isDreaming.collect { logger.v("isDreaming", it) } }
+ scope.launch {
+ keyguardInteractor.isDreaming.collect { logger.log(TAG, VERBOSE, "isDreaming", it) }
+ }
scope.launch {
interactor.finishedKeyguardTransitionStep.collect {
- logger.i("Finished transition", it)
+ logger.log(TAG, VERBOSE, "Finished transition", it)
}
}
scope.launch {
interactor.canceledKeyguardTransitionStep.collect {
- logger.i("Canceled transition", it)
+ logger.log(TAG, VERBOSE, "Canceled transition", it)
}
}
scope.launch {
- interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
+ interactor.startedKeyguardTransitionStep.collect {
+ logger.log(TAG, VERBOSE, "Started transition", it)
+ }
}
scope.launch {
- keyguardInteractor.dozeTransitionModel.collect { logger.i("Doze transition", it) }
+ keyguardInteractor.dozeTransitionModel.collect {
+ logger.log(TAG, VERBOSE, "Doze transition", it)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 04024be..9cdbcda 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -42,24 +42,32 @@
constructor(
repository: KeyguardTransitionRepository,
) {
+ /** (any)->AOD transition information */
+ val anyStateToAodTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == KeyguardState.AOD }
+
/** AOD->LOCKSCREEN transition information. */
val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
- /** LOCKSCREEN->AOD transition information. */
- val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
-
/** DREAMING->LOCKSCREEN transition information. */
val dreamingToLockscreenTransition: Flow<TransitionStep> =
repository.transition(DREAMING, LOCKSCREEN)
+ /** LOCKSCREEN->AOD transition information. */
+ val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
+
+ /** LOCKSCREEN->DREAMING transition information. */
+ val lockscreenToDreamingTransition: Flow<TransitionStep> =
+ repository.transition(LOCKSCREEN, DREAMING)
+
+ /** LOCKSCREEN->OCCLUDED transition information. */
+ val lockscreenToOccludedTransition: Flow<TransitionStep> =
+ repository.transition(LOCKSCREEN, OCCLUDED)
+
/** OCCLUDED->LOCKSCREEN transition information. */
val occludedToLockscreenTransition: Flow<TransitionStep> =
repository.transition(OCCLUDED, LOCKSCREEN)
- /** (any)->AOD transition information */
- val anyStateToAodTransition: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.to == KeyguardState.AOD }
-
/**
* AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
* Lockscreen (0f).
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt
new file mode 100644
index 0000000..19baf77
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** Camera launch sources */
+enum class CameraLaunchSourceModel {
+ /** Device is wiggled */
+ WIGGLE,
+ /** Power button has been double tapped */
+ POWER_DOUBLE_TAP,
+ /** Device has been lifted */
+ LIFT_TRIGGER,
+ /** Quick affordance button has been pressed */
+ QUICK_AFFORDANCE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 0e4058b..9d8bf7d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -45,7 +45,6 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.util.kotlin.pairwise
import kotlin.math.pow
import kotlin.math.sqrt
import kotlin.time.Duration.Companion.milliseconds
@@ -129,18 +128,6 @@
}
launch {
- viewModel.startButton
- .map { it.isActivated }
- .pairwise()
- .collect { (prev, next) ->
- when {
- !prev && next -> vibratorHelper?.vibrate(Vibrations.Activated)
- prev && !next -> vibratorHelper?.vibrate(Vibrations.Deactivated)
- }
- }
- }
-
- launch {
viewModel.endButton.collect { buttonModel ->
updateButton(
view = endButton,
@@ -153,18 +140,6 @@
}
launch {
- viewModel.endButton
- .map { it.isActivated }
- .pairwise()
- .collect { (prev, next) ->
- when {
- !prev && next -> vibratorHelper?.vibrate(Vibrations.Activated)
- prev && !next -> vibratorHelper?.vibrate(Vibrations.Deactivated)
- }
- }
- }
-
- launch {
viewModel.isOverlayContainerVisible.collect { isVisible ->
overlayContainer.visibility =
if (isVisible) {
@@ -383,6 +358,13 @@
.setDuration(longPressDurationMs)
.withEndAction {
view.setOnClickListener {
+ vibratorHelper?.vibrate(
+ if (viewModel.isActivated) {
+ Vibrations.Activated
+ } else {
+ Vibrations.Deactivated
+ }
+ )
viewModel.onClicked(
KeyguardQuickAffordanceViewModel.OnClickedParameters(
configKey = viewModel.configKey,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
new file mode 100644
index 0000000..d48f87d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.AnimationParams
+import com.android.systemui.keyguard.shared.model.TransitionState
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/**
+ * Breaks down LOCKSCREEN->DREAMING transition into discrete steps for corresponding views to
+ * consume.
+ */
+@SysUISingleton
+class LockscreenToDreamingTransitionViewModel
+@Inject
+constructor(
+ private val interactor: KeyguardTransitionInteractor,
+) {
+
+ /** Lockscreen views y-translation */
+ fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
+ return merge(
+ flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
+ (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
+ },
+ // On end, reset the translation to 0
+ interactor.lockscreenToDreamingTransition
+ .filter { step -> step.transitionState == TransitionState.FINISHED }
+ .map { 0f }
+ )
+ }
+
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
+
+ private fun flowForAnimation(params: AnimationParams): Flow<Float> {
+ return interactor.transitionStepAnimation(
+ interactor.lockscreenToDreamingTransition,
+ params,
+ totalDuration = TO_DREAMING_DURATION
+ )
+ }
+
+ companion object {
+ @JvmField val DREAMING_ANIMATION_DURATION_MS = TO_DREAMING_DURATION.inWholeMilliseconds
+
+ val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds)
+ val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
new file mode 100644
index 0000000..22d292e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.AnimationParams
+import com.android.systemui.keyguard.shared.model.TransitionState
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/**
+ * Breaks down LOCKSCREEN->OCCLUDED transition into discrete steps for corresponding views to
+ * consume.
+ */
+@SysUISingleton
+class LockscreenToOccludedTransitionViewModel
+@Inject
+constructor(
+ private val interactor: KeyguardTransitionInteractor,
+) {
+
+ /** Lockscreen views y-translation */
+ fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
+ return merge(
+ flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
+ (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
+ },
+ // On end, reset the translation to 0
+ interactor.lockscreenToOccludedTransition
+ .filter { step -> step.transitionState == TransitionState.FINISHED }
+ .map { 0f }
+ )
+ }
+
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
+
+ private fun flowForAnimation(params: AnimationParams): Flow<Float> {
+ return interactor.transitionStepAnimation(
+ interactor.lockscreenToOccludedTransition,
+ params,
+ totalDuration = TO_OCCLUDED_DURATION
+ )
+ }
+
+ companion object {
+ val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_OCCLUDED_DURATION)
+ val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt
index 0645236..9f563fe4 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt
@@ -23,3 +23,15 @@
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class KeyguardClockLog
+
+/** A [com.android.systemui.plugins.log.LogBuffer] for small keyguard clock logs. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class KeyguardSmallClockLog
+
+/** A [com.android.systemui.plugins.log.LogBuffer] for large keyguard clock logs. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class KeyguardLargeClockLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index bc29858..d7817e1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -335,13 +335,33 @@
}
/**
- * Provides a {@link LogBuffer} for keyguard clock logs.
+ * Provides a {@link LogBuffer} for general keyguard clock logs.
*/
@Provides
@SysUISingleton
@KeyguardClockLog
public static LogBuffer provideKeyguardClockLog(LogBufferFactory factory) {
- return factory.create("KeyguardClockLog", 500);
+ return factory.create("KeyguardClockLog", 100);
+ }
+
+ /**
+ * Provides a {@link LogBuffer} for keyguard small clock logs.
+ */
+ @Provides
+ @SysUISingleton
+ @KeyguardSmallClockLog
+ public static LogBuffer provideKeyguardSmallClockLog(LogBufferFactory factory) {
+ return factory.create("KeyguardSmallClockLog", 100);
+ }
+
+ /**
+ * Provides a {@link LogBuffer} for keyguard large clock logs.
+ */
+ @Provides
+ @SysUISingleton
+ @KeyguardLargeClockLog
+ public static LogBuffer provideKeyguardLargeClockLog(LogBufferFactory factory) {
+ return factory.create("KeyguardLargeClockLog", 100);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
index 7a90a74..7ccc43c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
@@ -29,6 +29,18 @@
private val dumpManager: DumpManager,
private val systemClock: SystemClock,
) {
+ private val existingBuffers = mutableMapOf<String, TableLogBuffer>()
+
+ /**
+ * Creates a new [TableLogBuffer]. This method should only be called from static contexts, where
+ * it is guaranteed only to be created one time. See [getOrCreate] for a cache-aware method of
+ * obtaining a buffer.
+ *
+ * @param name a unique table name
+ * @param maxSize the buffer max size. See [adjustMaxSize]
+ *
+ * @return a new [TableLogBuffer] registered with [DumpManager]
+ */
fun create(
name: String,
maxSize: Int,
@@ -37,4 +49,23 @@
dumpManager.registerNormalDumpable(name, tableBuffer)
return tableBuffer
}
+
+ /**
+ * Log buffers are retained indefinitely by [DumpManager], so that they can be represented in
+ * bugreports. Because of this, many of them are created statically in the Dagger graph.
+ *
+ * In the case where you have to create a logbuffer with a name only known at runtime, this
+ * method can be used to lazily create a table log buffer which is then cached for reuse.
+ *
+ * @return a [TableLogBuffer] suitable for reuse
+ */
+ fun getOrCreate(
+ name: String,
+ maxSize: Int,
+ ): TableLogBuffer =
+ existingBuffers.getOrElse(name) {
+ val buffer = create(name, maxSize)
+ existingBuffers[name] = buffer
+ buffer
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index d5558b2..e7f7647 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -94,7 +94,7 @@
private var currentCarouselWidth: Int = 0
/** The current height of the carousel */
- private var currentCarouselHeight: Int = 0
+ @VisibleForTesting var currentCarouselHeight: Int = 0
/** Are we currently showing only active players */
private var currentlyShowingOnlyActive: Boolean = false
@@ -128,14 +128,14 @@
/** The measured height of the carousel */
private var carouselMeasureHeight: Int = 0
private var desiredHostState: MediaHostState? = null
- private val mediaCarousel: MediaScrollView
+ @VisibleForTesting var mediaCarousel: MediaScrollView
val mediaCarouselScrollHandler: MediaCarouselScrollHandler
val mediaFrame: ViewGroup
@VisibleForTesting
lateinit var settingsButton: View
private set
private val mediaContent: ViewGroup
- @VisibleForTesting val pageIndicator: PageIndicator
+ @VisibleForTesting var pageIndicator: PageIndicator
private val visualStabilityCallback: OnReorderingAllowedListener
private var needsReordering: Boolean = false
private var keysNeedRemoval = mutableSetOf<String>()
@@ -160,25 +160,20 @@
}
companion object {
- const val ANIMATION_BASE_DURATION = 2200f
- const val DURATION = 167f
- const val DETAILS_DELAY = 1067f
- const val CONTROLS_DELAY = 1400f
- const val PAGINATION_DELAY = 1900f
- const val MEDIATITLES_DELAY = 1000f
- const val MEDIACONTAINERS_DELAY = 967f
val TRANSFORM_BEZIER = PathInterpolator(0.68F, 0F, 0F, 1F)
- val REVERSE_BEZIER = PathInterpolator(0F, 0.68F, 1F, 0F)
- fun calculateAlpha(squishinessFraction: Float, delay: Float, duration: Float): Float {
- val transformStartFraction = delay / ANIMATION_BASE_DURATION
- val transformDurationFraction = duration / ANIMATION_BASE_DURATION
- val squishinessToTime = REVERSE_BEZIER.getInterpolation(squishinessFraction)
- return MathUtils.constrain(
- (squishinessToTime - transformStartFraction) / transformDurationFraction,
- 0F,
- 1F
- )
+ fun calculateAlpha(
+ squishinessFraction: Float,
+ startPosition: Float,
+ endPosition: Float
+ ): Float {
+ val transformFraction =
+ MathUtils.constrain(
+ (squishinessFraction - startPosition) / (endPosition - startPosition),
+ 0F,
+ 1F
+ )
+ return TRANSFORM_BEZIER.getInterpolation(transformFraction)
}
}
@@ -813,7 +808,12 @@
val squishFraction = hostStates[currentEndLocation]?.squishFraction ?: 1.0F
val endAlpha =
(if (endIsVisible) 1.0f else 0.0f) *
- calculateAlpha(squishFraction, PAGINATION_DELAY, DURATION)
+ calculateAlpha(
+ squishFraction,
+ (pageIndicator.translationY + pageIndicator.height) /
+ mediaCarousel.measuredHeight,
+ 1F
+ )
var alpha = 1.0f
if (!endIsVisible || !startIsVisible) {
var progress = currentTransitionProgress
@@ -839,7 +839,8 @@
pageIndicator.translationX = translationX + mediaCarouselScrollHandler.contentTranslation
val layoutParams = pageIndicator.layoutParams as ViewGroup.MarginLayoutParams
pageIndicator.translationY =
- (currentCarouselHeight - pageIndicator.height - layoutParams.bottomMargin).toFloat()
+ (mediaCarousel.measuredHeight - pageIndicator.height - layoutParams.bottomMargin)
+ .toFloat()
}
/** Update the dimension of this carousel. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index f7a9bc7..66f12d6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -41,6 +41,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeStateEvents
@@ -93,6 +94,7 @@
private val keyguardStateController: KeyguardStateController,
private val bypassController: KeyguardBypassController,
private val mediaCarouselController: MediaCarouselController,
+ private val mediaManager: MediaDataManager,
private val keyguardViewController: KeyguardViewController,
private val dreamOverlayStateController: DreamOverlayStateController,
configurationController: ConfigurationController,
@@ -224,9 +226,9 @@
private var inSplitShade = false
- /** Is there any active media in the carousel? */
- private var hasActiveMedia: Boolean = false
- get() = mediaHosts.get(LOCATION_QQS)?.visible == true
+ /** Is there any active media or recommendation in the carousel? */
+ private var hasActiveMediaOrRecommendation: Boolean = false
+ get() = mediaManager.hasActiveMediaOrRecommendation()
/** Are we currently waiting on an animation to start? */
private var animationPending: Boolean = false
@@ -582,12 +584,8 @@
val viewHost = createUniqueObjectHost()
mediaObject.hostView = viewHost
mediaObject.addVisibilityChangeListener {
- // If QQS changes visibility, we need to force an update to ensure the transition
- // goes into the correct state
- val stateUpdate = mediaObject.location == LOCATION_QQS
-
// Never animate because of a visibility change, only state changes should do that
- updateDesiredLocation(forceNoAnimation = true, forceStateUpdate = stateUpdate)
+ updateDesiredLocation(forceNoAnimation = true)
}
mediaHosts[mediaObject.location] = mediaObject
if (mediaObject.location == desiredLocation) {
@@ -908,7 +906,7 @@
fun isCurrentlyInGuidedTransformation(): Boolean {
return hasValidStartAndEndLocations() &&
getTransformationProgress() >= 0 &&
- areGuidedTransitionHostsVisible()
+ (areGuidedTransitionHostsVisible() || !hasActiveMediaOrRecommendation)
}
private fun hasValidStartAndEndLocations(): Boolean {
@@ -965,7 +963,7 @@
private fun getQSTransformationProgress(): Float {
val currentHost = getHost(desiredLocation)
val previousHost = getHost(previousLocation)
- if (hasActiveMedia && (currentHost?.location == LOCATION_QS && !inSplitShade)) {
+ if (currentHost?.location == LOCATION_QS && !inSplitShade) {
if (previousHost?.location == LOCATION_QQS) {
if (previousHost.visible || statusbarState != StatusBarState.KEYGUARD) {
return qsExpansion
@@ -1028,7 +1026,8 @@
private fun updateHostAttachment() =
traceSection("MediaHierarchyManager#updateHostAttachment") {
var newLocation = resolveLocationForFading()
- var canUseOverlay = !isCurrentlyFading()
+ // Don't use the overlay when fading or when we don't have active media
+ var canUseOverlay = !isCurrentlyFading() && hasActiveMediaOrRecommendation
if (isCrossFadeAnimatorRunning) {
if (
getHost(newLocation)?.visible == true &&
@@ -1122,7 +1121,6 @@
dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
(qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
- !hasActiveMedia -> LOCATION_QS
onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 3224213..71cc47a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -24,11 +24,6 @@
import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.CONTROLS_DELAY
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DETAILS_DELAY
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIATITLES_DELAY
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.calculateAlpha
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.animation.MeasurementOutput
@@ -36,6 +31,8 @@
import com.android.systemui.util.animation.TransitionLayoutController
import com.android.systemui.util.animation.TransitionViewState
import com.android.systemui.util.traceSection
+import java.lang.Float.max
+import java.lang.Float.min
import javax.inject.Inject
/**
@@ -304,42 +301,109 @@
val squishedViewState = viewState.copy()
val squishedHeight = (squishedViewState.measureHeight * squishFraction).toInt()
squishedViewState.height = squishedHeight
- controlIds.forEach { id ->
- squishedViewState.widgetStates.get(id)?.let { state ->
- state.alpha = calculateAlpha(squishFraction, CONTROLS_DELAY, DURATION)
- }
- }
-
- detailIds.forEach { id ->
- squishedViewState.widgetStates.get(id)?.let { state ->
- state.alpha = calculateAlpha(squishFraction, DETAILS_DELAY, DURATION)
- }
- }
-
// We are not overriding the squishedViewStates height but only the children to avoid
// them remeasuring the whole view. Instead it just remains as the original size
backgroundIds.forEach { id ->
- squishedViewState.widgetStates.get(id)?.let { state ->
- state.height = squishedHeight
- }
+ squishedViewState.widgetStates.get(id)?.let { state -> state.height = squishedHeight }
}
- RecommendationViewHolder.mediaContainersIds.forEach { id ->
- squishedViewState.widgetStates.get(id)?.let { state ->
- state.alpha = calculateAlpha(squishFraction, MEDIACONTAINERS_DELAY, DURATION)
- }
- }
-
- RecommendationViewHolder.mediaTitlesAndSubtitlesIds.forEach { id ->
- squishedViewState.widgetStates.get(id)?.let { state ->
- state.alpha = calculateAlpha(squishFraction, MEDIATITLES_DELAY, DURATION)
- }
- }
-
+ // media player
+ val controlsTop =
+ calculateWidgetGroupAlphaForSquishiness(
+ controlIds,
+ squishedViewState.measureHeight.toFloat(),
+ squishedViewState,
+ squishFraction
+ )
+ calculateWidgetGroupAlphaForSquishiness(
+ detailIds,
+ controlsTop,
+ squishedViewState,
+ squishFraction
+ )
+ // recommendation card
+ val titlesTop =
+ calculateWidgetGroupAlphaForSquishiness(
+ RecommendationViewHolder.mediaTitlesAndSubtitlesIds,
+ squishedViewState.measureHeight.toFloat(),
+ squishedViewState,
+ squishFraction
+ )
+ calculateWidgetGroupAlphaForSquishiness(
+ RecommendationViewHolder.mediaContainersIds,
+ titlesTop,
+ squishedViewState,
+ squishFraction
+ )
return squishedViewState
}
/**
+ * This function is to make each widget in UMO disappear before being clipped by squished UMO
+ *
+ * The general rule is that widgets in UMO has been divided into several groups, and widgets in
+ * one group have the same alpha during squishing It will change from alpha 0.0 when the visible
+ * bottom of UMO reach the bottom of this group It will change to alpha 1.0 when the visible
+ * bottom of UMO reach the top of the group below e.g.Album title, artist title and play-pause
+ * button will change alpha together.
+ * ```
+ * And their alpha becomes 1.0 when the visible bottom of UMO reach the top of controls,
+ * including progress bar, next button, previous button
+ * ```
+ * widgetGroupIds: a group of widgets have same state during UMO is squished,
+ * ```
+ * e.g. Album title, artist title and play-pause button
+ * ```
+ * groupEndPosition: the height of UMO, when the height reaches this value,
+ * ```
+ * widgets in this group should have 1.0 as alpha
+ * e.g., the group of album title, artist title and play-pause button will become fully
+ * visible when the height of UMO reaches the top of controls group
+ * (progress bar, previous button and next button)
+ * ```
+ * squishedViewState: hold the widgetState of each widget, which will be modified
+ * squishFraction: the squishFraction of UMO
+ */
+ private fun calculateWidgetGroupAlphaForSquishiness(
+ widgetGroupIds: Set<Int>,
+ groupEndPosition: Float,
+ squishedViewState: TransitionViewState,
+ squishFraction: Float
+ ): Float {
+ val nonsquishedHeight = squishedViewState.measureHeight
+ var groupTop = squishedViewState.measureHeight.toFloat()
+ var groupBottom = 0F
+ widgetGroupIds.forEach { id ->
+ squishedViewState.widgetStates.get(id)?.let { state ->
+ groupTop = min(groupTop, state.y)
+ groupBottom = max(groupBottom, state.y + state.height)
+ }
+ }
+ // startPosition means to the height of squished UMO where the widget alpha should start
+ // changing from 0.0
+ // generally, it equals to the bottom of widgets, so that we can meet the requirement that
+ // widget should not go beyond the bounds of background
+ // endPosition means to the height of squished UMO where the widget alpha should finish
+ // changing alpha to 1.0
+ var startPosition = groupBottom
+ val endPosition = groupEndPosition
+ if (startPosition == endPosition) {
+ startPosition = (endPosition - 0.2 * (groupBottom - groupTop)).toFloat()
+ }
+ widgetGroupIds.forEach { id ->
+ squishedViewState.widgetStates.get(id)?.let { state ->
+ state.alpha =
+ calculateAlpha(
+ squishFraction,
+ startPosition / nonsquishedHeight,
+ endPosition / nonsquishedHeight
+ )
+ }
+ }
+ return groupTop // used for the widget group above this group
+ }
+
+ /**
* Obtain a new viewState for a given media state. This usually returns a cached state, but if
* it's not available, it will recreate one by measuring, which may be expensive.
*/
@@ -544,11 +608,13 @@
overrideSize?.let {
// To be safe we're using a maximum here. The override size should always be set
// properly though.
- if (result.measureHeight != it.measuredHeight
- || result.measureWidth != it.measuredWidth) {
+ if (
+ result.measureHeight != it.measuredHeight || result.measureWidth != it.measuredWidth
+ ) {
result.measureHeight = Math.max(it.measuredHeight, result.measureHeight)
result.measureWidth = Math.max(it.measuredWidth, result.measureWidth)
- // The measureHeight and the shown height should both be set to the overridden height
+ // The measureHeight and the shown height should both be set to the overridden
+ // height
result.height = result.measureHeight
result.width = result.measureWidth
// Make sure all background views are also resized such that their size is correct
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 316b642..7bc0c0c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -637,44 +637,21 @@
}
// For the first time building list, to make sure the top device is the connected
// device.
+ boolean needToHandleMutingExpectedDevice =
+ hasMutingExpectedDevice() && !isCurrentConnectedDeviceRemote();
+ final MediaDevice connectedMediaDevice =
+ needToHandleMutingExpectedDevice ? null
+ : getCurrentConnectedMediaDevice();
if (mMediaItemList.isEmpty()) {
- boolean needToHandleMutingExpectedDevice =
- hasMutingExpectedDevice() && !isCurrentConnectedDeviceRemote();
- final MediaDevice connectedMediaDevice =
- needToHandleMutingExpectedDevice ? null
- : getCurrentConnectedMediaDevice();
if (connectedMediaDevice == null) {
if (DEBUG) {
Log.d(TAG, "No connected media device or muting expected device exist.");
}
- if (needToHandleMutingExpectedDevice) {
- for (MediaDevice device : devices) {
- if (device.isMutingExpectedDevice()) {
- mMediaItemList.add(0, new MediaItem(device));
- mMediaItemList.add(1, new MediaItem(mContext.getString(
- R.string.media_output_group_title_speakers_and_displays),
- MediaItem.MediaItemType.TYPE_GROUP_DIVIDER));
- } else {
- mMediaItemList.add(new MediaItem(device));
- }
- }
- mMediaItemList.add(new MediaItem());
- } else {
- mMediaItemList.addAll(
- devices.stream().map(MediaItem::new).collect(Collectors.toList()));
- categorizeMediaItems(null);
- }
+ categorizeMediaItems(null, devices, needToHandleMutingExpectedDevice);
return;
}
// selected device exist
- for (MediaDevice device : devices) {
- if (TextUtils.equals(device.getId(), connectedMediaDevice.getId())) {
- mMediaItemList.add(0, new MediaItem(device));
- } else {
- mMediaItemList.add(new MediaItem(device));
- }
- }
- categorizeMediaItems(connectedMediaDevice);
+ categorizeMediaItems(connectedMediaDevice, devices, false);
return;
}
// To keep the same list order
@@ -708,31 +685,46 @@
}
}
- private void categorizeMediaItems(MediaDevice connectedMediaDevice) {
+ private void categorizeMediaItems(MediaDevice connectedMediaDevice, List<MediaDevice> devices,
+ boolean needToHandleMutingExpectedDevice) {
synchronized (mMediaDevicesLock) {
Set<String> selectedDevicesIds = getSelectedMediaDevice().stream().map(
MediaDevice::getId).collect(Collectors.toSet());
if (connectedMediaDevice != null) {
selectedDevicesIds.add(connectedMediaDevice.getId());
}
- int latestSelected = 1;
- for (MediaItem item : mMediaItemList) {
- if (item.getMediaDevice().isPresent()) {
- MediaDevice device = item.getMediaDevice().get();
- if (selectedDevicesIds.contains(device.getId())) {
- latestSelected = mMediaItemList.indexOf(item) + 1;
- } else {
- mMediaItemList.add(latestSelected, new MediaItem(mContext.getString(
- R.string.media_output_group_title_speakers_and_displays),
- MediaItem.MediaItemType.TYPE_GROUP_DIVIDER));
- break;
+ boolean suggestedDeviceAdded = false;
+ boolean displayGroupAdded = false;
+ for (MediaDevice device : devices) {
+ if (needToHandleMutingExpectedDevice && device.isMutingExpectedDevice()) {
+ mMediaItemList.add(0, new MediaItem(device));
+ } else if (!needToHandleMutingExpectedDevice && selectedDevicesIds.contains(
+ device.getId())) {
+ mMediaItemList.add(0, new MediaItem(device));
+ } else {
+ if (device.isSuggestedDevice() && !suggestedDeviceAdded) {
+ attachGroupDivider(mContext.getString(
+ R.string.media_output_group_title_suggested_device));
+ suggestedDeviceAdded = true;
+ } else if (!device.isSuggestedDevice() && !displayGroupAdded) {
+ attachGroupDivider(mContext.getString(
+ R.string.media_output_group_title_speakers_and_displays));
+ displayGroupAdded = true;
}
+ mMediaItemList.add(new MediaItem(device));
}
}
mMediaItemList.add(new MediaItem());
}
}
+ private void attachGroupDivider(String title) {
+ synchronized (mMediaDevicesLock) {
+ mMediaItemList.add(
+ new MediaItem(title, MediaItem.MediaItemType.TYPE_GROUP_DIVIDER));
+ }
+ }
+
private void attachRangeInfo(List<MediaDevice> devices) {
for (MediaDevice mediaDevice : devices) {
if (mNearbyDeviceInfoMap.containsKey(mediaDevice.getId())) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index 9f44d98..935f38d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -150,7 +150,12 @@
logger: MediaTttLogger<ChipbarInfo>,
): ChipbarInfo {
val packageName = routeInfo.clientPackageName
- val otherDeviceName = routeInfo.name.toString()
+ val otherDeviceName =
+ if (routeInfo.name.isBlank()) {
+ context.getString(R.string.media_ttt_default_device_type)
+ } else {
+ routeInfo.name.toString()
+ }
return ChipbarInfo(
// Display the app's icon as the start icon
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 6dd60d0..8356440 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -57,7 +57,9 @@
* If the keyguard is locked, notes will open as a full screen experience. A locked device has
* no contextual information which let us use the whole screen space available.
*
- * If no in multi-window or the keyguard is unlocked, notes will open as a floating experience.
+ * If no in multi-window or the keyguard is unlocked, notes will open as a bubble OR it will be
+ * collapsed if the notes bubble is already opened.
+ *
* That will let users open other apps in full screen, and take contextual notes.
*/
fun showNoteTask(isInMultiWindowMode: Boolean = false) {
@@ -75,7 +77,7 @@
context.startActivity(intent)
} else {
// TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter.
- bubbles.showAppBubble(intent)
+ bubbles.showOrHideAppBubble(intent)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index f49ffb4..774cb34 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -68,6 +68,7 @@
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.util.LifecycleFragment;
+import com.android.systemui.util.Utils;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -683,7 +684,7 @@
} else {
mQsMediaHost.setSquishFraction(mSquishinessFraction);
}
-
+ updateMediaPositions();
}
private void setAlphaAnimationProgress(float progress) {
@@ -758,6 +759,22 @@
- mQSPanelController.getPaddingBottom());
}
+ private void updateMediaPositions() {
+ if (Utils.useQsMediaPlayer(getContext())) {
+ View hostView = mQsMediaHost.getHostView();
+ // Make sure the media appears a bit from the top to make it look nicer
+ if (mLastQSExpansion > 0 && !isKeyguardState() && !mQqsMediaHost.getVisible()
+ && !mQSPanelController.shouldUseHorizontalLayout() && !mInSplitShade) {
+ float interpolation = 1.0f - mLastQSExpansion;
+ interpolation = Interpolators.ACCELERATE.getInterpolation(interpolation);
+ float translationY = -hostView.getHeight() * 1.3f * interpolation;
+ hostView.setTranslationY(translationY);
+ } else {
+ hostView.setTranslationY(0);
+ }
+ }
+ }
+
private boolean headerWillBeAnimating() {
return mStatusBarState == KEYGUARD && mShowCollapsedOnKeyguard && !isKeyguardState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 7cf63f6..1da30ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -36,7 +36,6 @@
void removeCallback(Callback callback);
void removeTile(String tileSpec);
void removeTiles(Collection<String> specs);
- void unmarkTileAsAutoAdded(String tileSpec);
int indexOf(String tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 7bb672c..e85d0a3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -372,18 +372,18 @@
if (mUsingHorizontalLayout) {
// Only height remaining
parameters.getDisappearSize().set(0.0f, 0.4f);
- // Disappearing on the right side on the bottom
- parameters.getGonePivot().set(1.0f, 1.0f);
+ // Disappearing on the right side on the top
+ parameters.getGonePivot().set(1.0f, 0.0f);
// translating a bit horizontal
parameters.getContentTranslationFraction().set(0.25f, 1.0f);
parameters.setDisappearEnd(0.6f);
} else {
// Only width remaining
parameters.getDisappearSize().set(1.0f, 0.0f);
- // Disappearing on the bottom
- parameters.getGonePivot().set(0.0f, 1.0f);
+ // Disappearing on the top
+ parameters.getGonePivot().set(0.0f, 0.0f);
// translating a bit vertical
- parameters.getContentTranslationFraction().set(0.0f, 1.05f);
+ parameters.getContentTranslationFraction().set(0.0f, 1f);
parameters.setDisappearEnd(0.95f);
}
parameters.setFadeStartPosition(0.95f);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index cad296b..100853c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -427,11 +427,6 @@
mMainExecutor.execute(() -> changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs)));
}
- @Override
- public void unmarkTileAsAutoAdded(String spec) {
- if (mAutoTiles != null) mAutoTiles.unmarkTileAsAutoAdded(spec);
- }
-
/**
* Add a tile to the end
*
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 79fcc7d..1712490 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -24,6 +24,7 @@
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Toolbar;
@@ -74,8 +75,8 @@
toolbar.setNavigationIcon(
getResources().getDrawable(value.resourceId, mContext.getTheme()));
- toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0,
- mContext.getString(com.android.internal.R.string.reset));
+ toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
toolbar.setTitle(R.string.qs_edit);
mRecyclerView = findViewById(android.R.id.list);
mTransparentView = findViewById(R.id.customizer_transparent_view);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
index 30f8124..1921586 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -219,9 +219,9 @@
// Small button with the number only.
foregroundServicesWithTextView.isVisible = false
- foregroundServicesWithNumberView.visibility = View.VISIBLE
+ foregroundServicesWithNumberView.isVisible = true
foregroundServicesWithNumberView.setOnClickListener {
- foregroundServices.onClick(Expandable.fromView(foregroundServicesWithTextView))
+ foregroundServices.onClick(Expandable.fromView(foregroundServicesWithNumberView))
}
foregroundServicesWithNumberHolder.number.text = foregroundServicesCount.toString()
foregroundServicesWithNumberHolder.number.contentDescription = foregroundServices.text
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 9f376ae..d32ef32 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -49,109 +49,135 @@
}
fun logTileAdded(tileSpec: String) {
- log(DEBUG, {
- str1 = tileSpec
- }, {
- "[$str1] Tile added"
- })
+ buffer.log(TAG, DEBUG, { str1 = tileSpec }, { "[$str1] Tile added" })
}
fun logTileDestroyed(tileSpec: String, reason: String) {
- log(DEBUG, {
- str1 = tileSpec
- str2 = reason
- }, {
- "[$str1] Tile destroyed. Reason: $str2"
- })
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = tileSpec
+ str2 = reason
+ },
+ { "[$str1] Tile destroyed. Reason: $str2" }
+ )
}
fun logTileChangeListening(tileSpec: String, listening: Boolean) {
- log(VERBOSE, {
- bool1 = listening
- str1 = tileSpec
- }, {
- "[$str1] Tile listening=$bool1"
- })
+ buffer.log(
+ TAG,
+ VERBOSE,
+ {
+ bool1 = listening
+ str1 = tileSpec
+ },
+ { "[$str1] Tile listening=$bool1" }
+ )
}
fun logAllTilesChangeListening(listening: Boolean, containerName: String, allSpecs: String) {
- log(DEBUG, {
- bool1 = listening
- str1 = containerName
- str2 = allSpecs
- }, {
- "Tiles listening=$bool1 in $str1. $str2"
- })
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = listening
+ str1 = containerName
+ str2 = allSpecs
+ },
+ { "Tiles listening=$bool1 in $str1. $str2" }
+ )
}
fun logTileClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) {
- log(DEBUG, {
- str1 = tileSpec
- int1 = eventId
- str2 = StatusBarState.toString(statusBarState)
- str3 = toStateString(state)
- }, {
- "[$str1][$int1] Tile clicked. StatusBarState=$str2. TileState=$str3"
- })
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = tileSpec
+ int1 = eventId
+ str2 = StatusBarState.toString(statusBarState)
+ str3 = toStateString(state)
+ },
+ { "[$str1][$int1] Tile clicked. StatusBarState=$str2. TileState=$str3" }
+ )
}
fun logHandleClick(tileSpec: String, eventId: Int) {
- log(DEBUG, {
- str1 = tileSpec
- int1 = eventId
- }, {
- "[$str1][$int1] Tile handling click."
- })
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = tileSpec
+ int1 = eventId
+ },
+ { "[$str1][$int1] Tile handling click." }
+ )
}
fun logTileSecondaryClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) {
- log(DEBUG, {
- str1 = tileSpec
- int1 = eventId
- str2 = StatusBarState.toString(statusBarState)
- str3 = toStateString(state)
- }, {
- "[$str1][$int1] Tile secondary clicked. StatusBarState=$str2. TileState=$str3"
- })
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = tileSpec
+ int1 = eventId
+ str2 = StatusBarState.toString(statusBarState)
+ str3 = toStateString(state)
+ },
+ { "[$str1][$int1] Tile secondary clicked. StatusBarState=$str2. TileState=$str3" }
+ )
}
fun logHandleSecondaryClick(tileSpec: String, eventId: Int) {
- log(DEBUG, {
- str1 = tileSpec
- int1 = eventId
- }, {
- "[$str1][$int1] Tile handling secondary click."
- })
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = tileSpec
+ int1 = eventId
+ },
+ { "[$str1][$int1] Tile handling secondary click." }
+ )
}
fun logTileLongClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) {
- log(DEBUG, {
- str1 = tileSpec
- int1 = eventId
- str2 = StatusBarState.toString(statusBarState)
- str3 = toStateString(state)
- }, {
- "[$str1][$int1] Tile long clicked. StatusBarState=$str2. TileState=$str3"
- })
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = tileSpec
+ int1 = eventId
+ str2 = StatusBarState.toString(statusBarState)
+ str3 = toStateString(state)
+ },
+ { "[$str1][$int1] Tile long clicked. StatusBarState=$str2. TileState=$str3" }
+ )
}
fun logHandleLongClick(tileSpec: String, eventId: Int) {
- log(DEBUG, {
- str1 = tileSpec
- int1 = eventId
- }, {
- "[$str1][$int1] Tile handling long click."
- })
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = tileSpec
+ int1 = eventId
+ },
+ { "[$str1][$int1] Tile handling long click." }
+ )
}
fun logInternetTileUpdate(tileSpec: String, lastType: Int, callback: String) {
- log(VERBOSE, {
- str1 = tileSpec
- int1 = lastType
- str2 = callback
- }, {
- "[$str1] mLastTileState=$int1, Callback=$str2."
- })
+ buffer.log(
+ TAG,
+ VERBOSE,
+ {
+ str1 = tileSpec
+ int1 = lastType
+ str2 = callback
+ },
+ { "[$str1] mLastTileState=$int1, Callback=$str2." }
+ )
}
// TODO(b/250618218): Remove this method once we know the root cause of b/250618218.
@@ -167,58 +193,75 @@
if (tileSpec != "internet") {
return
}
- log(VERBOSE, {
- str1 = tileSpec
- int1 = state
- bool1 = disabledByPolicy
- int2 = color
- }, {
- "[$str1] state=$int1, disabledByPolicy=$bool1, color=$int2."
- })
+ buffer.log(
+ TAG,
+ VERBOSE,
+ {
+ str1 = tileSpec
+ int1 = state
+ bool1 = disabledByPolicy
+ int2 = color
+ },
+ { "[$str1] state=$int1, disabledByPolicy=$bool1, color=$int2." }
+ )
}
fun logTileUpdated(tileSpec: String, state: QSTile.State) {
- log(VERBOSE, {
- str1 = tileSpec
- str2 = state.label?.toString()
- str3 = state.icon?.toString()
- int1 = state.state
- if (state is QSTile.SignalState) {
- bool1 = true
- bool2 = state.activityIn
- bool3 = state.activityOut
+ buffer.log(
+ TAG,
+ VERBOSE,
+ {
+ str1 = tileSpec
+ str2 = state.label?.toString()
+ str3 = state.icon?.toString()
+ int1 = state.state
+ if (state is QSTile.SignalState) {
+ bool1 = true
+ bool2 = state.activityIn
+ bool3 = state.activityOut
+ }
+ },
+ {
+ "[$str1] Tile updated. Label=$str2. State=$int1. Icon=$str3." +
+ if (bool1) " Activity in/out=$bool2/$bool3" else ""
}
- }, {
- "[$str1] Tile updated. Label=$str2. State=$int1. Icon=$str3." +
- if (bool1) " Activity in/out=$bool2/$bool3" else ""
- })
+ )
}
fun logPanelExpanded(expanded: Boolean, containerName: String) {
- log(DEBUG, {
- str1 = containerName
- bool1 = expanded
- }, {
- "$str1 expanded=$bool1"
- })
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = containerName
+ bool1 = expanded
+ },
+ { "$str1 expanded=$bool1" }
+ )
}
fun logOnViewAttached(orientation: Int, containerName: String) {
- log(DEBUG, {
- str1 = containerName
- int1 = orientation
- }, {
- "onViewAttached: $str1 orientation $int1"
- })
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = containerName
+ int1 = orientation
+ },
+ { "onViewAttached: $str1 orientation $int1" }
+ )
}
fun logOnViewDetached(orientation: Int, containerName: String) {
- log(DEBUG, {
- str1 = containerName
- int1 = orientation
- }, {
- "onViewDetached: $str1 orientation $int1"
- })
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = containerName
+ int1 = orientation
+ },
+ { "onViewDetached: $str1 orientation $int1" }
+ )
}
fun logOnConfigurationChanged(
@@ -226,13 +269,16 @@
newOrientation: Int,
containerName: String
) {
- log(DEBUG, {
- str1 = containerName
- int1 = lastOrientation
- int2 = newOrientation
- }, {
- "configuration change: $str1 orientation was $int1, now $int2"
- })
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = containerName
+ int1 = lastOrientation
+ int2 = newOrientation
+ },
+ { "configuration change: $str1 orientation was $int1, now $int2" }
+ )
}
fun logSwitchTileLayout(
@@ -241,32 +287,41 @@
force: Boolean,
containerName: String
) {
- log(DEBUG, {
- str1 = containerName
- bool1 = after
- bool2 = before
- bool3 = force
- }, {
- "change tile layout: $str1 horizontal=$bool1 (was $bool2), force? $bool3"
- })
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = containerName
+ bool1 = after
+ bool2 = before
+ bool3 = force
+ },
+ { "change tile layout: $str1 horizontal=$bool1 (was $bool2), force? $bool3" }
+ )
}
fun logTileDistributionInProgress(tilesPerPageCount: Int, totalTilesCount: Int) {
- log(DEBUG, {
- int1 = tilesPerPageCount
- int2 = totalTilesCount
- }, {
- "Distributing tiles: [tilesPerPageCount=$int1] [totalTilesCount=$int2]"
- })
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = tilesPerPageCount
+ int2 = totalTilesCount
+ },
+ { "Distributing tiles: [tilesPerPageCount=$int1] [totalTilesCount=$int2]" }
+ )
}
fun logTileDistributed(tileName: String, pageIndex: Int) {
- log(DEBUG, {
- str1 = tileName
- int1 = pageIndex
- }, {
- "Adding $str1 to page number $int1"
- })
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = tileName
+ int1 = pageIndex
+ },
+ { "Adding $str1 to page number $int1" }
+ )
}
private fun toStateString(state: Int): String {
@@ -277,12 +332,4 @@
else -> "wrong state"
}
}
-
- private inline fun log(
- logLevel: LogLevel,
- initializer: LogMessage.() -> Unit,
- noinline printer: LogMessage.() -> String
- ) {
- buffer.log(TAG, logLevel, initializer, printer)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index a92c7e3..24a4f60b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -33,7 +33,6 @@
import com.android.systemui.qs.tiles.BluetoothTile;
import com.android.systemui.qs.tiles.CameraToggleTile;
import com.android.systemui.qs.tiles.CastTile;
-import com.android.systemui.qs.tiles.CellularTile;
import com.android.systemui.qs.tiles.ColorCorrectionTile;
import com.android.systemui.qs.tiles.ColorInversionTile;
import com.android.systemui.qs.tiles.DataSaverTile;
@@ -54,7 +53,6 @@
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.qs.tiles.ScreenRecordTile;
import com.android.systemui.qs.tiles.UiModeNightTile;
-import com.android.systemui.qs.tiles.WifiTile;
import com.android.systemui.qs.tiles.WorkModeTile;
import com.android.systemui.util.leak.GarbageMonitor;
@@ -68,10 +66,8 @@
private static final String TAG = "QSFactory";
- private final Provider<WifiTile> mWifiTileProvider;
private final Provider<InternetTile> mInternetTileProvider;
private final Provider<BluetoothTile> mBluetoothTileProvider;
- private final Provider<CellularTile> mCellularTileProvider;
private final Provider<DndTile> mDndTileProvider;
private final Provider<ColorCorrectionTile> mColorCorrectionTileProvider;
private final Provider<ColorInversionTile> mColorInversionTileProvider;
@@ -106,10 +102,8 @@
public QSFactoryImpl(
Lazy<QSHost> qsHostLazy,
Provider<CustomTile.Builder> customTileBuilderProvider,
- Provider<WifiTile> wifiTileProvider,
Provider<InternetTile> internetTileProvider,
Provider<BluetoothTile> bluetoothTileProvider,
- Provider<CellularTile> cellularTileProvider,
Provider<DndTile> dndTileProvider,
Provider<ColorInversionTile> colorInversionTileProvider,
Provider<AirplaneModeTile> airplaneModeTileProvider,
@@ -139,10 +133,8 @@
mQsHostLazy = qsHostLazy;
mCustomTileBuilderProvider = customTileBuilderProvider;
- mWifiTileProvider = wifiTileProvider;
mInternetTileProvider = internetTileProvider;
mBluetoothTileProvider = bluetoothTileProvider;
- mCellularTileProvider = cellularTileProvider;
mDndTileProvider = dndTileProvider;
mColorInversionTileProvider = colorInversionTileProvider;
mAirplaneModeTileProvider = airplaneModeTileProvider;
@@ -186,14 +178,10 @@
protected QSTileImpl createTileInternal(String tileSpec) {
// Stock tiles.
switch (tileSpec) {
- case "wifi":
- return mWifiTileProvider.get();
case "internet":
return mInternetTileProvider.get();
case "bt":
return mBluetoothTileProvider.get();
- case "cell":
- return mCellularTileProvider.get();
case "dnd":
return mDndTileProvider.get();
case "inversion":
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
deleted file mode 100644
index 04a25fc..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Copyright (C) 2014 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;
-
-import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA;
-
-import android.annotation.NonNull;
-import android.app.AlertDialog;
-import android.app.AlertDialog.Builder;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.service.quicksettings.Tile;
-import android.telephony.SubscriptionManager;
-import android.text.Html;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.WindowManager.LayoutParams;
-import android.widget.Switch;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.net.DataUsageController;
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.QSIconView;
-import com.android.systemui.plugins.qs.QSTile.SignalState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.SignalTileView;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.connectivity.IconState;
-import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
-import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.SignalCallback;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import javax.inject.Inject;
-
-/** Quick settings tile: Cellular **/
-public class CellularTile extends QSTileImpl<SignalState> {
- private static final String ENABLE_SETTINGS_DATA_PLAN = "enable.settings.data.plan";
-
- private final NetworkController mController;
- private final DataUsageController mDataController;
- private final KeyguardStateController mKeyguard;
- private final CellSignalCallback mSignalCallback = new CellSignalCallback();
-
- @Inject
- public CellularTile(
- QSHost host,
- @Background Looper backgroundLooper,
- @Main Handler mainHandler,
- FalsingManager falsingManager,
- MetricsLogger metricsLogger,
- StatusBarStateController statusBarStateController,
- ActivityStarter activityStarter,
- QSLogger qsLogger,
- NetworkController networkController,
- KeyguardStateController keyguardStateController
-
- ) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
- statusBarStateController, activityStarter, qsLogger);
- mController = networkController;
- mKeyguard = keyguardStateController;
- mDataController = mController.getMobileDataController();
- mController.observe(getLifecycle(), mSignalCallback);
- }
-
- @Override
- public SignalState newTileState() {
- return new SignalState();
- }
-
- @Override
- public QSIconView createTileView(Context context) {
- return new SignalTileView(context);
- }
-
- @Override
- public Intent getLongClickIntent() {
- if (getState().state == Tile.STATE_UNAVAILABLE) {
- return new Intent(Settings.ACTION_WIRELESS_SETTINGS);
- }
- return getCellularSettingIntent();
- }
-
- @Override
- protected void handleClick(@Nullable View view) {
- if (getState().state == Tile.STATE_UNAVAILABLE) {
- return;
- }
- if (mDataController.isMobileDataEnabled()) {
- maybeShowDisableDialog();
- } else {
- mDataController.setMobileDataEnabled(true);
- }
- }
-
- private void maybeShowDisableDialog() {
- if (Prefs.getBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, false)) {
- // Directly turn off mobile data if the user has seen the dialog before.
- mDataController.setMobileDataEnabled(false);
- return;
- }
- String carrierName = mController.getMobileDataNetworkName();
- boolean isInService = mController.isMobileDataNetworkInService();
- if (TextUtils.isEmpty(carrierName) || !isInService) {
- carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier);
- }
- AlertDialog dialog = new Builder(mContext)
- .setTitle(R.string.mobile_data_disable_title)
- .setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName))
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(
- com.android.internal.R.string.alert_windows_notification_turn_off_action,
- (d, w) -> {
- mDataController.setMobileDataEnabled(false);
- Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true);
- })
- .create();
- dialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG);
- SystemUIDialog.setShowForAllUsers(dialog, true);
- SystemUIDialog.registerDismissListener(dialog);
- SystemUIDialog.setWindowOnTop(dialog, mKeyguard.isShowing());
- dialog.show();
- }
-
- @Override
- protected void handleSecondaryClick(@Nullable View view) {
- handleLongClick(view);
- }
-
- @Override
- public CharSequence getTileLabel() {
- return mContext.getString(R.string.quick_settings_cellular_detail_title);
- }
-
- @Override
- protected void handleUpdateState(SignalState state, Object arg) {
- CallbackInfo cb = (CallbackInfo) arg;
- if (cb == null) {
- cb = mSignalCallback.mInfo;
- }
-
- final Resources r = mContext.getResources();
- state.label = r.getString(R.string.mobile_data);
- boolean mobileDataEnabled = mDataController.isMobileDataSupported()
- && mDataController.isMobileDataEnabled();
- state.value = mobileDataEnabled;
- state.activityIn = mobileDataEnabled && cb.activityIn;
- state.activityOut = mobileDataEnabled && cb.activityOut;
- state.expandedAccessibilityClassName = Switch.class.getName();
- if (cb.noSim) {
- state.icon = ResourceIcon.get(R.drawable.ic_qs_no_sim);
- } else {
- state.icon = ResourceIcon.get(R.drawable.ic_swap_vert);
- }
-
- if (cb.noSim) {
- state.state = Tile.STATE_UNAVAILABLE;
- state.secondaryLabel = r.getString(R.string.keyguard_missing_sim_message_short);
- } else if (cb.airplaneModeEnabled) {
- state.state = Tile.STATE_UNAVAILABLE;
- state.secondaryLabel = r.getString(R.string.status_bar_airplane);
- } else if (mobileDataEnabled) {
- state.state = Tile.STATE_ACTIVE;
- state.secondaryLabel = appendMobileDataType(
- // Only show carrier name if there are more than 1 subscription
- cb.multipleSubs ? cb.dataSubscriptionName : "",
- getMobileDataContentName(cb));
- } else {
- state.state = Tile.STATE_INACTIVE;
- state.secondaryLabel = r.getString(R.string.cell_data_off);
- }
-
- state.contentDescription = state.label;
- if (state.state == Tile.STATE_INACTIVE) {
- // This information is appended later by converting the Tile.STATE_INACTIVE state.
- state.stateDescription = "";
- } else {
- state.stateDescription = state.secondaryLabel;
- }
- }
-
- private CharSequence appendMobileDataType(CharSequence current, CharSequence dataType) {
- if (TextUtils.isEmpty(dataType)) {
- return Html.fromHtml(current.toString(), 0);
- }
- if (TextUtils.isEmpty(current)) {
- return Html.fromHtml(dataType.toString(), 0);
- }
- String concat = mContext.getString(R.string.mobile_carrier_text_format, current, dataType);
- return Html.fromHtml(concat, 0);
- }
-
- private CharSequence getMobileDataContentName(CallbackInfo cb) {
- if (cb.roaming && !TextUtils.isEmpty(cb.dataContentDescription)) {
- String roaming = mContext.getString(R.string.data_connection_roaming);
- String dataDescription = cb.dataContentDescription.toString();
- return mContext.getString(R.string.mobile_data_text_format, roaming, dataDescription);
- }
- if (cb.roaming) {
- return mContext.getString(R.string.data_connection_roaming);
- }
- return cb.dataContentDescription;
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.QS_CELLULAR;
- }
-
- @Override
- public boolean isAvailable() {
- return mController.hasMobileDataFeature()
- && mHost.getUserContext().getUserId() == UserHandle.USER_SYSTEM;
- }
-
- private static final class CallbackInfo {
- boolean airplaneModeEnabled;
- @Nullable
- CharSequence dataSubscriptionName;
- @Nullable
- CharSequence dataContentDescription;
- boolean activityIn;
- boolean activityOut;
- boolean noSim;
- boolean roaming;
- boolean multipleSubs;
- }
-
- private final class CellSignalCallback implements SignalCallback {
- private final CallbackInfo mInfo = new CallbackInfo();
-
- @Override
- public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) {
- if (indicators.qsIcon == null) {
- // Not data sim, don't display.
- return;
- }
- mInfo.dataSubscriptionName = mController.getMobileDataNetworkName();
- mInfo.dataContentDescription = indicators.qsDescription != null
- ? indicators.typeContentDescriptionHtml : null;
- mInfo.activityIn = indicators.activityIn;
- mInfo.activityOut = indicators.activityOut;
- mInfo.roaming = indicators.roaming;
- mInfo.multipleSubs = mController.getNumberSubscriptions() > 1;
- refreshState(mInfo);
- }
-
- @Override
- public void setNoSims(boolean show, boolean simDetected) {
- mInfo.noSim = show;
- refreshState(mInfo);
- }
-
- @Override
- public void setIsAirplaneMode(@NonNull IconState icon) {
- mInfo.airplaneModeEnabled = icon.visible;
- refreshState(mInfo);
- }
- }
-
- static Intent getCellularSettingIntent() {
- Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS);
- int dataSub = SubscriptionManager.getDefaultDataSubscriptionId();
- if (dataSub != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- intent.putExtra(Settings.EXTRA_SUB_ID,
- SubscriptionManager.getDefaultDataSubscriptionId());
- }
- return intent;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 57a00c9..b6b657e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -204,15 +204,6 @@
Trace.endSection();
}
- @Override
- public void onUserListItemClicked(@NonNull UserRecord record,
- @Nullable UserSwitchDialogController.DialogShower dialogShower) {
- if (dialogShower != null) {
- mDialogShower.dismiss();
- }
- super.onUserListItemClicked(record, dialogShower);
- }
-
public void linkToViewGroup(ViewGroup viewGroup) {
PseudoGridView.ViewGroupAdapterBridge.link(viewGroup, this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
deleted file mode 100644
index b2be56cc..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2014 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;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
-import android.service.quicksettings.Tile;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-import android.widget.Switch;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.QSIconView;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.plugins.qs.QSTile.SignalState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.AlphaControlledSignalTileView;
-import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.tileimpl.QSIconViewImpl;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.connectivity.AccessPointController;
-import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.SignalCallback;
-import com.android.systemui.statusbar.connectivity.WifiIcons;
-import com.android.systemui.statusbar.connectivity.WifiIndicators;
-
-import javax.inject.Inject;
-
-/** Quick settings tile: Wifi **/
-public class WifiTile extends QSTileImpl<SignalState> {
- private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);
-
- protected final NetworkController mController;
- private final AccessPointController mWifiController;
- private final QSTile.SignalState mStateBeforeClick = newTileState();
-
- protected final WifiSignalCallback mSignalCallback = new WifiSignalCallback();
- private boolean mExpectDisabled;
-
- @Inject
- public WifiTile(
- QSHost host,
- @Background Looper backgroundLooper,
- @Main Handler mainHandler,
- FalsingManager falsingManager,
- MetricsLogger metricsLogger,
- StatusBarStateController statusBarStateController,
- ActivityStarter activityStarter,
- QSLogger qsLogger,
- NetworkController networkController,
- AccessPointController accessPointController
- ) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
- statusBarStateController, activityStarter, qsLogger);
- mController = networkController;
- mWifiController = accessPointController;
- mController.observe(getLifecycle(), mSignalCallback);
- mStateBeforeClick.spec = "wifi";
- }
-
- @Override
- public SignalState newTileState() {
- return new SignalState();
- }
-
- @Override
- public QSIconView createTileView(Context context) {
- return new AlphaControlledSignalTileView(context);
- }
-
- @Override
- public Intent getLongClickIntent() {
- return WIFI_SETTINGS;
- }
-
- @Override
- protected void handleClick(@Nullable View view) {
- // Secondary clicks are header clicks, just toggle.
- mState.copyTo(mStateBeforeClick);
- boolean wifiEnabled = mState.value;
- // Immediately enter transient state when turning on wifi.
- refreshState(wifiEnabled ? null : ARG_SHOW_TRANSIENT_ENABLING);
- mController.setWifiEnabled(!wifiEnabled);
- mExpectDisabled = wifiEnabled;
- if (mExpectDisabled) {
- mHandler.postDelayed(() -> {
- if (mExpectDisabled) {
- mExpectDisabled = false;
- refreshState();
- }
- }, QSIconViewImpl.QS_ANIM_LENGTH);
- }
- }
-
- @Override
- protected void handleSecondaryClick(@Nullable View view) {
- if (!mWifiController.canConfigWifi()) {
- mActivityStarter.postStartActivityDismissingKeyguard(
- new Intent(Settings.ACTION_WIFI_SETTINGS), 0);
- return;
- }
- if (!mState.value) {
- mController.setWifiEnabled(true);
- }
- }
-
- @Override
- public CharSequence getTileLabel() {
- return mContext.getString(R.string.quick_settings_wifi_label);
- }
-
- @Override
- protected void handleUpdateState(SignalState state, Object arg) {
- if (DEBUG) Log.d(TAG, "handleUpdateState arg=" + arg);
- final CallbackInfo cb = mSignalCallback.mInfo;
- if (mExpectDisabled) {
- if (cb.enabled) {
- return; // Ignore updates until disabled event occurs.
- } else {
- mExpectDisabled = false;
- }
- }
- boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING;
- boolean wifiConnected = cb.enabled && (cb.wifiSignalIconId > 0)
- && (cb.ssid != null || cb.wifiSignalIconId != WifiIcons.QS_WIFI_NO_NETWORK);
- boolean wifiNotConnected = (cb.ssid == null)
- && (cb.wifiSignalIconId == WifiIcons.QS_WIFI_NO_NETWORK);
- if (state.slash == null) {
- state.slash = new SlashState();
- state.slash.rotation = 6;
- }
- state.slash.isSlashed = false;
- boolean isTransient = transientEnabling || cb.isTransient;
- state.secondaryLabel = getSecondaryLabel(isTransient, cb.statusLabel);
- state.state = Tile.STATE_ACTIVE;
- state.dualTarget = true;
- state.value = transientEnabling || cb.enabled;
- state.activityIn = cb.enabled && cb.activityIn;
- state.activityOut = cb.enabled && cb.activityOut;
- final StringBuffer minimalContentDescription = new StringBuffer();
- final StringBuffer minimalStateDescription = new StringBuffer();
- final Resources r = mContext.getResources();
- if (isTransient) {
- state.icon = ResourceIcon.get(
- com.android.internal.R.drawable.ic_signal_wifi_transient_animation);
- state.label = r.getString(R.string.quick_settings_wifi_label);
- } else if (!state.value) {
- state.slash.isSlashed = true;
- state.state = Tile.STATE_INACTIVE;
- state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_DISABLED);
- state.label = r.getString(R.string.quick_settings_wifi_label);
- } else if (wifiConnected) {
- state.icon = ResourceIcon.get(cb.wifiSignalIconId);
- state.label = cb.ssid != null ? removeDoubleQuotes(cb.ssid) : getTileLabel();
- } else if (wifiNotConnected) {
- state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK);
- state.label = r.getString(R.string.quick_settings_wifi_label);
- } else {
- state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK);
- state.label = r.getString(R.string.quick_settings_wifi_label);
- }
- minimalContentDescription.append(
- mContext.getString(R.string.quick_settings_wifi_label)).append(",");
- if (state.value) {
- if (wifiConnected) {
- minimalStateDescription.append(cb.wifiSignalContentDescription);
- minimalContentDescription.append(removeDoubleQuotes(cb.ssid));
- if (!TextUtils.isEmpty(state.secondaryLabel)) {
- minimalContentDescription.append(",").append(state.secondaryLabel);
- }
- }
- }
- state.stateDescription = minimalStateDescription.toString();
- state.contentDescription = minimalContentDescription.toString();
- state.dualLabelContentDescription = r.getString(
- R.string.accessibility_quick_settings_open_settings, getTileLabel());
- state.expandedAccessibilityClassName = Switch.class.getName();
- }
-
- private CharSequence getSecondaryLabel(boolean isTransient, String statusLabel) {
- return isTransient
- ? mContext.getString(R.string.quick_settings_wifi_secondary_label_transient)
- : statusLabel;
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.QS_WIFI;
- }
-
- @Override
- public boolean isAvailable() {
- return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
- }
-
- @Nullable
- private static String removeDoubleQuotes(String string) {
- if (string == null) return null;
- final int length = string.length();
- if ((length > 1) && (string.charAt(0) == '"') && (string.charAt(length - 1) == '"')) {
- return string.substring(1, length - 1);
- }
- return string;
- }
-
- protected static final class CallbackInfo {
- boolean enabled;
- boolean connected;
- int wifiSignalIconId;
- @Nullable
- String ssid;
- boolean activityIn;
- boolean activityOut;
- @Nullable
- String wifiSignalContentDescription;
- boolean isTransient;
- @Nullable
- public String statusLabel;
-
- @Override
- public String toString() {
- return new StringBuilder("CallbackInfo[")
- .append("enabled=").append(enabled)
- .append(",connected=").append(connected)
- .append(",wifiSignalIconId=").append(wifiSignalIconId)
- .append(",ssid=").append(ssid)
- .append(",activityIn=").append(activityIn)
- .append(",activityOut=").append(activityOut)
- .append(",wifiSignalContentDescription=").append(wifiSignalContentDescription)
- .append(",isTransient=").append(isTransient)
- .append(']').toString();
- }
- }
-
- protected final class WifiSignalCallback implements SignalCallback {
- final CallbackInfo mInfo = new CallbackInfo();
-
- @Override
- public void setWifiIndicators(@NonNull WifiIndicators indicators) {
- if (DEBUG) Log.d(TAG, "onWifiSignalChanged enabled=" + indicators.enabled);
- if (indicators.qsIcon == null) {
- return;
- }
- mInfo.enabled = indicators.enabled;
- mInfo.connected = indicators.qsIcon.visible;
- mInfo.wifiSignalIconId = indicators.qsIcon.icon;
- mInfo.ssid = indicators.description;
- mInfo.activityIn = indicators.activityIn;
- mInfo.activityOut = indicators.activityOut;
- mInfo.wifiSignalContentDescription = indicators.qsIcon.contentDescription;
- mInfo.isTransient = indicators.isTransient;
- mInfo.statusLabel = indicators.statusLabel;
- refreshState();
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index a6c7781..72c6bfe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -101,7 +101,6 @@
@MainThread
public void onManagedProfileRemoved() {
mHost.removeTile(getTileSpec());
- mHost.unmarkTileAsAutoAdded(getTileSpec());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index 314252b..4c9c99c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -36,6 +36,7 @@
import com.android.systemui.qs.QSUserSwitcherEvent
import com.android.systemui.qs.tiles.UserDetailView
import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.user.ui.dialog.DialogShowerImpl
import javax.inject.Inject
import javax.inject.Provider
@@ -130,19 +131,6 @@
}
}
- private class DialogShowerImpl(
- private val animateFrom: Dialog,
- private val dialogLaunchAnimator: DialogLaunchAnimator
- ) : DialogInterface by animateFrom, DialogShower {
- override fun showDialog(dialog: Dialog, cuj: DialogCuj) {
- dialogLaunchAnimator.showFromDialog(
- dialog,
- animateFrom = animateFrom,
- cuj
- )
- }
- }
-
interface DialogShower : DialogInterface {
fun showDialog(dialog: Dialog, cuj: DialogCuj)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index b4934cf..bf5fbd2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -20,8 +20,7 @@
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_STORAGE;
import static com.android.systemui.screenshot.LogConfig.logTag;
-import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.QUICK_SHARE_ACTION;
-import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.REGULAR_SMART_ACTIONS;
+import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType;
import android.app.ActivityTaskManager;
import android.app.Notification;
@@ -155,7 +154,8 @@
CompletableFuture<List<Notification.Action>> smartActionsFuture =
mScreenshotSmartActions.getSmartActionsFuture(
- mScreenshotId, uri, image, mSmartActionsProvider, REGULAR_SMART_ACTIONS,
+ mScreenshotId, uri, image, mSmartActionsProvider,
+ ScreenshotSmartActionType.REGULAR_SMART_ACTIONS,
smartActionsEnabled, user);
List<Notification.Action> smartActions = new ArrayList<>();
if (smartActionsEnabled) {
@@ -166,7 +166,8 @@
smartActions.addAll(buildSmartActions(
mScreenshotSmartActions.getSmartActions(
mScreenshotId, smartActionsFuture, timeoutMs,
- mSmartActionsProvider, REGULAR_SMART_ACTIONS),
+ mSmartActionsProvider,
+ ScreenshotSmartActionType.REGULAR_SMART_ACTIONS),
mContext));
}
@@ -476,7 +477,7 @@
CompletableFuture<List<Notification.Action>> quickShareActionsFuture =
mScreenshotSmartActions.getSmartActionsFuture(
mScreenshotId, null, image, mSmartActionsProvider,
- QUICK_SHARE_ACTION,
+ ScreenshotSmartActionType.QUICK_SHARE_ACTION,
true /* smartActionsEnabled */, user);
int timeoutMs = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -485,7 +486,8 @@
List<Notification.Action> quickShareActions =
mScreenshotSmartActions.getSmartActions(
mScreenshotId, quickShareActionsFuture, timeoutMs,
- mSmartActionsProvider, QUICK_SHARE_ACTION);
+ mSmartActionsProvider,
+ ScreenshotSmartActionType.QUICK_SHARE_ACTION);
if (!quickShareActions.isEmpty()) {
mQuickShareData.quickShareAction = quickShareActions.get(0);
mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 5716a1d72..b21a485 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -280,6 +280,7 @@
private final TimeoutHandler mScreenshotHandler;
private final ActionIntentExecutor mActionExecutor;
private final UserManager mUserManager;
+ private final WorkProfileMessageController mWorkProfileMessageController;
private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
if (DEBUG_INPUT) {
@@ -326,7 +327,8 @@
BroadcastSender broadcastSender,
ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
ActionIntentExecutor actionExecutor,
- UserManager userManager
+ UserManager userManager,
+ WorkProfileMessageController workProfileMessageController
) {
mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsController;
@@ -358,6 +360,7 @@
mFlags = flags;
mActionExecutor = actionExecutor;
mUserManager = userManager;
+ mWorkProfileMessageController = workProfileMessageController;
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
@@ -683,10 +686,9 @@
return true;
}
});
-
if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
- mScreenshotView.badgeScreenshot(
- mContext.getPackageManager().getUserBadgeForDensity(owner, 0));
+ mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
+ mContext.getDrawable(R.drawable.overlay_badge_background), owner));
}
mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
if (DEBUG_WINDOW) {
@@ -784,9 +786,9 @@
mLongScreenshotHolder.setLongScreenshot(longScreenshot);
mLongScreenshotHolder.setTransitionDestinationCallback(
(transitionDestination, onTransitionEnd) -> {
- mScreenshotView.startLongScreenshotTransition(
- transitionDestination, onTransitionEnd,
- longScreenshot);
+ mScreenshotView.startLongScreenshotTransition(
+ transitionDestination, onTransitionEnd,
+ longScreenshot);
// TODO: Do this via ActionIntentExecutor instead.
mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
@@ -1037,10 +1039,8 @@
private void doPostAnimation(ScreenshotController.SavedImageData imageData) {
mScreenshotView.setChipIntents(imageData);
- if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
- && mUserManager.isManagedProfile(imageData.owner.getIdentifier())) {
- // TODO: Read app from configuration
- mScreenshotView.showWorkProfileMessage("Files");
+ if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+ mWorkProfileMessageController.onScreenshotTaken(imageData.owner, mScreenshotView);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index e8ceb52..200a7dc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -33,6 +33,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
@@ -100,7 +101,8 @@
* Handles the visual elements and animations for the screenshot flow.
*/
public class ScreenshotView extends FrameLayout implements
- ViewTreeObserver.OnComputeInternalInsetsListener {
+ ViewTreeObserver.OnComputeInternalInsetsListener,
+ WorkProfileMessageController.WorkProfileMessageDisplay {
interface ScreenshotViewCallback {
void onUserInteraction();
@@ -351,13 +353,23 @@
* been taken and which app can be used to view it.
*
* @param appName The name of the app to use to view screenshots
+ * @param appIcon Optional icon for the relevant files app
+ * @param onDismiss Runnable to be run when the user dismisses this message
*/
- void showWorkProfileMessage(String appName) {
+ @Override
+ public void showWorkProfileMessage(CharSequence appName, @Nullable Drawable appIcon,
+ Runnable onDismiss) {
+ if (appIcon != null) {
+ // Replace the default icon if one is provided.
+ ImageView imageView = mMessageContainer.findViewById(R.id.screenshot_message_icon);
+ imageView.setImageDrawable(appIcon);
+ }
mMessageContent.setText(
mContext.getString(R.string.screenshot_work_profile_notification, appName));
mMessageContainer.setVisibility(VISIBLE);
mMessageContainer.findViewById(R.id.message_dismiss_button).setOnClickListener((v) -> {
mMessageContainer.setVisibility(View.GONE);
+ onDismiss.run();
});
}
@@ -1078,7 +1090,7 @@
mScreenshotBadge.setVisibility(View.GONE);
mScreenshotBadge.setImageDrawable(null);
mPendingSharedTransition = false;
- mActionsContainerBackground.setVisibility(View.GONE);
+ mActionsContainerBackground.setVisibility(View.INVISIBLE);
mActionsContainer.setVisibility(View.GONE);
mDismissButton.setVisibility(View.GONE);
mScrollingScrim.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 2176825..35e9f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -21,7 +21,6 @@
import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE;
import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI;
-import static com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR;
import static com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
@@ -122,7 +121,6 @@
mContext = context;
mBgExecutor = bgExecutor;
mFeatureFlags = featureFlags;
- mFeatureFlags.addListener(SCREENSHOT_REQUEST_PROCESSOR, FlagEvent::requestNoRestart);
mFeatureFlags.addListener(SCREENSHOT_WORK_PROFILE_POLICY, FlagEvent::requestNoRestart);
mProcessor = processor;
}
@@ -224,14 +222,8 @@
return;
}
- if (mFeatureFlags.isEnabled(SCREENSHOT_REQUEST_PROCESSOR)) {
- Log.d(TAG, "handleMessage: Using request processor");
- mProcessor.processAsync(request,
- (r) -> dispatchToController(r, onSaved, callback));
- return;
- }
-
- dispatchToController(request, onSaved, callback);
+ mProcessor.processAsync(request,
+ (r) -> dispatchToController(r, onSaved, callback));
}
private void dispatchToController(ScreenshotHelper.ScreenshotRequest request,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
new file mode 100644
index 0000000..5d7e56f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.Log
+import com.android.systemui.R
+import javax.inject.Inject
+
+/**
+ * Handles all the non-UI portions of the work profile first run:
+ * - Track whether the user has already dismissed it.
+ * - Load the proper icon and app name.
+ */
+class WorkProfileMessageController
+@Inject
+constructor(
+ private val context: Context,
+ private val userManager: UserManager,
+ private val packageManager: PackageManager,
+) {
+
+ /**
+ * Determine if a message should be shown to the user, send message details to messageDisplay if
+ * appropriate.
+ */
+ fun onScreenshotTaken(userHandle: UserHandle, messageDisplay: WorkProfileMessageDisplay) {
+ if (userManager.isManagedProfile(userHandle.identifier) && !messageAlreadyDismissed()) {
+ var badgedIcon: Drawable? = null
+ var label: CharSequence? = null
+ val fileManager = fileManagerComponentName()
+ try {
+ val info =
+ packageManager.getActivityInfo(
+ fileManager,
+ PackageManager.ComponentInfoFlags.of(0)
+ )
+ val icon = packageManager.getActivityIcon(fileManager)
+ badgedIcon = packageManager.getUserBadgedIcon(icon, userHandle)
+ label = info.loadLabel(packageManager)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Component $fileManager not found")
+ }
+
+ // If label wasn't loaded, use a default
+ val badgedLabel =
+ packageManager.getUserBadgedLabel(label ?: defaultFileAppName(), userHandle)
+
+ messageDisplay.showWorkProfileMessage(badgedLabel, badgedIcon) { onMessageDismissed() }
+ }
+ }
+
+ private fun messageAlreadyDismissed(): Boolean {
+ return sharedPreference().getBoolean(PREFERENCE_KEY, false)
+ }
+
+ private fun onMessageDismissed() {
+ val editor = sharedPreference().edit()
+ editor.putBoolean(PREFERENCE_KEY, true)
+ editor.apply()
+ }
+
+ private fun sharedPreference() =
+ context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
+
+ private fun fileManagerComponentName() =
+ ComponentName.unflattenFromString(
+ context.getString(R.string.config_sceenshotWorkProfileFilesApp)
+ )
+
+ private fun defaultFileAppName() = context.getString(R.string.screenshot_default_files_app_name)
+
+ /** UI that can show work profile messages (ScreenshotView in practice) */
+ interface WorkProfileMessageDisplay {
+ /**
+ * Show the given message and icon, calling onDismiss if the user explicitly dismisses the
+ * message.
+ */
+ fun showWorkProfileMessage(text: CharSequence, icon: Drawable?, onDismiss: Runnable)
+ }
+
+ companion object {
+ const val TAG = "WorkProfileMessageCtrl"
+ const val SHARED_PREFERENCES_NAME = "com.android.systemui.screenshot"
+ const val PREFERENCE_KEY = "work_profile_first_run"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 28da38b..61390c5 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -112,7 +112,7 @@
// These get called when a managed profile goes in or out of quiet mode.
addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
-
+ addAction(Intent.ACTION_MANAGED_PROFILE_ADDED)
addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)
addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)
}
@@ -129,6 +129,7 @@
Intent.ACTION_USER_INFO_CHANGED,
Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
+ Intent.ACTION_MANAGED_PROFILE_ADDED,
Intent.ACTION_MANAGED_PROFILE_REMOVED,
Intent.ACTION_MANAGED_PROFILE_UNLOCKED -> {
handleProfilesChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 7fc0a5f..e406be1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -175,9 +175,10 @@
*/
var shadeExpandedFraction = -1f
set(value) {
- if (visible && field != value) {
+ if (field != value) {
header.alpha = ShadeInterpolation.getContentAlpha(value)
field = value
+ updateVisibility()
}
}
@@ -331,6 +332,9 @@
.setDuration(duration)
.alpha(if (show) 0f else 1f)
.setInterpolator(if (show) Interpolators.ALPHA_OUT else Interpolators.ALPHA_IN)
+ .setUpdateListener {
+ updateVisibility()
+ }
.start()
}
@@ -414,7 +418,7 @@
private fun updateVisibility() {
val visibility = if (!largeScreenActive && !combinedHeaders || qsDisabled) {
View.GONE
- } else if (qsVisible) {
+ } else if (qsVisible && header.alpha > 0f) {
View.VISIBLE
} else {
View.INVISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index f8882cf..c68e1ad 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -138,12 +138,15 @@
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
@@ -360,6 +363,7 @@
private final FragmentListener mQsFragmentListener = new QsFragmentListener();
private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
private final NotificationGutsManager mGutsManager;
+ private final AlternateBouncerInteractor mAlternateBouncerInteractor;
private long mDownTime;
private boolean mTouchSlopExceededBeforeDown;
@@ -687,12 +691,16 @@
private boolean mExpandLatencyTracking;
private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
private OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel;
+ private LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel;
+ private LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel;
private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private CoroutineDispatcher mMainDispatcher;
- private boolean mIsToLockscreenTransitionRunning = false;
+ private boolean mIsOcclusionTransitionRunning = false;
private int mDreamingToLockscreenTransitionTranslationY;
private int mOccludedToLockscreenTransitionTranslationY;
+ private int mLockscreenToDreamingTransitionTranslationY;
+ private int mLockscreenToOccludedTransitionTranslationY;
private boolean mUnocclusionTransitionFlagEnabled = false;
private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
@@ -711,13 +719,25 @@
private final Consumer<TransitionStep> mDreamingToLockscreenTransition =
(TransitionStep step) -> {
- mIsToLockscreenTransitionRunning =
+ mIsOcclusionTransitionRunning =
step.getTransitionState() == TransitionState.RUNNING;
};
private final Consumer<TransitionStep> mOccludedToLockscreenTransition =
(TransitionStep step) -> {
- mIsToLockscreenTransitionRunning =
+ mIsOcclusionTransitionRunning =
+ step.getTransitionState() == TransitionState.RUNNING;
+ };
+
+ private final Consumer<TransitionStep> mLockscreenToDreamingTransition =
+ (TransitionStep step) -> {
+ mIsOcclusionTransitionRunning =
+ step.getTransitionState() == TransitionState.RUNNING;
+ };
+
+ private final Consumer<TransitionStep> mLockscreenToOccludedTransition =
+ (TransitionStep step) -> {
+ mIsOcclusionTransitionRunning =
step.getTransitionState() == TransitionState.RUNNING;
};
@@ -789,8 +809,11 @@
SystemClock systemClock,
KeyguardBottomAreaViewModel keyguardBottomAreaViewModel,
KeyguardBottomAreaInteractor keyguardBottomAreaInteractor,
+ AlternateBouncerInteractor alternateBouncerInteractor,
DreamingToLockscreenTransitionViewModel dreamingToLockscreenTransitionViewModel,
OccludedToLockscreenTransitionViewModel occludedToLockscreenTransitionViewModel,
+ LockscreenToDreamingTransitionViewModel lockscreenToDreamingTransitionViewModel,
+ LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel,
@Main CoroutineDispatcher mainDispatcher,
KeyguardTransitionInteractor keyguardTransitionInteractor,
DumpManager dumpManager) {
@@ -810,6 +833,8 @@
mGutsManager = gutsManager;
mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
mOccludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel;
+ mLockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel;
+ mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
@@ -980,6 +1005,7 @@
unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay);
}
});
+ mAlternateBouncerInteractor = alternateBouncerInteractor;
dumpManager.registerDumpable(this);
}
@@ -1117,22 +1143,44 @@
collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
mDreamingToLockscreenTransition, mMainDispatcher);
collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(),
- toLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
+ setTransitionAlpha(mNotificationStackScrollLayoutController),
mMainDispatcher);
collectFlow(mView, mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY(
mDreamingToLockscreenTransitionTranslationY),
- toLockscreenTransitionY(mNotificationStackScrollLayoutController),
+ setTransitionY(mNotificationStackScrollLayoutController),
mMainDispatcher);
// Occluded->Lockscreen
collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(),
mOccludedToLockscreenTransition, mMainDispatcher);
collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(),
- toLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
+ setTransitionAlpha(mNotificationStackScrollLayoutController),
mMainDispatcher);
collectFlow(mView, mOccludedToLockscreenTransitionViewModel.lockscreenTranslationY(
mOccludedToLockscreenTransitionTranslationY),
- toLockscreenTransitionY(mNotificationStackScrollLayoutController),
+ setTransitionY(mNotificationStackScrollLayoutController),
+ mMainDispatcher);
+
+ // Lockscreen->Dreaming
+ collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(),
+ mLockscreenToDreamingTransition, mMainDispatcher);
+ collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(),
+ setTransitionAlpha(mNotificationStackScrollLayoutController),
+ mMainDispatcher);
+ collectFlow(mView, mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY(
+ mLockscreenToDreamingTransitionTranslationY),
+ setTransitionY(mNotificationStackScrollLayoutController),
+ mMainDispatcher);
+
+ // Lockscreen->Occluded
+ collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToOccludedTransition(),
+ mLockscreenToOccludedTransition, mMainDispatcher);
+ collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(),
+ setTransitionAlpha(mNotificationStackScrollLayoutController),
+ mMainDispatcher);
+ collectFlow(mView, mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY(
+ mLockscreenToOccludedTransitionTranslationY),
+ setTransitionY(mNotificationStackScrollLayoutController),
mMainDispatcher);
}
}
@@ -1173,6 +1221,10 @@
R.dimen.dreaming_to_lockscreen_transition_lockscreen_translation_y);
mOccludedToLockscreenTransitionTranslationY = mResources.getDimensionPixelSize(
R.dimen.occluded_to_lockscreen_transition_lockscreen_translation_y);
+ mLockscreenToDreamingTransitionTranslationY = mResources.getDimensionPixelSize(
+ R.dimen.lockscreen_to_dreaming_transition_lockscreen_translation_y);
+ mLockscreenToOccludedTransitionTranslationY = mResources.getDimensionPixelSize(
+ R.dimen.lockscreen_to_occluded_transition_lockscreen_translation_y);
}
private void updateViewControllers(KeyguardStatusView keyguardStatusView,
@@ -1836,7 +1888,7 @@
}
private void updateClock() {
- if (mIsToLockscreenTransitionRunning) {
+ if (mIsOcclusionTransitionRunning) {
return;
}
float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha;
@@ -2288,7 +2340,7 @@
// When false, down but not synthesized motion event.
mLastEventSynthesizedDown = mExpectingSynthesizedDown;
mLastDownEvents.insert(
- mSystemClock.currentTimeMillis(),
+ event.getEventTime(),
mDownX,
mDownY,
mQsTouchAboveFalsingThreshold,
@@ -2727,7 +2779,7 @@
} else if (statusBarState == KEYGUARD
|| statusBarState == StatusBarState.SHADE_LOCKED) {
mKeyguardBottomArea.setVisibility(View.VISIBLE);
- if (!mIsToLockscreenTransitionRunning) {
+ if (!mIsOcclusionTransitionRunning) {
mKeyguardBottomArea.setAlpha(1f);
}
} else {
@@ -3596,7 +3648,7 @@
}
private void updateNotificationTranslucency() {
- if (mIsToLockscreenTransitionRunning) {
+ if (mIsOcclusionTransitionRunning) {
return;
}
float alpha = 1f;
@@ -3654,7 +3706,7 @@
}
private void updateKeyguardBottomAreaAlpha() {
- if (mIsToLockscreenTransitionRunning) {
+ if (mIsOcclusionTransitionRunning) {
return;
}
// There are two possible panel expansion behaviors:
@@ -4902,7 +4954,7 @@
mUpdateFlingVelocity = vel;
}
} else if (!mCentralSurfaces.isBouncerShowing()
- && !mStatusBarKeyguardViewManager.isShowingAlternateBouncer()
+ && !mAlternateBouncerInteractor.isVisibleState()
&& !mKeyguardStateController.isKeyguardGoingAway()) {
onEmptySpaceClick();
onTrackingStopped(true);
@@ -5891,7 +5943,7 @@
mCurrentPanelState = state;
}
- private Consumer<Float> toLockscreenTransitionAlpha(
+ private Consumer<Float> setTransitionAlpha(
NotificationStackScrollLayoutController stackScroller) {
return (Float alpha) -> {
mKeyguardStatusViewController.setAlpha(alpha);
@@ -5909,7 +5961,7 @@
};
}
- private Consumer<Float> toLockscreenTransitionY(
+ private Consumer<Float> setTransitionY(
NotificationStackScrollLayoutController stackScroller) {
return (Float translationY) -> {
mKeyguardStatusViewController.setTranslationY(translationY, /* excludeMedia= */false);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 5c1ddd6..7ed6e3e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -16,6 +16,8 @@
package com.android.systemui.shade;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+
import android.app.StatusBarManager;
import android.media.AudioManager;
import android.media.session.MediaSessionLegacyHelper;
@@ -39,6 +41,10 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.statusbar.DragDownHelper;
@@ -57,6 +63,7 @@
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import java.io.PrintWriter;
+import java.util.function.Consumer;
import javax.inject.Inject;
@@ -79,6 +86,7 @@
private final AmbientState mAmbientState;
private final PulsingGestureListener mPulsingGestureListener;
private final NotificationInsetsController mNotificationInsetsController;
+ private final AlternateBouncerInteractor mAlternateBouncerInteractor;
private GestureDetector mPulsingWakeupGestureHandler;
private View mBrightnessMirror;
@@ -96,6 +104,13 @@
private final ShadeExpansionStateManager mShadeExpansionStateManager;
private boolean mIsTrackingBarGesture = false;
+ private boolean mIsOcclusionTransitionRunning = false;
+
+ private final Consumer<TransitionStep> mLockscreenToDreamingTransition =
+ (TransitionStep step) -> {
+ mIsOcclusionTransitionRunning =
+ step.getTransitionState() == TransitionState.RUNNING;
+ };
@Inject
public NotificationShadeWindowViewController(
@@ -119,7 +134,9 @@
PulsingGestureListener pulsingGestureListener,
FeatureFlags featureFlags,
KeyguardBouncerViewModel keyguardBouncerViewModel,
- KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory
+ KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
+ AlternateBouncerInteractor alternateBouncerInteractor,
+ KeyguardTransitionInteractor keyguardTransitionInteractor
) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
@@ -139,6 +156,7 @@
mAmbientState = ambientState;
mPulsingGestureListener = pulsingGestureListener;
mNotificationInsetsController = notificationInsetsController;
+ mAlternateBouncerInteractor = alternateBouncerInteractor;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -148,6 +166,11 @@
keyguardBouncerViewModel,
keyguardBouncerComponentFactory);
}
+
+ if (featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION)) {
+ collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
+ mLockscreenToDreamingTransition);
+ }
}
/**
@@ -215,6 +238,10 @@
return true;
}
+ if (mIsOcclusionTransitionRunning) {
+ return false;
+ }
+
mFalsingCollector.onTouchEvent(ev);
mPulsingWakeupGestureHandler.onTouchEvent(ev);
mStatusBarKeyguardViewManager.onTouch(ev);
@@ -292,7 +319,7 @@
return true;
}
- if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
+ if (mAlternateBouncerInteractor.isVisibleState()) {
// capture all touches if the alt auth bouncer is showing
return true;
}
@@ -330,7 +357,7 @@
handled = !mService.isPulsing();
}
- if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
+ if (mAlternateBouncerInteractor.isVisibleState()) {
// eat the touch
handled = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 5fedbeb..11617be 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -36,16 +36,9 @@
buffer.log(TAG, LogLevel.DEBUG, msg)
}
- private inline fun log(
- logLevel: LogLevel,
- initializer: LogMessage.() -> Unit,
- noinline printer: LogMessage.() -> String
- ) {
- buffer.log(TAG, logLevel, initializer, printer)
- }
-
fun onQsInterceptMoveQsTrackingEnabled(h: Float) {
- log(
+ buffer.log(
+ TAG,
LogLevel.VERBOSE,
{ double1 = h.toDouble() },
{ "onQsIntercept: move action, QS tracking enabled. h = $double1" }
@@ -62,7 +55,8 @@
keyguardShowing: Boolean,
qsExpansionEnabled: Boolean
) {
- log(
+ buffer.log(
+ TAG,
LogLevel.VERBOSE,
{
int1 = initialTouchY.toInt()
@@ -82,7 +76,8 @@
}
fun logMotionEvent(event: MotionEvent, message: String) {
- log(
+ buffer.log(
+ TAG,
LogLevel.VERBOSE,
{
str1 = message
@@ -99,7 +94,8 @@
}
fun logMotionEventStatusBarState(event: MotionEvent, statusBarState: Int, message: String) {
- log(
+ buffer.log(
+ TAG,
LogLevel.VERBOSE,
{
str1 = message
@@ -128,25 +124,33 @@
tracking: Boolean,
dragDownPxAmount: Float,
) {
- log(LogLevel.VERBOSE, {
- str1 = message
- double1 = fraction.toDouble()
- bool1 = expanded
- bool2 = tracking
- long1 = dragDownPxAmount.toLong()
- }, {
- "$str1 fraction=$double1,expanded=$bool1," +
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = message
+ double1 = fraction.toDouble()
+ bool1 = expanded
+ bool2 = tracking
+ long1 = dragDownPxAmount.toLong()
+ },
+ {
+ "$str1 fraction=$double1,expanded=$bool1," +
"tracking=$bool2," + "dragDownPxAmount=$dragDownPxAmount"
- })
+ }
+ )
}
fun logHasVibrated(hasVibratedOnOpen: Boolean, fraction: Float) {
- log(LogLevel.VERBOSE, {
- bool1 = hasVibratedOnOpen
- double1 = fraction.toDouble()
- }, {
- "hasVibratedOnOpen=$bool1, expansionFraction=$double1"
- })
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ bool1 = hasVibratedOnOpen
+ double1 = fraction.toDouble()
+ },
+ { "hasVibratedOnOpen=$bool1, expansionFraction=$double1" }
+ )
}
fun logQsExpansionChanged(
@@ -159,42 +163,56 @@
qsAnimatorExpand: Boolean,
animatingQs: Boolean
) {
- log(LogLevel.VERBOSE, {
- str1 = message
- bool1 = qsExpanded
- int1 = qsMinExpansionHeight
- int2 = qsMaxExpansionHeight
- bool2 = stackScrollerOverscrolling
- bool3 = dozing
- bool4 = qsAnimatorExpand
- // 0 = false, 1 = true
- long1 = animatingQs.compareTo(false).toLong()
- }, {
- "$str1 qsExpanded=$bool1,qsMinExpansionHeight=$int1,qsMaxExpansionHeight=$int2," +
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = message
+ bool1 = qsExpanded
+ int1 = qsMinExpansionHeight
+ int2 = qsMaxExpansionHeight
+ bool2 = stackScrollerOverscrolling
+ bool3 = dozing
+ bool4 = qsAnimatorExpand
+ // 0 = false, 1 = true
+ long1 = animatingQs.compareTo(false).toLong()
+ },
+ {
+ "$str1 qsExpanded=$bool1,qsMinExpansionHeight=$int1,qsMaxExpansionHeight=$int2," +
"stackScrollerOverscrolling=$bool2,dozing=$bool3,qsAnimatorExpand=$bool4," +
"animatingQs=$long1"
- })
+ }
+ )
}
fun logSingleTapUp(isDozing: Boolean, singleTapEnabled: Boolean, isNotDocked: Boolean) {
- log(LogLevel.DEBUG, {
- bool1 = isDozing
- bool2 = singleTapEnabled
- bool3 = isNotDocked
- }, {
- "PulsingGestureListener#onSingleTapUp all of this must true for single " +
- "tap to be detected: isDozing: $bool1, singleTapEnabled: $bool2, isNotDocked: $bool3"
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ bool1 = isDozing
+ bool2 = singleTapEnabled
+ bool3 = isNotDocked
+ },
+ {
+ "PulsingGestureListener#onSingleTapUp all of this must true for single " +
+ "tap to be detected: isDozing: $bool1, singleTapEnabled: $bool2, isNotDocked: $bool3"
})
}
fun logSingleTapUpFalsingState(proximityIsNotNear: Boolean, isNotFalseTap: Boolean) {
- log(LogLevel.DEBUG, {
- bool1 = proximityIsNotNear
- bool2 = isNotFalseTap
- }, {
- "PulsingGestureListener#onSingleTapUp all of this must true for single " +
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ bool1 = proximityIsNotNear
+ bool2 = isNotFalseTap
+ },
+ {
+ "PulsingGestureListener#onSingleTapUp all of this must true for single " +
"tap to be detected: proximityIsNotNear: $bool1, isNotFalseTap: $bool2"
- })
+ }
+ )
}
fun logNotInterceptingTouchInstantExpanding(
@@ -202,13 +220,18 @@
notificationsDragEnabled: Boolean,
touchDisabled: Boolean
) {
- log(LogLevel.VERBOSE, {
- bool1 = instantExpanding
- bool2 = notificationsDragEnabled
- bool3 = touchDisabled
- }, {
- "NPVC not intercepting touch, instantExpanding: $bool1, " +
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ bool1 = instantExpanding
+ bool2 = notificationsDragEnabled
+ bool3 = touchDisabled
+ },
+ {
+ "NPVC not intercepting touch, instantExpanding: $bool1, " +
"!notificationsDragEnabled: $bool2, touchDisabled: $bool3"
- })
+ }
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
index c6a6e87..9851625 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
@@ -32,11 +32,21 @@
ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
fun logApplyingWindowLayoutParams(lp: WindowManager.LayoutParams) {
- log(DEBUG, { str1 = lp.toString() }, { "Applying new window layout params: $str1" })
+ buffer.log(
+ TAG,
+ DEBUG,
+ { str1 = lp.toString() },
+ { "Applying new window layout params: $str1" }
+ )
}
fun logNewState(state: Any) {
- log(DEBUG, { str1 = state.toString() }, { "Applying new state: $str1" })
+ buffer.log(
+ TAG,
+ DEBUG,
+ { str1 = state.toString() },
+ { "Applying new state: $str1" }
+ )
}
private inline fun log(
@@ -48,11 +58,16 @@
}
fun logApplyVisibility(visible: Boolean) {
- log(DEBUG, { bool1 = visible }, { "Updating visibility, should be visible : $bool1" })
+ buffer.log(
+ TAG,
+ DEBUG,
+ { bool1 = visible },
+ { "Updating visibility, should be visible : $bool1" })
}
fun logShadeVisibleAndFocusable(visible: Boolean) {
- log(
+ buffer.log(
+ TAG,
DEBUG,
{ bool1 = visible },
{ "Updating shade, should be visible and focusable: $bool1" }
@@ -60,6 +75,11 @@
}
fun logShadeFocusable(focusable: Boolean) {
- log(DEBUG, { bool1 = focusable }, { "Updating shade, should be focusable : $bool1" })
+ buffer.log(
+ TAG,
+ DEBUG,
+ { bool1 = focusable },
+ { "Updating shade, should be focusable : $bool1" }
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 750d004..584a382 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -334,7 +334,7 @@
/**
* @see IStatusBar#setBiometicContextListener(IBiometricContextListener)
*/
- default void setBiometicContextListener(IBiometricContextListener listener) {
+ default void setBiometricContextListener(IBiometricContextListener listener) {
}
/**
@@ -1583,7 +1583,7 @@
}
case MSG_SET_BIOMETRICS_LISTENER:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).setBiometicContextListener(
+ mCallbacks.get(i).setBiometricContextListener(
(IBiometricContextListener) msg.obj);
}
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 6a658b6..006b552 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -41,6 +41,7 @@
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
+import static com.android.systemui.plugins.log.LogLevel.ERROR;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -90,6 +91,7 @@
import com.android.systemui.keyguard.KeyguardIndication;
import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -155,6 +157,7 @@
private final KeyguardBypassController mKeyguardBypassController;
private final AccessibilityManager mAccessibilityManager;
private final Handler mHandler;
+ private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@VisibleForTesting
public KeyguardIndicationRotateTextViewController mRotateTextViewController;
@@ -234,7 +237,8 @@
KeyguardBypassController keyguardBypassController,
AccessibilityManager accessibilityManager,
FaceHelpMessageDeferral faceHelpMessageDeferral,
- KeyguardLogger keyguardLogger) {
+ KeyguardLogger keyguardLogger,
+ AlternateBouncerInteractor alternateBouncerInteractor) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mDevicePolicyManager = devicePolicyManager;
@@ -256,6 +260,7 @@
mScreenLifecycle = screenLifecycle;
mKeyguardLogger = keyguardLogger;
mScreenLifecycle.addObserver(mScreenObserver);
+ mAlternateBouncerInteractor = alternateBouncerInteractor;
mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>();
@@ -928,7 +933,7 @@
}
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
- if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
+ if (mAlternateBouncerInteractor.isVisibleState()) {
return; // udfps affordance is highlighted, no need to show action to unlock
} else if (mKeyguardUpdateMonitor.isFaceEnrolled()
&& !mKeyguardUpdateMonitor.getIsFaceAuthenticated()) {
@@ -1028,7 +1033,7 @@
mChargingTimeRemaining = mPowerPluggedIn
? mBatteryInfo.computeChargeTimeRemaining() : -1;
} catch (RemoteException e) {
- mKeyguardLogger.logException(e, "Error calling IBatteryStats");
+ mKeyguardLogger.log(TAG, ERROR, "Error calling IBatteryStats", e);
mChargingTimeRemaining = -1;
}
updateDeviceEntryIndication(!wasPluggedIn && mPowerPluggedInWired);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 56b689e..7d0ac18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -290,7 +290,8 @@
false,
null,
0,
- false
+ false,
+ 0
);
}
return ranking;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 8f9365c..99081e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -65,8 +65,6 @@
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.ListenerSet;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -74,6 +72,8 @@
import java.util.Optional;
import java.util.function.Consumer;
+import dagger.Lazy;
+
/**
* Class for handling remote input state over a set of notifications. This class handles things
* like keeping notifications temporarily that were cancelled as a response to a remote input
@@ -465,8 +465,7 @@
riv.getController().setRemoteInput(input);
riv.getController().setRemoteInputs(inputs);
riv.getController().setEditedSuggestionInfo(editedSuggestionInfo);
- ViewGroup parent = view.getParent() != null ? (ViewGroup) view.getParent() : null;
- riv.focusAnimated(parent);
+ riv.focusAnimated();
if (userMessageContent != null) {
riv.setEditTextContent(userMessageContent);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 14d0d7e..9a65e34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -31,6 +31,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -61,7 +62,6 @@
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarIconList;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags;
@@ -280,7 +280,7 @@
@SysUISingleton
static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager,
KeyguardStateController keyguardStateController,
- Lazy<StatusBarKeyguardViewManager> statusBarKeyguardViewManager,
+ Lazy<AlternateBouncerInteractor> alternateBouncerInteractor,
InteractionJankMonitor interactionJankMonitor) {
DialogLaunchAnimator.Callback callback = new DialogLaunchAnimator.Callback() {
@Override
@@ -300,7 +300,7 @@
@Override
public boolean isShowingAlternateAuthOnUnlock() {
- return statusBarKeyguardViewManager.get().canShowAlternateBouncer();
+ return alternateBouncerInteractor.get().canShowAlternateBouncerForFingerprint();
}
};
return new DialogLaunchAnimator(callback, interactionJankMonitor);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
index c496102..b084a76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
@@ -109,7 +109,7 @@
return true;
}
if (ev.getAction() == MotionEvent.ACTION_UP) {
- mView.setLastActionUpTime(SystemClock.uptimeMillis());
+ mView.setLastActionUpTime(ev.getEventTime());
}
// With a11y, just do nothing.
if (mAccessibilityManager.isTouchExplorationEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 8d48d73..9b93d7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -1431,6 +1431,22 @@
@Override
public void applyRoundnessAndInvalidate() {
boolean last = true;
+ if (mUseRoundnessSourceTypes) {
+ if (mNotificationHeaderWrapper != null) {
+ mNotificationHeaderWrapper.requestTopRoundness(
+ /* value = */ getTopRoundness(),
+ /* sourceType = */ FROM_PARENT,
+ /* animate = */ false
+ );
+ }
+ if (mNotificationHeaderWrapperLowPriority != null) {
+ mNotificationHeaderWrapperLowPriority.requestTopRoundness(
+ /* value = */ getTopRoundness(),
+ /* sourceType = */ FROM_PARENT,
+ /* animate = */ false
+ );
+ }
+ }
for (int i = mAttachedChildren.size() - 1; i >= 0; i--) {
ExpandableNotificationRow child = mAttachedChildren.get(i);
if (child.getVisibility() == View.GONE) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index ca1e397..356ddfa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1811,9 +1811,7 @@
@Override
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- mBottomInset = insets.getSystemWindowInsetBottom()
- + insets.getInsets(WindowInsets.Type.ime()).bottom;
-
+ mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
mWaterfallTopInset = 0;
final DisplayCutout cutout = insets.getDisplayCutout();
if (cutout != null) {
@@ -2262,7 +2260,11 @@
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private int getImeInset() {
- return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight()));
+ // The NotificationStackScrollLayout does not extend all the way to the bottom of the
+ // display. Therefore, subtract that space from the mBottomInset, in order to only include
+ // the portion of the bottom inset that actually overlaps the NotificationStackScrollLayout.
+ return Math.max(0, mBottomInset
+ - (getRootView().getHeight() - getHeight() - getLocationOnScreen()[1]));
}
/**
@@ -2970,12 +2972,19 @@
childInGroup = (ExpandableNotificationRow) requestedView;
requestedView = requestedRow = childInGroup.getNotificationParent();
}
- int position = 0;
+ final float scrimTopPadding = mAmbientState.isOnKeyguard() ? 0 : mMinimumPaddings;
+ int position = (int) scrimTopPadding;
+ int visibleIndex = -1;
+ ExpandableView lastVisibleChild = null;
for (int i = 0; i < getChildCount(); i++) {
ExpandableView child = getChildAtIndex(i);
boolean notGone = child.getVisibility() != View.GONE;
+ if (notGone) visibleIndex++;
if (notGone && !child.hasNoContentHeight()) {
- if (position != 0) {
+ if (position != scrimTopPadding) {
+ if (lastVisibleChild != null) {
+ position += calculateGapHeight(lastVisibleChild, child, visibleIndex);
+ }
position += mPaddingBetweenElements;
}
}
@@ -2987,6 +2996,7 @@
}
if (notGone) {
position += getIntrinsicHeight(child);
+ lastVisibleChild = child;
}
}
return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 9070ead..149ec54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -154,9 +154,7 @@
if (!mAutoTracker.isAdded(SAVER)) {
mDataSaverController.addCallback(mDataSaverListener);
}
- if (!mAutoTracker.isAdded(WORK)) {
- mManagedProfileController.addCallback(mProfileCallback);
- }
+ mManagedProfileController.addCallback(mProfileCallback);
if (!mAutoTracker.isAdded(NIGHT)
&& ColorDisplayManager.isNightDisplayAvailable(mContext)) {
mNightDisplayListener.setCallback(mNightDisplayCallback);
@@ -275,18 +273,18 @@
return mCurrentUser.getIdentifier();
}
- public void unmarkTileAsAutoAdded(String tabSpec) {
- mAutoTracker.setTileRemoved(tabSpec);
- }
-
private final ManagedProfileController.Callback mProfileCallback =
new ManagedProfileController.Callback() {
@Override
public void onManagedProfileChanged() {
- if (mAutoTracker.isAdded(WORK)) return;
if (mManagedProfileController.hasActiveProfile()) {
+ if (mAutoTracker.isAdded(WORK)) return;
mHost.addTile(WORK);
mAutoTracker.setTileAdded(WORK);
+ } else {
+ if (!mAutoTracker.isAdded(WORK)) return;
+ mHost.removeTile(WORK);
+ mAutoTracker.setTileRemoved(WORK);
}
}
@@ -429,7 +427,7 @@
initSafetyTile();
} else if (!isSafetyCenterEnabled && mAutoTracker.isAdded(mSafetySpec)) {
mHost.removeTile(mSafetySpec);
- mHost.unmarkTileAsAutoAdded(mSafetySpec);
+ mAutoTracker.setTileRemoved(mSafetySpec);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 895a293..db2c0a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -18,6 +18,8 @@
import static android.app.StatusBarManager.SESSION_KEYGUARD;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.UNKNOWN_LAST_WAKE_TIME;
+
import android.annotation.IntDef;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricFaceConstants;
@@ -27,7 +29,6 @@
import android.metrics.LogMaker;
import android.os.Handler;
import android.os.PowerManager;
-import android.os.SystemClock;
import android.os.Trace;
import androidx.annotation.Nullable;
@@ -62,6 +63,7 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.time.SystemClock;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -78,6 +80,7 @@
*/
@SysUISingleton
public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable {
+ private static final long RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS = 400L;
private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
@@ -169,9 +172,11 @@
private final MetricsLogger mMetricsLogger;
private final AuthController mAuthController;
private final StatusBarStateController mStatusBarStateController;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
private final LatencyTracker mLatencyTracker;
private final VibratorHelper mVibratorHelper;
private final BiometricUnlockLogger mLogger;
+ private final SystemClock mSystemClock;
private long mLastFpFailureUptimeMillis;
private int mNumConsecutiveFpFailures;
@@ -279,14 +284,17 @@
SessionTracker sessionTracker,
LatencyTracker latencyTracker,
ScreenOffAnimationController screenOffAnimationController,
- VibratorHelper vibrator) {
+ VibratorHelper vibrator,
+ SystemClock systemClock
+ ) {
mPowerManager = powerManager;
mShadeController = shadeController;
mUpdateMonitor = keyguardUpdateMonitor;
mUpdateMonitor.registerCallback(this);
mMediaManager = notificationMediaManager;
mLatencyTracker = latencyTracker;
- wakefulnessLifecycle.addObserver(mWakefulnessObserver);
+ mWakefulnessLifecycle = wakefulnessLifecycle;
+ mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
screenLifecycle.addObserver(mScreenObserver);
mNotificationShadeWindowController = notificationShadeWindowController;
@@ -306,6 +314,7 @@
mScreenOffAnimationController = screenOffAnimationController;
mVibratorHelper = vibrator;
mLogger = biometricUnlockLogger;
+ mSystemClock = systemClock;
dumpManager.registerDumpable(getClass().getName(), this);
}
@@ -429,8 +438,11 @@
Runnable wakeUp = ()-> {
if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
mLogger.i("bio wakelock: Authenticated, waking up...");
- mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_BIOMETRIC,
- "android.policy:BIOMETRIC");
+ mPowerManager.wakeUp(
+ mSystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_BIOMETRIC,
+ "android.policy:BIOMETRIC"
+ );
}
Trace.beginSection("release wake-and-unlock");
releaseBiometricWakeLock();
@@ -670,7 +682,7 @@
startWakeAndUnlock(MODE_ONLY_WAKE);
} else if (biometricSourceType == BiometricSourceType.FINGERPRINT
&& mUpdateMonitor.isUdfpsSupported()) {
- long currUptimeMillis = SystemClock.uptimeMillis();
+ long currUptimeMillis = mSystemClock.uptimeMillis();
if (currUptimeMillis - mLastFpFailureUptimeMillis < mConsecutiveFpFailureThreshold) {
mNumConsecutiveFpFailures += 1;
} else {
@@ -718,12 +730,26 @@
cleanup();
}
- //these haptics are for device-entry only
+ // these haptics are for device-entry only
private void vibrateSuccess(BiometricSourceType type) {
+ if (mAuthController.isSfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())
+ && lastWakeupFromPowerButtonWithinHapticThreshold()) {
+ mLogger.d("Skip auth success haptic. Power button was recently pressed.");
+ return;
+ }
mVibratorHelper.vibrateAuthSuccess(
getClass().getSimpleName() + ", type =" + type + "device-entry::success");
}
+ private boolean lastWakeupFromPowerButtonWithinHapticThreshold() {
+ final boolean lastWakeupFromPowerButton = mWakefulnessLifecycle.getLastWakeReason()
+ == PowerManager.WAKE_REASON_POWER_BUTTON;
+ return lastWakeupFromPowerButton
+ && mWakefulnessLifecycle.getLastWakeTime() != UNKNOWN_LAST_WAKE_TIME
+ && mSystemClock.uptimeMillis() - mWakefulnessLifecycle.getLastWakeTime()
+ < RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS;
+ }
+
private void vibrateError(BiometricSourceType type) {
mVibratorHelper.vibrateAuthError(
getClass().getSimpleName() + ", type =" + type + "device-entry::error");
@@ -816,7 +842,7 @@
if (mUpdateMonitor.isUdfpsSupported()) {
pw.print(" mNumConsecutiveFpFailures="); pw.println(mNumConsecutiveFpFailures);
pw.print(" time since last failure=");
- pw.println(SystemClock.uptimeMillis() - mLastFpFailureUptimeMillis);
+ pw.println(mSystemClock.uptimeMillis() - mLastFpFailureUptimeMillis);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 198572a..f1e1f42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -162,6 +162,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
import com.android.systemui.navigationbar.NavigationBarController;
@@ -485,6 +486,7 @@
private final ShadeController mShadeController;
private final InitController mInitController;
private final Lazy<CameraLauncher> mCameraLauncherLazy;
+ private final AlternateBouncerInteractor mAlternateBouncerInteractor;
private final PluginDependencyProvider mPluginDependencyProvider;
private final KeyguardDismissUtil mKeyguardDismissUtil;
@@ -763,7 +765,9 @@
WiredChargingRippleController wiredChargingRippleController,
IDreamManager dreamManager,
Lazy<CameraLauncher> cameraLauncherLazy,
- Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy) {
+ Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy,
+ AlternateBouncerInteractor alternateBouncerInteractor
+ ) {
mContext = context;
mNotificationsController = notificationsController;
mFragmentService = fragmentService;
@@ -841,6 +845,7 @@
mWallpaperManager = wallpaperManager;
mJankMonitor = jankMonitor;
mCameraLauncherLazy = cameraLauncherLazy;
+ mAlternateBouncerInteractor = alternateBouncerInteractor;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -3257,8 +3262,7 @@
private void showBouncerOrLockScreenIfKeyguard() {
// If the keyguard is animating away, we aren't really the keyguard anymore and should not
// show the bouncer/lockscreen.
- if (!mKeyguardViewMediator.isHiding()
- && !mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
+ if (!mKeyguardViewMediator.isHiding() && !mKeyguardUpdateMonitor.isKeyguardGoingAway()) {
if (mState == StatusBarState.SHADE_LOCKED) {
// shade is showing while locked on the keyguard, so go back to showing the
// lock screen where users can use the UDFPS affordance to enter the device
@@ -3737,7 +3741,7 @@
boolean launchingAffordanceWithPreview = mLaunchingAffordance;
mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
- if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
+ if (mAlternateBouncerInteractor.isVisibleState()) {
if (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
|| mTransitionToFullShadeProgress > 0f) {
mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
@@ -4262,8 +4266,7 @@
@Override
public void onDozeAmountChanged(float linear, float eased) {
- if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
- && !mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)
+ if (!mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)
&& !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
mLightRevealScrim.setRevealAmount(1f - linear);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index de7b152..0446cef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -44,10 +44,9 @@
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.tuner.TunerService;
@@ -82,7 +81,6 @@
private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
private final Resources mResources;
private final BatteryController mBatteryController;
- private final FeatureFlags mFeatureFlags;
private final ScreenOffAnimationController mScreenOffAnimationController;
private final FoldAodAnimationController mFoldAodAnimationController;
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@@ -125,7 +123,6 @@
BatteryController batteryController,
TunerService tunerService,
DumpManager dumpManager,
- FeatureFlags featureFlags,
ScreenOffAnimationController screenOffAnimationController,
Optional<SysUIUnfoldComponent> sysUiUnfoldComponent,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
@@ -141,7 +138,6 @@
mControlScreenOffAnimation = !getDisplayNeedsBlanking();
mPowerManager = powerManager;
mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation);
- mFeatureFlags = featureFlags;
mScreenOffAnimationController = screenOffAnimationController;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
@@ -162,6 +158,13 @@
SettingsObserver quickPickupSettingsObserver = new SettingsObserver(context, handler);
quickPickupSettingsObserver.observe();
+
+ batteryController.addCallback(new BatteryStateChangeCallback() {
+ @Override
+ public void onPowerSaveChanged(boolean isPowerSave) {
+ dispatchAlwaysOnEvent();
+ }
+ });
}
private void updateQuickPickupEnabled() {
@@ -300,13 +303,10 @@
/**
* Whether we're capable of controlling the screen off animation if we want to. This isn't
- * possible if AOD isn't even enabled or if the flag is disabled, or if the display needs
- * blanking.
+ * possible if AOD isn't even enabled or if the display needs blanking.
*/
public boolean canControlUnlockedScreenOff() {
- return getAlwaysOn()
- && mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
- && !getDisplayNeedsBlanking();
+ return getAlwaysOn() && !getDisplayNeedsBlanking();
}
/**
@@ -424,9 +424,7 @@
updateControlScreenOff();
}
- for (Callback callback : mCallbacks) {
- callback.onAlwaysOnChange();
- }
+ dispatchAlwaysOnEvent();
mScreenOffAnimationController.onAlwaysOnChanged(getAlwaysOn());
}
@@ -463,6 +461,12 @@
pw.print("isQuickPickupEnabled(): "); pw.println(isQuickPickupEnabled());
}
+ private void dispatchAlwaysOnEvent() {
+ for (Callback callback : mCallbacks) {
+ callback.onAlwaysOnChange();
+ }
+ }
+
private boolean getPostureSpecificBool(
int[] postureMapping,
boolean defaultSensorBool,
@@ -477,7 +481,8 @@
return bool;
}
- interface Callback {
+ /** Callbacks for doze parameter related information */
+ public interface Callback {
/**
* Invoked when the value of getAlwaysOn may have changed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 3483574..4ad3199 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -45,6 +45,7 @@
import com.android.systemui.animation.Interpolators;
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.CommandQueue;
@@ -76,6 +77,7 @@
/** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */
public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> {
+ private static final String TAG = "KeyguardStatusBarViewController";
private static final AnimationProperties KEYGUARD_HUN_PROPERTIES =
new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
@@ -422,7 +424,7 @@
/** Animate the keyguard status bar in. */
public void animateKeyguardStatusBarIn() {
- mLogger.d("animating status bar in");
+ mLogger.log(TAG, LogLevel.DEBUG, "animating status bar in");
if (mDisableStateTracker.isDisabled()) {
// If our view is disabled, don't allow us to animate in.
return;
@@ -438,7 +440,7 @@
/** Animate the keyguard status bar out. */
public void animateKeyguardStatusBarOut(long startDelay, long duration) {
- mLogger.d("animating status bar out");
+ mLogger.log(TAG, LogLevel.DEBUG, "animating status bar out");
ValueAnimator anim = ValueAnimator.ofFloat(mView.getAlpha(), 0f);
anim.addUpdateListener(mAnimatorUpdateListener);
anim.setStartDelay(startDelay);
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 d480fab..7d917bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -58,6 +58,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.data.BouncerView;
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.navigationbar.NavigationBarView;
@@ -134,6 +135,7 @@
private KeyguardMessageAreaController<AuthKeyguardMessageArea> mKeyguardMessageAreaController;
private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ private final AlternateBouncerInteractor mAlternateBouncerInteractor;
private final BouncerView mPrimaryBouncerView;
private final Lazy<ShadeController> mShadeController;
@@ -253,6 +255,7 @@
final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
private boolean mIsModernBouncerEnabled;
private boolean mIsUnoccludeTransitionFlagEnabled;
+ private boolean mIsModernAlternateBouncerEnabled;
private OnDismissAction mAfterKeyguardGoneAction;
private Runnable mKeyguardGoneCancelAction;
@@ -269,7 +272,7 @@
private final LatencyTracker mLatencyTracker;
private final KeyguardSecurityModel mKeyguardSecurityModel;
@Nullable private KeyguardBypassController mBypassController;
- @Nullable private AlternateBouncer mAlternateBouncer;
+ @Nullable private OccludingAppBiometricUI mOccludingAppBiometricUI;
private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
@@ -306,7 +309,8 @@
FeatureFlags featureFlags,
PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor,
PrimaryBouncerInteractor primaryBouncerInteractor,
- BouncerView primaryBouncerView) {
+ BouncerView primaryBouncerView,
+ AlternateBouncerInteractor alternateBouncerInteractor) {
mContext = context;
mViewMediatorCallback = callback;
mLockPatternUtils = lockPatternUtils;
@@ -331,6 +335,8 @@
.map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER);
mIsUnoccludeTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION);
+ mIsModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER);
+ mAlternateBouncerInteractor = alternateBouncerInteractor;
}
@Override
@@ -363,23 +369,51 @@
}
/**
- * Sets the given alt auth interceptor to null if it's the current auth interceptor. Else,
- * does nothing.
+ * Sets the given legacy alternate bouncer to null if it's the current alternate bouncer. Else,
+ * does nothing. Only used if modern alternate bouncer is NOT enabled.
*/
- public void removeAlternateAuthInterceptor(@NonNull AlternateBouncer authInterceptor) {
- if (Objects.equals(mAlternateBouncer, authInterceptor)) {
- mAlternateBouncer = null;
- hideAlternateBouncer(true);
+ public void removeLegacyAlternateBouncer(
+ @NonNull LegacyAlternateBouncer alternateBouncerLegacy) {
+ if (!mIsModernAlternateBouncerEnabled) {
+ if (Objects.equals(mAlternateBouncerInteractor.getLegacyAlternateBouncer(),
+ alternateBouncerLegacy)) {
+ mAlternateBouncerInteractor.setLegacyAlternateBouncer(null);
+ hideAlternateBouncer(true);
+ }
}
}
/**
- * Sets a new alt auth interceptor.
+ * Sets a new legacy alternate bouncer. Only used if mdoern alternate bouncer is NOT enable.
*/
- public void setAlternateBouncer(@NonNull AlternateBouncer authInterceptor) {
- if (!Objects.equals(mAlternateBouncer, authInterceptor)) {
- mAlternateBouncer = authInterceptor;
- hideAlternateBouncer(false);
+ public void setLegacyAlternateBouncer(@NonNull LegacyAlternateBouncer alternateBouncerLegacy) {
+ if (!mIsModernAlternateBouncerEnabled) {
+ if (!Objects.equals(mAlternateBouncerInteractor.getLegacyAlternateBouncer(),
+ alternateBouncerLegacy)) {
+ mAlternateBouncerInteractor.setLegacyAlternateBouncer(alternateBouncerLegacy);
+ hideAlternateBouncer(false);
+ }
+ }
+
+ }
+
+
+ /**
+ * Sets the given OccludingAppBiometricUI to null if it's the current auth interceptor. Else,
+ * does nothing.
+ */
+ public void removeOccludingAppBiometricUI(@NonNull OccludingAppBiometricUI biometricUI) {
+ if (Objects.equals(mOccludingAppBiometricUI, biometricUI)) {
+ mOccludingAppBiometricUI = null;
+ }
+ }
+
+ /**
+ * Sets a new OccludingAppBiometricUI.
+ */
+ public void setOccludingAppBiometricUI(@NonNull OccludingAppBiometricUI biometricUI) {
+ if (!Objects.equals(mOccludingAppBiometricUI, biometricUI)) {
+ mOccludingAppBiometricUI = biometricUI;
}
}
@@ -566,18 +600,11 @@
* {@see KeyguardBouncer#show(boolean, boolean)}
*/
public void showBouncer(boolean scrimmed) {
- if (canShowAlternateBouncer()) {
- updateAlternateBouncerShowing(mAlternateBouncer.showAlternateBouncer());
- return;
+ if (!mAlternateBouncerInteractor.show()) {
+ showPrimaryBouncer(scrimmed);
+ } else {
+ updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState());
}
-
- showPrimaryBouncer(scrimmed);
- }
-
- /** Whether we can show the alternate bouncer instead of the primary bouncer. */
- public boolean canShowAlternateBouncer() {
- return mAlternateBouncer != null
- && mKeyguardUpdateManager.isUnlockingWithBiometricAllowed(true);
}
/**
@@ -641,9 +668,9 @@
mKeyguardGoneCancelAction = cancelAction;
mDismissActionWillAnimateOnKeyguard = r != null && r.willRunAnimationOnKeyguard();
- // If there is an an alternate auth interceptor (like the UDFPS), show that one
+ // If there is an alternate auth interceptor (like the UDFPS), show that one
// instead of the bouncer.
- if (canShowAlternateBouncer()) {
+ if (mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()) {
if (!afterKeyguardGone) {
if (mPrimaryBouncer != null) {
mPrimaryBouncer.setDismissAction(mAfterKeyguardGoneAction,
@@ -656,7 +683,7 @@
mKeyguardGoneCancelAction = null;
}
- updateAlternateBouncerShowing(mAlternateBouncer.showAlternateBouncer());
+ updateAlternateBouncerShowing(mAlternateBouncerInteractor.show());
return;
}
@@ -725,10 +752,7 @@
@Override
public void hideAlternateBouncer(boolean forceUpdateScrim) {
- final boolean updateScrim = (mAlternateBouncer != null
- && mAlternateBouncer.hideAlternateBouncer())
- || forceUpdateScrim;
- updateAlternateBouncerShowing(updateScrim);
+ updateAlternateBouncerShowing(mAlternateBouncerInteractor.hide() || forceUpdateScrim);
}
private void updateAlternateBouncerShowing(boolean updateScrim) {
@@ -738,7 +762,7 @@
return;
}
- final boolean isShowingAlternateBouncer = isShowingAlternateBouncer();
+ final boolean isShowingAlternateBouncer = mAlternateBouncerInteractor.isVisibleState();
if (mKeyguardMessageAreaController != null) {
mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer);
mKeyguardMessageAreaController.setMessage("");
@@ -1095,7 +1119,7 @@
@Override
public boolean isBouncerShowing() {
- return primaryBouncerIsShowing() || isShowingAlternateBouncer();
+ return primaryBouncerIsShowing() || mAlternateBouncerInteractor.isVisibleState();
}
@Override
@@ -1339,7 +1363,7 @@
mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
}
- if (mAlternateBouncer != null && isShowingAlternateBouncer()) {
+ if (mAlternateBouncerInteractor.isVisibleState()) {
hideAlternateBouncer(false);
executeAfterKeyguardGoneAction();
}
@@ -1347,7 +1371,7 @@
/** Display security message to relevant KeyguardMessageArea. */
public void setKeyguardMessage(String message, ColorStateList colorState) {
- if (isShowingAlternateBouncer()) {
+ if (mAlternateBouncerInteractor.isVisibleState()) {
if (mKeyguardMessageAreaController != null) {
mKeyguardMessageAreaController.setMessage(message);
}
@@ -1421,6 +1445,7 @@
public void dump(PrintWriter pw) {
pw.println("StatusBarKeyguardViewManager:");
+ pw.println(" mIsModernAlternateBouncerEnabled: " + mIsModernAlternateBouncerEnabled);
pw.println(" mRemoteInputActive: " + mRemoteInputActive);
pw.println(" mDozing: " + mDozing);
pw.println(" mAfterKeyguardGoneAction: " + mAfterKeyguardGoneAction);
@@ -1438,9 +1463,9 @@
mPrimaryBouncer.dump(pw);
}
- if (mAlternateBouncer != null) {
- pw.println("AlternateBouncer:");
- mAlternateBouncer.dump(pw);
+ if (mOccludingAppBiometricUI != null) {
+ pw.println("mOccludingAppBiometricUI:");
+ mOccludingAppBiometricUI.dump(pw);
}
}
@@ -1492,14 +1517,17 @@
return mPrimaryBouncer;
}
- public boolean isShowingAlternateBouncer() {
- return mAlternateBouncer != null && mAlternateBouncer.isShowingAlternateBouncer();
- }
-
/**
- * Forward touches to callbacks.
+ * For any touches on the NPVC, show the primary bouncer if the alternate bouncer is currently
+ * showing.
*/
public void onTouch(MotionEvent event) {
+ if (mAlternateBouncerInteractor.isVisibleState()
+ && mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()) {
+ showPrimaryBouncer(true);
+ }
+
+ // Forward NPVC touches to callbacks in case they want to respond to touches
for (KeyguardViewManagerCallback callback: mCallbacks) {
callback.onTouch(event);
}
@@ -1542,8 +1570,8 @@
*/
public void requestFp(boolean request, int udfpsColor) {
mKeyguardUpdateManager.requestFingerprintAuthOnOccludingApp(request);
- if (mAlternateBouncer != null) {
- mAlternateBouncer.requestUdfps(request, udfpsColor);
+ if (mOccludingAppBiometricUI != null) {
+ mOccludingAppBiometricUI.requestUdfps(request, udfpsColor);
}
}
@@ -1614,10 +1642,9 @@
}
/**
- * Delegate used to send show and hide events to an alternate authentication method instead of
- * the regular pin/pattern/password bouncer.
+ * @Deprecated Delegate used to send show and hide events to an alternate bouncer.
*/
- public interface AlternateBouncer {
+ public interface LegacyAlternateBouncer {
/**
* Show alternate authentication bouncer.
* @return whether alternate auth method was newly shown
@@ -1634,7 +1661,13 @@
* @return true if the alternate auth bouncer is showing
*/
boolean isShowingAlternateBouncer();
+ }
+ /**
+ * Delegate used to send show and hide events to an alternate authentication method instead of
+ * the regular pin/pattern/password bouncer.
+ */
+ public interface OccludingAppBiometricUI {
/**
* Use when an app occluding the keyguard would like to give the user ability to
* unlock the device using udfps.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
index 6cd8c78..9e6bb20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.phone
+import android.view.InsetsFlags
import android.view.InsetsVisibilities
+import android.view.ViewDebug
import android.view.WindowInsetsController.Appearance
import android.view.WindowInsetsController.Behavior
import com.android.internal.statusbar.LetterboxDetails
@@ -148,4 +150,20 @@
) {
val letterboxesArray = letterboxes.toTypedArray()
val appearanceRegionsArray = appearanceRegions.toTypedArray()
+ override fun toString(): String {
+ val appearanceToString =
+ ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
+ return """SystemBarAttributesParams(
+ displayId=$displayId,
+ appearance=$appearanceToString,
+ appearanceRegions=$appearanceRegions,
+ navbarColorManagedByIme=$navbarColorManagedByIme,
+ behavior=$behavior,
+ requestedVisibilities=$requestedVisibilities,
+ packageName='$packageName',
+ letterboxes=$letterboxes,
+ letterboxesArray=${letterboxesArray.contentToString()},
+ appearanceRegionsArray=${appearanceRegionsArray.contentToString()}
+ )""".trimMargin()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 0e164e7..8ac1237 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -149,7 +149,7 @@
}
private fun createDemoMobileConnectionRepo(subId: Int): DemoMobileConnectionRepository {
- val tableLogBuffer = logFactory.create("DemoMobileConnectionLog [$subId]", 100)
+ val tableLogBuffer = logFactory.getOrCreate("DemoMobileConnectionLog [$subId]", 100)
return DemoMobileConnectionRepository(
subId,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 0fa0fea..4e42f9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -308,7 +308,7 @@
networkNameSeparator: String,
globalMobileDataSettingChangedEvent: Flow<Unit>,
): MobileConnectionRepository {
- val mobileLogger = logFactory.create(tableBufferLogName(subId), 100)
+ val mobileLogger = logFactory.getOrCreate(tableBufferLogName(subId), 100)
return MobileConnectionRepositoryImpl(
context,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index 68d30d3..2b4f51c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -60,7 +60,7 @@
* animation to and from the parent dialog.
*/
@JvmOverloads
- open fun onUserListItemClicked(
+ fun onUserListItemClicked(
record: UserRecord,
dialogShower: DialogShower? = null,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index c9ed0cb..f8c17e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -109,6 +109,8 @@
private static final long FOCUS_ANIMATION_FADE_IN_DELAY = 33;
private static final long FOCUS_ANIMATION_FADE_IN_DURATION = 83;
private static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f;
+ private static final long DEFOCUS_ANIMATION_FADE_OUT_DELAY = 120;
+ private static final long DEFOCUS_ANIMATION_CROSSFADE_DELAY = 180;
public final Object mToken = new Object();
@@ -421,7 +423,7 @@
}
@VisibleForTesting
- void onDefocus(boolean animate, boolean logClose) {
+ void onDefocus(boolean animate, boolean logClose, @Nullable Runnable doAfterDefocus) {
mController.removeRemoteInput(mEntry, mToken);
mEntry.remoteInputText = mEditText.getText();
@@ -431,18 +433,20 @@
ViewGroup parent = (ViewGroup) getParent();
if (animate && parent != null && mIsFocusAnimationFlagActive) {
-
ViewGroup grandParent = (ViewGroup) parent.getParent();
ViewGroupOverlay overlay = parent.getOverlay();
+ View actionsContainer = getActionsContainerLayout();
+ int actionsContainerHeight =
+ actionsContainer != null ? actionsContainer.getHeight() : 0;
// After adding this RemoteInputView to the overlay of the parent (and thus removing
// it from the parent itself), the parent will shrink in height. This causes the
// overlay to be moved. To correct the position of the overlay we need to offset it.
- int overlayOffsetY = getMaxSiblingHeight() - getHeight();
+ int overlayOffsetY = actionsContainerHeight - getHeight();
overlay.add(this);
if (grandParent != null) grandParent.setClipChildren(false);
- Animator animator = getDefocusAnimator(overlayOffsetY);
+ Animator animator = getDefocusAnimator(actionsContainer, overlayOffsetY);
View self = this;
animator.addListener(new AnimatorListenerAdapter() {
@Override
@@ -454,8 +458,12 @@
if (mWrapper != null) {
mWrapper.setRemoteInputVisible(false);
}
+ if (doAfterDefocus != null) {
+ doAfterDefocus.run();
+ }
}
});
+ if (actionsContainer != null) actionsContainer.setAlpha(0f);
animator.start();
} else if (animate && mRevealParams != null && mRevealParams.radius > 0) {
@@ -474,6 +482,7 @@
reveal.start();
} else {
setVisibility(GONE);
+ if (doAfterDefocus != null) doAfterDefocus.run();
if (mWrapper != null) {
mWrapper.setRemoteInputVisible(false);
}
@@ -596,10 +605,8 @@
/**
* Focuses the RemoteInputView and animates its appearance
- *
- * @param crossFadeView view that will be crossfaded during the appearance animation
*/
- public void focusAnimated(View crossFadeView) {
+ public void focusAnimated() {
if (!mIsFocusAnimationFlagActive && getVisibility() != VISIBLE
&& mRevealParams != null) {
android.animation.Animator animator = mRevealParams.createCircularRevealAnimator(this);
@@ -609,7 +616,7 @@
} else if (mIsFocusAnimationFlagActive && getVisibility() != VISIBLE) {
mIsAnimatingAppearance = true;
setAlpha(0f);
- Animator focusAnimator = getFocusAnimator(crossFadeView);
+ Animator focusAnimator = getFocusAnimator(getActionsContainerLayout());
focusAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
@@ -661,6 +668,23 @@
}
private void reset() {
+ if (mIsFocusAnimationFlagActive) {
+ mProgressBar.setVisibility(INVISIBLE);
+ mResetting = true;
+ mSending = false;
+ onDefocus(true /* animate */, false /* logClose */, () -> {
+ mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
+ mEditText.getText().clear();
+ mEditText.setEnabled(isAggregatedVisible());
+ mSendButton.setVisibility(VISIBLE);
+ mController.removeSpinning(mEntry.getKey(), mToken);
+ updateSendButton();
+ setAttachment(null);
+ mResetting = false;
+ });
+ return;
+ }
+
mResetting = true;
mSending = false;
mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
@@ -671,7 +695,7 @@
mProgressBar.setVisibility(INVISIBLE);
mController.removeSpinning(mEntry.getKey(), mToken);
updateSendButton();
- onDefocus(false /* animate */, false /* logClose */);
+ onDefocus(false /* animate */, false /* logClose */, null /* doAfterDefocus */);
setAttachment(null);
mResetting = false;
@@ -825,23 +849,22 @@
}
/**
- * @return max sibling height (0 in case of no siblings)
+ * @return action button container view (i.e. ViewGroup containing Reply button etc.)
*/
- public int getMaxSiblingHeight() {
+ public View getActionsContainerLayout() {
ViewGroup parentView = (ViewGroup) getParent();
- int maxHeight = 0;
- if (parentView == null) return 0;
- for (int i = 0; i < parentView.getChildCount(); i++) {
- View siblingView = parentView.getChildAt(i);
- if (siblingView != this) maxHeight = Math.max(maxHeight, siblingView.getHeight());
- }
- return maxHeight;
+ if (parentView == null) return null;
+ return parentView.findViewById(com.android.internal.R.id.actions_container_layout);
}
/**
* Creates an animator for the focus animation.
+ *
+ * @param fadeOutView View that will be faded out during the focus animation.
*/
- private Animator getFocusAnimator(View crossFadeView) {
+ private Animator getFocusAnimator(@Nullable View fadeOutView) {
+ final AnimatorSet animatorSet = new AnimatorSet();
+
final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f);
alphaAnimator.setStartDelay(FOCUS_ANIMATION_FADE_IN_DELAY);
alphaAnimator.setDuration(FOCUS_ANIMATION_FADE_IN_DURATION);
@@ -854,30 +877,36 @@
scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
- final Animator crossFadeViewAlphaAnimator =
- ObjectAnimator.ofFloat(crossFadeView, View.ALPHA, 1f, 0f);
- crossFadeViewAlphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION);
- crossFadeViewAlphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
- alphaAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation, boolean isReverse) {
- crossFadeView.setAlpha(1f);
- }
- });
-
- final AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(alphaAnimator, scaleAnimator, crossFadeViewAlphaAnimator);
+ if (fadeOutView == null) {
+ animatorSet.playTogether(alphaAnimator, scaleAnimator);
+ } else {
+ final Animator fadeOutViewAlphaAnimator =
+ ObjectAnimator.ofFloat(fadeOutView, View.ALPHA, 1f, 0f);
+ fadeOutViewAlphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION);
+ fadeOutViewAlphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation, boolean isReverse) {
+ fadeOutView.setAlpha(1f);
+ }
+ });
+ animatorSet.playTogether(alphaAnimator, scaleAnimator, fadeOutViewAlphaAnimator);
+ }
return animatorSet;
}
/**
* Creates an animator for the defocus animation.
*
- * @param offsetY The RemoteInputView will be offset by offsetY during the animation
+ * @param fadeInView View that will be faded in during the defocus animation.
+ * @param offsetY The RemoteInputView will be offset by offsetY during the animation
*/
- private Animator getDefocusAnimator(int offsetY) {
+ private Animator getDefocusAnimator(@Nullable View fadeInView, int offsetY) {
+ final AnimatorSet animatorSet = new AnimatorSet();
+
final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f);
- alphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION);
+ alphaAnimator.setDuration(FOCUS_ANIMATION_FADE_IN_DURATION);
+ alphaAnimator.setStartDelay(DEFOCUS_ANIMATION_FADE_OUT_DELAY);
alphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1f, FOCUS_ANIMATION_MIN_SCALE);
@@ -893,8 +922,17 @@
}
});
- final AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(alphaAnimator, scaleAnimator);
+ if (fadeInView == null) {
+ animatorSet.playTogether(alphaAnimator, scaleAnimator);
+ } else {
+ fadeInView.forceHasOverlappingRendering(false);
+ Animator fadeInViewAlphaAnimator =
+ ObjectAnimator.ofFloat(fadeInView, View.ALPHA, 0f, 1f);
+ fadeInViewAlphaAnimator.setDuration(FOCUS_ANIMATION_FADE_IN_DURATION);
+ fadeInViewAlphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
+ fadeInViewAlphaAnimator.setStartDelay(DEFOCUS_ANIMATION_CROSSFADE_DELAY);
+ animatorSet.playTogether(alphaAnimator, scaleAnimator, fadeInViewAlphaAnimator);
+ }
return animatorSet;
}
@@ -1011,7 +1049,8 @@
if (isFocusable() && isEnabled()) {
setInnerFocusable(false);
if (mRemoteInputView != null) {
- mRemoteInputView.onDefocus(animate, true /* logClose */);
+ mRemoteInputView
+ .onDefocus(animate, true /* logClose */, null /* doAfterDefocus */);
}
mShowImeOnInputConnection = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusFirstUsageListener.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusFirstUsageListener.kt
deleted file mode 100644
index 154c6e2..0000000
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusFirstUsageListener.kt
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.stylus
-
-import android.content.Context
-import android.hardware.BatteryState
-import android.hardware.input.InputManager
-import android.os.Handler
-import android.util.Log
-import android.view.InputDevice
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-/**
- * A listener that detects when a stylus has first been used, by detecting 1) the presence of an
- * internal SOURCE_STYLUS with a battery, or 2) any added SOURCE_STYLUS device with a bluetooth
- * address.
- */
-@SysUISingleton
-class StylusFirstUsageListener
-@Inject
-constructor(
- private val context: Context,
- private val inputManager: InputManager,
- private val stylusManager: StylusManager,
- private val featureFlags: FeatureFlags,
- @Background private val executor: Executor,
- @Background private val handler: Handler,
-) : CoreStartable, StylusManager.StylusCallback, InputManager.InputDeviceBatteryListener {
-
- // Set must be only accessed from the background handler, which is the same handler that
- // runs the StylusManager callbacks.
- private val internalStylusDeviceIds: MutableSet<Int> = mutableSetOf()
- @VisibleForTesting var hasStarted = false
-
- override fun start() {
- if (true) return // TODO(b/261826950): remove on main
- if (hasStarted) return
- if (!featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)) return
- if (inputManager.isStylusEverUsed(context)) return
- if (!hostDeviceSupportsStylusInput()) return
-
- hasStarted = true
- inputManager.inputDeviceIds.forEach(this::onStylusAdded)
- stylusManager.registerCallback(this)
- stylusManager.startListener()
- }
-
- override fun onStylusAdded(deviceId: Int) {
- if (!hasStarted) return
-
- val device = inputManager.getInputDevice(deviceId) ?: return
- if (device.isExternal || !device.supportsSource(InputDevice.SOURCE_STYLUS)) return
-
- try {
- inputManager.addInputDeviceBatteryListener(deviceId, executor, this)
- internalStylusDeviceIds += deviceId
- } catch (e: SecurityException) {
- Log.e(TAG, "$e: Failed to register battery listener for $deviceId ${device.name}.")
- }
- }
-
- override fun onStylusRemoved(deviceId: Int) {
- if (!hasStarted) return
-
- if (!internalStylusDeviceIds.contains(deviceId)) return
- try {
- inputManager.removeInputDeviceBatteryListener(deviceId, this)
- internalStylusDeviceIds.remove(deviceId)
- } catch (e: SecurityException) {
- Log.e(TAG, "$e: Failed to remove registered battery listener for $deviceId.")
- }
- }
-
- override fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {
- if (!hasStarted) return
-
- onRemoteDeviceFound()
- }
-
- override fun onBatteryStateChanged(
- deviceId: Int,
- eventTimeMillis: Long,
- batteryState: BatteryState
- ) {
- if (!hasStarted) return
-
- if (batteryState.isPresent) {
- onRemoteDeviceFound()
- }
- }
-
- private fun onRemoteDeviceFound() {
- inputManager.setStylusEverUsed(context, true)
- cleanupListeners()
- }
-
- private fun cleanupListeners() {
- stylusManager.unregisterCallback(this)
- handler.post {
- internalStylusDeviceIds.forEach {
- inputManager.removeInputDeviceBatteryListener(it, this)
- }
- }
- }
-
- private fun hostDeviceSupportsStylusInput(): Boolean {
- return inputManager.inputDeviceIds
- .asSequence()
- .mapNotNull { inputManager.getInputDevice(it) }
- .any { it.supportsSource(InputDevice.SOURCE_STYLUS) && !it.isExternal }
- }
-
- companion object {
- private val TAG = StylusFirstUsageListener::class.simpleName.orEmpty()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 302d6a9..235495cf 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -18,6 +18,8 @@
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
+import android.content.Context
+import android.hardware.BatteryState
import android.hardware.input.InputManager
import android.os.Handler
import android.util.ArrayMap
@@ -25,6 +27,8 @@
import android.view.InputDevice
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -37,25 +41,37 @@
class StylusManager
@Inject
constructor(
+ private val context: Context,
private val inputManager: InputManager,
private val bluetoothAdapter: BluetoothAdapter?,
@Background private val handler: Handler,
@Background private val executor: Executor,
-) : InputManager.InputDeviceListener, BluetoothAdapter.OnMetadataChangedListener {
+ private val featureFlags: FeatureFlags,
+) :
+ InputManager.InputDeviceListener,
+ InputManager.InputDeviceBatteryListener,
+ BluetoothAdapter.OnMetadataChangedListener {
private val stylusCallbacks: CopyOnWriteArrayList<StylusCallback> = CopyOnWriteArrayList()
private val stylusBatteryCallbacks: CopyOnWriteArrayList<StylusBatteryCallback> =
CopyOnWriteArrayList()
// This map should only be accessed on the handler
private val inputDeviceAddressMap: MutableMap<Int, String?> = ArrayMap()
+ // This variable should only be accessed on the handler
+ private var hasStarted: Boolean = false
/**
* Starts listening to InputManager InputDevice events. Will also load the InputManager snapshot
* at time of starting.
*/
fun startListener() {
- addExistingStylusToMap()
- inputManager.registerInputDeviceListener(this, handler)
+ handler.post {
+ if (hasStarted) return@post
+ hasStarted = true
+ addExistingStylusToMap()
+
+ inputManager.registerInputDeviceListener(this, handler)
+ }
}
/** Registers a StylusCallback to listen to stylus events. */
@@ -77,21 +93,30 @@
}
override fun onInputDeviceAdded(deviceId: Int) {
+ if (!hasStarted) return
+
val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
+ if (!device.isExternal) {
+ registerBatteryListener(deviceId)
+ }
+
// TODO(b/257936830): get address once input api available
val btAddress: String? = null
inputDeviceAddressMap[deviceId] = btAddress
executeStylusCallbacks { cb -> cb.onStylusAdded(deviceId) }
if (btAddress != null) {
+ onStylusUsed()
onStylusBluetoothConnected(btAddress)
executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, btAddress) }
}
}
override fun onInputDeviceChanged(deviceId: Int) {
+ if (!hasStarted) return
+
val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
@@ -112,7 +137,10 @@
}
override fun onInputDeviceRemoved(deviceId: Int) {
+ if (!hasStarted) return
+
if (!inputDeviceAddressMap.contains(deviceId)) return
+ unregisterBatteryListener(deviceId)
val btAddress: String? = inputDeviceAddressMap[deviceId]
inputDeviceAddressMap.remove(deviceId)
@@ -124,13 +152,14 @@
}
override fun onMetadataChanged(device: BluetoothDevice, key: Int, value: ByteArray?) {
- handler.post executeMetadataChanged@{
- if (key != BluetoothDevice.METADATA_MAIN_CHARGING || value == null)
- return@executeMetadataChanged
+ handler.post {
+ if (!hasStarted) return@post
+
+ if (key != BluetoothDevice.METADATA_MAIN_CHARGING || value == null) return@post
val inputDeviceId: Int =
inputDeviceAddressMap.filterValues { it == device.address }.keys.firstOrNull()
- ?: return@executeMetadataChanged
+ ?: return@post
val isCharging = String(value) == "true"
@@ -140,6 +169,24 @@
}
}
+ override fun onBatteryStateChanged(
+ deviceId: Int,
+ eventTimeMillis: Long,
+ batteryState: BatteryState
+ ) {
+ handler.post {
+ if (!hasStarted) return@post
+
+ if (batteryState.isPresent) {
+ onStylusUsed()
+ }
+
+ executeStylusBatteryCallbacks { cb ->
+ cb.onStylusUsiBatteryStateChanged(deviceId, eventTimeMillis, batteryState)
+ }
+ }
+ }
+
private fun onStylusBluetoothConnected(btAddress: String) {
val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
try {
@@ -158,6 +205,21 @@
}
}
+ /**
+ * An InputDevice that supports [InputDevice.SOURCE_STYLUS] may still be present even when a
+ * physical stylus device has never been used. This method is run when 1) a USI stylus battery
+ * event happens, or 2) a bluetooth stylus is connected, as they are both indicators that a
+ * physical stylus device has actually been used.
+ */
+ private fun onStylusUsed() {
+ if (true) return // TODO(b/261826950): remove on main
+ if (!featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)) return
+ if (inputManager.isStylusEverUsed(context)) return
+
+ inputManager.setStylusEverUsed(context, true)
+ executeStylusCallbacks { cb -> cb.onStylusFirstUsed() }
+ }
+
private fun executeStylusCallbacks(run: (cb: StylusCallback) -> Unit) {
stylusCallbacks.forEach(run)
}
@@ -166,31 +228,69 @@
stylusBatteryCallbacks.forEach(run)
}
+ private fun registerBatteryListener(deviceId: Int) {
+ try {
+ inputManager.addInputDeviceBatteryListener(deviceId, executor, this)
+ } catch (e: SecurityException) {
+ Log.e(TAG, "$e: Failed to register battery listener for $deviceId.")
+ }
+ }
+
+ private fun unregisterBatteryListener(deviceId: Int) {
+ // If deviceId wasn't registered, the result is a no-op, so an "is registered"
+ // check is not needed.
+ try {
+ inputManager.removeInputDeviceBatteryListener(deviceId, this)
+ } catch (e: SecurityException) {
+ Log.e(TAG, "$e: Failed to remove registered battery listener for $deviceId.")
+ }
+ }
+
private fun addExistingStylusToMap() {
for (deviceId: Int in inputManager.inputDeviceIds) {
val device: InputDevice = inputManager.getInputDevice(deviceId) ?: continue
if (device.supportsSource(InputDevice.SOURCE_STYLUS)) {
// TODO(b/257936830): get address once input api available
inputDeviceAddressMap[deviceId] = null
+
+ if (!device.isExternal) { // TODO(b/263556967): add supportsUsi check once available
+ // For most devices, an active (non-bluetooth) stylus is represented by an
+ // internal InputDevice. This InputDevice will be present in InputManager
+ // before CoreStartables run, and will not be removed.
+ // In many cases, it reports the battery level of the stylus.
+ registerBatteryListener(deviceId)
+ }
}
}
}
- /** Callback interface to receive events from the StylusManager. */
+ /**
+ * Callback interface to receive events from the StylusManager. All callbacks are run on the
+ * same background handler.
+ */
interface StylusCallback {
fun onStylusAdded(deviceId: Int) {}
fun onStylusRemoved(deviceId: Int) {}
fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {}
fun onStylusBluetoothDisconnected(deviceId: Int, btAddress: String) {}
+ fun onStylusFirstUsed() {}
}
- /** Callback interface to receive stylus battery events from the StylusManager. */
+ /**
+ * Callback interface to receive stylus battery events from the StylusManager. All callbacks are
+ * runs on the same background handler.
+ */
interface StylusBatteryCallback {
fun onStylusBluetoothChargingStateChanged(
inputDeviceId: Int,
btDevice: BluetoothDevice,
isCharging: Boolean
) {}
+ fun onStylusUsiBatteryStateChanged(
+ deviceId: Int,
+ eventTimeMillis: Long,
+ batteryState: BatteryState,
+ ) {}
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt
index 11233dd..5a8850a 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt
@@ -18,14 +18,11 @@
import android.hardware.BatteryState
import android.hardware.input.InputManager
-import android.util.Log
import android.view.InputDevice
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import java.util.concurrent.Executor
import javax.inject.Inject
/**
@@ -40,16 +37,7 @@
private val inputManager: InputManager,
private val stylusUsiPowerUi: StylusUsiPowerUI,
private val featureFlags: FeatureFlags,
- @Background private val executor: Executor,
-) : CoreStartable, StylusManager.StylusCallback, InputManager.InputDeviceBatteryListener {
-
- override fun onStylusAdded(deviceId: Int) {
- val device = inputManager.getInputDevice(deviceId) ?: return
-
- if (!device.isExternal) {
- registerBatteryListener(deviceId)
- }
- }
+) : CoreStartable, StylusManager.StylusCallback, StylusManager.StylusBatteryCallback {
override fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {
stylusUsiPowerUi.refresh()
@@ -59,57 +47,30 @@
stylusUsiPowerUi.refresh()
}
- override fun onStylusRemoved(deviceId: Int) {
- val device = inputManager.getInputDevice(deviceId) ?: return
-
- if (!device.isExternal) {
- unregisterBatteryListener(deviceId)
- }
- }
-
- override fun onBatteryStateChanged(
+ override fun onStylusUsiBatteryStateChanged(
deviceId: Int,
eventTimeMillis: Long,
batteryState: BatteryState
) {
- if (batteryState.isPresent) {
- stylusUsiPowerUi.updateBatteryState(batteryState)
- }
- }
-
- private fun registerBatteryListener(deviceId: Int) {
- try {
- inputManager.addInputDeviceBatteryListener(deviceId, executor, this)
- } catch (e: SecurityException) {
- Log.e(TAG, "$e: Failed to register battery listener for $deviceId.")
- }
- }
-
- private fun unregisterBatteryListener(deviceId: Int) {
- try {
- inputManager.removeInputDeviceBatteryListener(deviceId, this)
- } catch (e: SecurityException) {
- Log.e(TAG, "$e: Failed to unregister battery listener for $deviceId.")
+ if (batteryState.isPresent && batteryState.capacity > 0f) {
+ stylusUsiPowerUi.updateBatteryState(deviceId, batteryState)
}
}
override fun start() {
if (!featureFlags.isEnabled(Flags.ENABLE_USI_BATTERY_NOTIFICATIONS)) return
- addBatteryListenerForInternalStyluses()
+ if (!hostDeviceSupportsStylusInput()) return
+ stylusUsiPowerUi.init()
stylusManager.registerCallback(this)
stylusManager.startListener()
}
- private fun addBatteryListenerForInternalStyluses() {
- // For most devices, an active stylus is represented by an internal InputDevice.
- // This InputDevice will be present in InputManager before CoreStartables run,
- // and will not be removed. In many cases, it reports the battery level of the stylus.
- inputManager.inputDeviceIds
+ private fun hostDeviceSupportsStylusInput(): Boolean {
+ return inputManager.inputDeviceIds
.asSequence()
.mapNotNull { inputManager.getInputDevice(it) }
- .filter { it.supportsSource(InputDevice.SOURCE_STYLUS) }
- .forEach { onStylusAdded(it.id) }
+ .any { it.supportsSource(InputDevice.SOURCE_STYLUS) && !it.isExternal }
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
index 70a5b36..8d5e01c 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
@@ -18,17 +18,21 @@
import android.Manifest
import android.app.PendingIntent
+import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.hardware.BatteryState
import android.hardware.input.InputManager
+import android.os.Bundle
import android.os.Handler
import android.os.UserHandle
+import android.util.Log
import android.view.InputDevice
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -53,6 +57,7 @@
// These values must only be accessed on the handler.
private var batteryCapacity = 1.0f
private var suppressed = false
+ private var inputDeviceId: Int? = null
fun init() {
val filter =
@@ -87,10 +92,12 @@
}
}
- fun updateBatteryState(batteryState: BatteryState) {
+ fun updateBatteryState(deviceId: Int, batteryState: BatteryState) {
handler.post updateBattery@{
- if (batteryState.capacity == batteryCapacity) return@updateBattery
+ if (batteryState.capacity == batteryCapacity || batteryState.capacity <= 0f)
+ return@updateBattery
+ inputDeviceId = deviceId
batteryCapacity = batteryState.capacity
refresh()
}
@@ -123,13 +130,13 @@
.setSmallIcon(R.drawable.ic_power_low)
.setDeleteIntent(getPendingBroadcast(ACTION_DISMISSED_LOW_BATTERY))
.setContentIntent(getPendingBroadcast(ACTION_CLICKED_LOW_BATTERY))
- .setContentTitle(context.getString(R.string.stylus_battery_low))
- .setContentText(
+ .setContentTitle(
context.getString(
- R.string.battery_low_percent_format,
+ R.string.stylus_battery_low_percentage,
NumberFormat.getPercentInstance().format(batteryCapacity)
)
)
+ .setContentText(context.getString(R.string.stylus_battery_low_subtitle))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setLocalOnly(true)
.setAutoCancel(true)
@@ -150,23 +157,41 @@
}
private fun getPendingBroadcast(action: String): PendingIntent? {
- return PendingIntent.getBroadcastAsUser(
+ return PendingIntent.getBroadcast(
context,
0,
- Intent(action),
+ Intent(action).setPackage(context.packageName),
PendingIntent.FLAG_IMMUTABLE,
- UserHandle.CURRENT
)
}
- private val receiver: BroadcastReceiver =
+ @VisibleForTesting
+ internal val receiver: BroadcastReceiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
ACTION_DISMISSED_LOW_BATTERY -> updateSuppression(true)
ACTION_CLICKED_LOW_BATTERY -> {
updateSuppression(true)
- // TODO(b/261584943): open USI device details page
+ if (inputDeviceId == null) return
+
+ val args = Bundle()
+ args.putInt(KEY_DEVICE_INPUT_ID, inputDeviceId!!)
+ try {
+ context.startActivity(
+ Intent(ACTION_STYLUS_USI_DETAILS)
+ .putExtra(KEY_SETTINGS_FRAGMENT_ARGS, args)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ )
+ } catch (e: ActivityNotFoundException) {
+ // In the rare scenario where the Settings app manifest doesn't contain
+ // the USI details activity, ignore the intent.
+ Log.e(
+ StylusUsiPowerUI::class.java.simpleName,
+ "Cannot open USI details page."
+ )
+ }
}
}
}
@@ -177,9 +202,13 @@
// https://source.chromium.org/chromium/chromium/src/+/main:ash/system/power/peripheral_battery_notifier.cc;l=41
private const val LOW_BATTERY_THRESHOLD = 0.16f
- private val USI_NOTIFICATION_ID = R.string.stylus_battery_low
+ private val USI_NOTIFICATION_ID = R.string.stylus_battery_low_percentage
- private const val ACTION_DISMISSED_LOW_BATTERY = "StylusUsiPowerUI.dismiss"
- private const val ACTION_CLICKED_LOW_BATTERY = "StylusUsiPowerUI.click"
+ @VisibleForTesting const val ACTION_DISMISSED_LOW_BATTERY = "StylusUsiPowerUI.dismiss"
+ @VisibleForTesting const val ACTION_CLICKED_LOW_BATTERY = "StylusUsiPowerUI.click"
+ @VisibleForTesting
+ const val ACTION_STYLUS_USI_DETAILS = "com.android.settings.STYLUS_USI_DETAILS_SETTINGS"
+ @VisibleForTesting const val KEY_DEVICE_INPUT_ID = "device_input_id"
+ @VisibleForTesting const val KEY_SETTINGS_FRAGMENT_ARGS = ":settings:show_fragment_args"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 59ad24a..2709da3 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -17,6 +17,9 @@
package com.android.systemui.unfold
import android.content.Context
+import android.hardware.devicestate.DeviceStateManager
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.LifecycleScreenStatusProvider
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.system.SystemUnfoldSharedModule
@@ -32,6 +35,7 @@
import dagger.Module
import dagger.Provides
import java.util.Optional
+import java.util.concurrent.Executor
import javax.inject.Named
import javax.inject.Singleton
@@ -40,6 +44,20 @@
@Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui"
+ /** A globally available FoldStateListener that allows one to query the fold state. */
+ @Provides
+ @Singleton
+ fun providesFoldStateListener(
+ deviceStateManager: DeviceStateManager,
+ @Application context: Context,
+ @Main executor: Executor
+ ): DeviceStateManager.FoldStateListener {
+ val listener = DeviceStateManager.FoldStateListener(context)
+ deviceStateManager.registerCallback(executor, listener)
+
+ return listener
+ }
+
@Provides
@Singleton
fun providesFoldStateLoggingProvider(
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index d7b0971..c0ba3cc 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -115,9 +115,7 @@
private val callbackMutex = Mutex()
private val callbacks = mutableSetOf<UserCallback>()
private val userInfos: Flow<List<UserInfo>> =
- repository.userInfos.map { userInfos ->
- userInfos.filter { it.isFull }
- }
+ repository.userInfos.map { userInfos -> userInfos.filter { it.isFull } }
/** List of current on-device users to select from. */
val users: Flow<List<UserModel>>
@@ -445,7 +443,8 @@
)
)
}
- UserActionModel.ADD_SUPERVISED_USER ->
+ UserActionModel.ADD_SUPERVISED_USER -> {
+ dismissDialog()
activityStarter.startActivity(
Intent()
.setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
@@ -453,6 +452,7 @@
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
/* dismissShade= */ true,
)
+ }
UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
activityStarter.startActivity(
Intent(Settings.ACTION_USER_SETTINGS),
@@ -493,7 +493,7 @@
fun showUserSwitcher(context: Context, expandable: Expandable) {
if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
- showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog)
+ showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable))
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
index 85c2964..14cc3e7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -18,11 +18,13 @@
package com.android.systemui.user.domain.model
import android.os.UserHandle
+import com.android.systemui.animation.Expandable
import com.android.systemui.qs.user.UserSwitchDialogController
/** Encapsulates a request to show a dialog. */
sealed class ShowDialogRequestModel(
open val dialogShower: UserSwitchDialogController.DialogShower? = null,
+ open val expandable: Expandable? = null,
) {
data class ShowAddUserDialog(
val userHandle: UserHandle,
@@ -45,5 +47,7 @@
) : ShowDialogRequestModel(dialogShower)
/** Show the user switcher dialog */
- object ShowUserSwitcherDialog : ShowDialogRequestModel()
+ data class ShowUserSwitcherDialog(
+ override val expandable: Expandable?,
+ ) : ShowDialogRequestModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/DialogShowerImpl.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/DialogShowerImpl.kt
new file mode 100644
index 0000000..3fe2a7b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/DialogShowerImpl.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.user.ui.dialog
+
+import android.app.Dialog
+import android.content.DialogInterface
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
+
+/** Extracted from [UserSwitchDialogController] */
+class DialogShowerImpl(
+ private val animateFrom: Dialog,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+) : DialogInterface by animateFrom, DialogShower {
+ override fun showDialog(dialog: Dialog, cuj: DialogCuj) {
+ dialogLaunchAnimator.showFromDialog(dialog, animateFrom = animateFrom, cuj)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
index ed25898..b8ae257 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
@@ -60,6 +60,7 @@
setView(gridFrame)
adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
+ adapter.injectDialogShower(DialogShowerImpl(this, dialogLaunchAnimator))
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 4141054..79721b3 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -66,12 +66,6 @@
private fun startHandlingDialogShowRequests() {
applicationScope.get().launch {
interactor.get().dialogShowRequests.filterNotNull().collect { request ->
- currentDialog?.let {
- if (it.isShowing) {
- it.cancel()
- }
- }
-
val (dialog, dialogCuj) =
when (request) {
is ShowDialogRequestModel.ShowAddUserDialog ->
@@ -133,7 +127,10 @@
}
currentDialog = dialog
- if (request.dialogShower != null && dialogCuj != null) {
+ val controller = request.expandable?.dialogLaunchController(dialogCuj)
+ if (controller != null) {
+ dialogLaunchAnimator.get().show(dialog, controller)
+ } else if (request.dialogShower != null && dialogCuj != null) {
request.dialogShower?.showDialog(dialog, dialogCuj)
} else {
dialog.show()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index db1853d..52eef42 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1487,6 +1487,7 @@
.setDuration(mDialogHideAnimationDurationMs)
.setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
.withEndAction(() -> mHandler.postDelayed(() -> {
+ mController.notifyVisible(false);
mDialog.dismiss();
tryToRemoveCaptionsTooltip();
mIsAnimatingDismiss = false;
@@ -1497,7 +1498,6 @@
animator.setListener(getJankListener(getDialogView(), TYPE_DISMISS,
mDialogHideAnimationDurationMs)).start();
checkODICaptionsTooltip(true);
- mController.notifyVisible(false);
synchronized (mSafetyWarningLock) {
if (mSafetyWarning != null) {
if (D.BUG) Log.d(TAG, "SafetyWarning dismissed");
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index e8f8e25..c76b127 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.keyguard
+import com.android.systemui.statusbar.CommandQueue
import android.content.BroadcastReceiver
import android.testing.AndroidTestingRunner
import android.view.View
@@ -81,8 +82,10 @@
@Mock private lateinit var largeClockEvents: ClockFaceEvents
@Mock private lateinit var parentView: View
@Mock private lateinit var transitionRepository: KeyguardTransitionRepository
+ @Mock private lateinit var commandQueue: CommandQueue
private lateinit var repository: FakeKeyguardRepository
- @Mock private lateinit var logBuffer: LogBuffer
+ @Mock private lateinit var smallLogBuffer: LogBuffer
+ @Mock private lateinit var largeLogBuffer: LogBuffer
private lateinit var underTest: ClockEventController
@Before
@@ -99,7 +102,7 @@
repository = FakeKeyguardRepository()
underTest = ClockEventController(
- KeyguardInteractor(repository = repository),
+ KeyguardInteractor(repository = repository, commandQueue = commandQueue),
KeyguardTransitionInteractor(repository = transitionRepository),
broadcastDispatcher,
batteryController,
@@ -109,7 +112,8 @@
context,
mainExecutor,
bgExecutor,
- logBuffer,
+ smallLogBuffer,
+ largeLogBuffer,
featureFlags
)
underTest.clock = clock
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index c8e7538..9a9acf3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -48,6 +48,7 @@
import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.ClockEvents;
import com.android.systemui.plugins.ClockFaceController;
+import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.shared.clocks.ClockRegistry;
@@ -115,6 +116,8 @@
private FrameLayout mLargeClockFrame;
@Mock
private SecureSettings mSecureSettings;
+ @Mock
+ private LogBuffer mLogBuffer;
private final View mFakeSmartspaceView = new View(mContext);
@@ -156,7 +159,8 @@
mSecureSettings,
mExecutor,
mDumpManager,
- mClockEventController
+ mClockEventController,
+ mLogBuffer
);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 254f953..8dc1e8f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
@@ -189,6 +190,7 @@
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE);
}
@Test
@@ -198,6 +200,7 @@
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE);
}
@Test
@@ -212,6 +215,7 @@
// only big clock is removed at switch
assertThat(mLargeClockFrame.getParent()).isNull();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE);
}
@Test
@@ -223,6 +227,7 @@
// only big clock is removed at switch
assertThat(mLargeClockFrame.getParent()).isNull();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 13cd328..df6752a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -32,6 +32,9 @@
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
import static com.google.common.truth.Truth.assertThat;
@@ -92,6 +95,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.service.dreams.IDreamManager;
import android.service.trust.TrustAgentService;
import android.telephony.ServiceState;
@@ -116,6 +120,7 @@
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.SessionTracker;
@@ -123,6 +128,7 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.settings.SecureSettings;
@@ -142,6 +148,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -191,6 +198,8 @@
@Mock
private DevicePolicyManager mDevicePolicyManager;
@Mock
+ private DevicePostureController mDevicePostureController;
+ @Mock
private IDreamManager mDreamManager;
@Mock
private KeyguardBypassController mKeyguardBypassController;
@@ -233,6 +242,8 @@
@Mock
private GlobalSettings mGlobalSettings;
private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
+ @Mock
+ private FingerprintInteractiveToAuthProvider mInteractiveToAuthProvider;
private final int mCurrentUserId = 100;
@@ -296,6 +307,7 @@
.thenReturn(new ServiceState());
when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings);
when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
+ when(mDevicePostureController.getDevicePosture()).thenReturn(DEVICE_POSTURE_UNKNOWN);
mMockitoSession = ExtendedMockito.mockitoSession()
.spyStatic(SubscriptionManager.class)
@@ -307,6 +319,9 @@
when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
ExtendedMockito.doReturn(mActivityService).when(ActivityManager::getService);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.systemui.R.integer.config_face_auth_supported_posture,
+ DEVICE_POSTURE_UNKNOWN);
mFaceWakeUpTriggersConfig = new FaceWakeUpTriggersConfig(
mContext.getResources(),
mGlobalSettings,
@@ -1250,7 +1265,7 @@
}
@Test
- public void testStartsListeningForSfps_whenKeyguardIsVisible_ifRequireScreenOnToAuthEnabled()
+ public void startsListeningForSfps_whenKeyguardIsVisible_ifRequireInteractiveToAuthEnabled()
throws RemoteException {
// SFPS supported and enrolled
final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
@@ -1258,12 +1273,8 @@
when(mAuthController.getSfpsProps()).thenReturn(props);
when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
- // WHEN require screen on to auth is disabled, and keyguard is not awake
- when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(0);
- mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
-
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true);
+ // WHEN require interactive to auth is disabled, and keyguard is not awake
+ when(mInteractiveToAuthProvider.isEnabled(anyInt())).thenReturn(false);
// Preconditions for sfps auth to run
keyguardNotGoingAway();
@@ -1279,9 +1290,8 @@
// THEN we should listen for sfps when screen off, because require screen on is disabled
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
- // WHEN require screen on to auth is enabled, and keyguard is not awake
- when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(1);
- mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+ // WHEN require interactive to auth is enabled, and keyguard is not awake
+ when(mInteractiveToAuthProvider.isEnabled(anyInt())).thenReturn(true);
// THEN we shouldn't listen for sfps when screen off, because require screen on is enabled
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
@@ -1295,6 +1305,61 @@
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
}
+ @Test
+ public void notListeningForSfps_whenGoingToSleep_ifRequireInteractiveToAuthEnabled()
+ throws RemoteException {
+ // GIVEN SFPS supported and enrolled
+ final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+ props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
+ when(mAuthController.getSfpsProps()).thenReturn(props);
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+
+ // GIVEN Preconditions for sfps auth to run
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ statusBarShadeIsLocked();
+
+ // WHEN require interactive to auth is enabled & keyguard is going to sleep
+ when(mInteractiveToAuthProvider.isEnabled(anyInt())).thenReturn(true);
+ deviceGoingToSleep();
+
+ mTestableLooper.processAllMessages();
+
+ // THEN we should NOT listen for sfps because device is going to sleep
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
+ }
+
+ @Test
+ public void listeningForSfps_whenGoingToSleep_ifRequireInteractiveToAuthDisabled()
+ throws RemoteException {
+ // GIVEN SFPS supported and enrolled
+ final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+ props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
+ when(mAuthController.getSfpsProps()).thenReturn(props);
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+
+ // GIVEN Preconditions for sfps auth to run
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ statusBarShadeIsLocked();
+
+ // WHEN require interactive to auth is disabled & keyguard is going to sleep
+ when(mInteractiveToAuthProvider.isEnabled(anyInt())).thenReturn(false);
+ deviceGoingToSleep();
+
+ mTestableLooper.processAllMessages();
+
+ // THEN we should listen for sfps because screen on to auth is disabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+ }
private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal(
@FingerprintSensorProperties.SensorType int sensorType) {
@@ -2187,6 +2252,54 @@
eq(true));
}
+ @Test
+ public void testShouldListenForFace_withAuthSupportPostureConfig_returnsTrue()
+ throws RemoteException {
+ mKeyguardUpdateMonitor.mConfigFaceAuthSupportedPosture = DEVICE_POSTURE_CLOSED;
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ supportsFaceDetection();
+
+ deviceInPostureStateOpened();
+ mTestableLooper.processAllMessages();
+ // Should not listen for face when posture state in DEVICE_POSTURE_OPENED
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+
+ deviceInPostureStateClosed();
+ mTestableLooper.processAllMessages();
+ // Should listen for face when posture state in DEVICE_POSTURE_CLOSED
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+ }
+
+ @Test
+ public void testShouldListenForFace_withoutAuthSupportPostureConfig_returnsTrue()
+ throws RemoteException {
+ mKeyguardUpdateMonitor.mConfigFaceAuthSupportedPosture = DEVICE_POSTURE_UNKNOWN;
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ supportsFaceDetection();
+
+ deviceInPostureStateClosed();
+ mTestableLooper.processAllMessages();
+ // Whether device in any posture state, always listen for face
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ deviceInPostureStateOpened();
+ mTestableLooper.processAllMessages();
+ // Whether device in any posture state, always listen for face
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+ }
+
private void userDeviceLockDown() {
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId))
@@ -2266,6 +2379,14 @@
.onAuthenticationAcquired(FINGERPRINT_ACQUIRED_START);
}
+ private void deviceInPostureStateOpened() {
+ mKeyguardUpdateMonitor.mPostureCallback.onPostureChanged(DEVICE_POSTURE_OPENED);
+ }
+
+ private void deviceInPostureStateClosed() {
+ mKeyguardUpdateMonitor.mPostureCallback.onPostureChanged(DEVICE_POSTURE_CLOSED);
+ }
+
private void successfulFingerprintAuth() {
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
.onAuthenticationSucceeded(
@@ -2407,7 +2528,8 @@
mPowerManager, mTrustManager, mSubscriptionManager, mUserManager,
mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
- mFaceWakeUpTriggersConfig);
+ mFaceWakeUpTriggersConfig, mDevicePostureController,
+ Optional.of(mInteractiveToAuthProvider));
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index e4c41a7..05bd1e4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -49,6 +49,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -91,6 +92,7 @@
protected @Mock AuthRippleController mAuthRippleController;
protected @Mock FeatureFlags mFeatureFlags;
protected @Mock KeyguardTransitionRepository mTransitionRepository;
+ protected @Mock CommandQueue mCommandQueue;
protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
protected LockIconViewController mUnderTest;
@@ -157,7 +159,7 @@
mAuthRippleController,
mResources,
new KeyguardTransitionInteractor(mTransitionRepository),
- new KeyguardInteractor(new FakeKeyguardRepository()),
+ new KeyguardInteractor(new FakeKeyguardRepository(), mCommandQueue),
mFeatureFlags
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 83bf183..ace0ccb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -739,7 +739,7 @@
public void testForwardsDozeEvents() throws RemoteException {
when(mStatusBarStateController.isDozing()).thenReturn(true);
when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
- mAuthController.setBiometicContextListener(mContextListener);
+ mAuthController.setBiometricContextListener(mContextListener);
mStatusBarStateListenerCaptor.getValue().onDozingChanged(true);
mStatusBarStateListenerCaptor.getValue().onDozingChanged(false);
@@ -754,7 +754,7 @@
public void testForwardsWakeEvents() throws RemoteException {
when(mStatusBarStateController.isDozing()).thenReturn(false);
when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
- mAuthController.setBiometicContextListener(mContextListener);
+ mAuthController.setBiometricContextListener(mContextListener);
mWakefullnessObserverCaptor.getValue().onStartedGoingToSleep();
mWakefullnessObserverCaptor.getValue().onFinishedGoingToSleep();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 53bc2c2..1ef119d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -43,6 +43,7 @@
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
@@ -52,7 +53,6 @@
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.time.SystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -95,7 +95,6 @@
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var transitionController: LockscreenShadeTransitionController
@Mock private lateinit var configurationController: ConfigurationController
- @Mock private lateinit var systemClock: SystemClock
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var unlockedScreenOffAnimationController:
UnlockedScreenOffAnimationController
@@ -106,7 +105,8 @@
@Mock private lateinit var udfpsEnrollView: UdfpsEnrollView
@Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
@Mock private lateinit var featureFlags: FeatureFlags
- @Mock private lateinit var mPrimaryBouncerInteractor: PrimaryBouncerInteractor
+ @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+ @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -138,10 +138,10 @@
context, fingerprintManager, inflater, windowManager, accessibilityManager,
statusBarStateController, shadeExpansionStateManager, statusBarKeyguardViewManager,
keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
- configurationController, systemClock, keyguardStateController,
+ configurationController, keyguardStateController,
unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
- mPrimaryBouncerInteractor, isDebuggable
+ primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable,
)
block()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index b061eb3..0c34e54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -81,6 +81,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -202,6 +203,8 @@
private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@Mock
private SinglePointerTouchProcessor mSinglePointerTouchProcessor;
+ @Mock
+ private AlternateBouncerInteractor mAlternateBouncerInteractor;
// Capture listeners so that they can be used to send events
@Captor
@@ -292,7 +295,8 @@
mDisplayManager, mHandler, mConfigurationController, mSystemClock,
mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
- mPrimaryBouncerInteractor, mSinglePointerTouchProcessor);
+ mPrimaryBouncerInteractor, mSinglePointerTouchProcessor,
+ mAlternateBouncerInteractor);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -406,7 +410,7 @@
// GIVEN overlay was showing and the udfps bouncer is showing
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
- when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(true);
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
// WHEN the overlay is hidden
mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
index 3c61382..9c32c38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -30,6 +30,7 @@
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -43,7 +44,6 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.mockito.ArgumentCaptor;
@@ -73,9 +73,9 @@
protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
protected @Mock KeyguardBouncer mBouncer;
protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
- protected FakeSystemClock mSystemClock = new FakeSystemClock();
protected UdfpsKeyguardViewController mController;
@@ -86,10 +86,6 @@
private @Captor ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
protected List<ShadeExpansionListener> mExpansionListeners;
- private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.AlternateBouncer>
- mAlternateBouncerCaptor;
- protected StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
-
private @Captor ArgumentCaptor<KeyguardStateController.Callback>
mKeyguardStateControllerCallbackCaptor;
protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
@@ -135,12 +131,6 @@
}
}
- protected void captureAltAuthInterceptor() {
- verify(mStatusBarKeyguardViewManager).setAlternateBouncer(
- mAlternateBouncerCaptor.capture());
- mAlternateBouncer = mAlternateBouncerCaptor.getValue();
- }
-
protected void captureKeyguardStateControllerCallback() {
verify(mKeyguardStateController).addCallback(
mKeyguardStateControllerCallbackCaptor.capture());
@@ -160,6 +150,7 @@
protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
boolean useModernBouncer, boolean useExpandedOverlay) {
mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
+ mFeatureFlags.set(Flags.MODERN_ALTERNATE_BOUNCER, useModernBouncer);
mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay);
when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn(
useModernBouncer ? null : mBouncer);
@@ -172,14 +163,14 @@
mDumpManager,
mLockscreenShadeTransitionController,
mConfigurationController,
- mSystemClock,
mKeyguardStateController,
mUnlockedScreenOffAnimationController,
mDialogManager,
mUdfpsController,
mActivityLaunchAnimator,
mFeatureFlags,
- mPrimaryBouncerInteractor);
+ mPrimaryBouncerInteractor,
+ mAlternateBouncerInteractor);
return controller;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index babe533..813eeeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -19,18 +19,15 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
+import android.testing.TestableLooper;
import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -46,7 +43,8 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
+
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest {
private @Captor ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback>
mBouncerExpansionCallbackCaptor;
@@ -72,8 +70,6 @@
assertTrue(mController.shouldPauseAuth());
}
-
-
@Test
public void testRegistersExpansionChangedListenerOnAttached() {
mController.onViewAttached();
@@ -237,85 +233,9 @@
public void testOverrideShouldPauseAuthOnShadeLocked() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureAltAuthInterceptor();
sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
assertTrue(mController.shouldPauseAuth());
-
- mAlternateBouncer.showAlternateBouncer(); // force show
- assertFalse(mController.shouldPauseAuth());
- assertTrue(mAlternateBouncer.isShowingAlternateBouncer());
-
- mAlternateBouncer.hideAlternateBouncer(); // stop force show
- assertTrue(mController.shouldPauseAuth());
- assertFalse(mAlternateBouncer.isShowingAlternateBouncer());
- }
-
- @Test
- public void testOnDetachedStateReset() {
- // GIVEN view is attached
- mController.onViewAttached();
- captureAltAuthInterceptor();
-
- // WHEN view is detached
- mController.onViewDetached();
-
- // THEN remove alternate auth interceptor
- verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAlternateBouncer);
- }
-
- @Test
- public void testHiddenUdfpsBouncerOnTouchOutside_nothingHappens() {
- // GIVEN view is attached
- mController.onViewAttached();
- captureAltAuthInterceptor();
-
- // GIVEN udfps bouncer isn't showing
- mAlternateBouncer.hideAlternateBouncer();
-
- // WHEN touch is observed outside the view
- mController.onTouchOutsideView();
-
- // THEN bouncer / alt auth methods are never called
- verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
- verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
- verify(mStatusBarKeyguardViewManager, never()).hideAlternateBouncer(anyBoolean());
- }
-
- @Test
- public void testShowingUdfpsBouncerOnTouchOutsideWithinThreshold_nothingHappens() {
- // GIVEN view is attached
- mController.onViewAttached();
- captureAltAuthInterceptor();
-
- // GIVEN udfps bouncer is showing
- mAlternateBouncer.showAlternateBouncer();
-
- // WHEN touch is observed outside the view 200ms later (just within threshold)
- mSystemClock.advanceTime(200);
- mController.onTouchOutsideView();
-
- // THEN bouncer / alt auth methods are never called because not enough time has passed
- verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
- verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
- verify(mStatusBarKeyguardViewManager, never()).hideAlternateBouncer(anyBoolean());
- }
-
- @Test
- public void testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showPrimaryBouncer() {
- // GIVEN view is attached
- mController.onViewAttached();
- captureAltAuthInterceptor();
-
- // GIVEN udfps bouncer is showing
- mAlternateBouncer.showAlternateBouncer();
-
- // WHEN touch is observed outside the view 205ms later
- mSystemClock.advanceTime(205);
- mController.onTouchOutsideView();
-
- // THEN show the bouncer
- verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(eq(true));
}
@Test
@@ -334,25 +254,6 @@
}
@Test
- public void testShowUdfpsBouncer() {
- // GIVEN view is attached and status bar expansion is 0
- mController.onViewAttached();
- captureStatusBarExpansionListeners();
- captureKeyguardStateControllerCallback();
- captureAltAuthInterceptor();
- updateStatusBarExpansion(0, true);
- reset(mView);
- when(mView.getContext()).thenReturn(mResourceContext);
- when(mResourceContext.getString(anyInt())).thenReturn("test string");
-
- // WHEN status bar expansion is 0 but udfps bouncer is requested
- mAlternateBouncer.showAlternateBouncer();
-
- // THEN alpha is 255
- verify(mView).setUnpausedAlpha(255);
- }
-
- @Test
public void testTransitionToFullShadeProgress() {
// GIVEN view is attached and status bar expansion is 1f
mController.onViewAttached();
@@ -370,24 +271,6 @@
}
@Test
- public void testShowUdfpsBouncer_transitionToFullShadeProgress() {
- // GIVEN view is attached and status bar expansion is 1f
- mController.onViewAttached();
- captureStatusBarExpansionListeners();
- captureKeyguardStateControllerCallback();
- captureAltAuthInterceptor();
- updateStatusBarExpansion(1f, true);
- mAlternateBouncer.showAlternateBouncer();
- reset(mView);
-
- // WHEN we're transitioning to the full shade
- mController.setTransitionToFullShadeProgress(1.0f);
-
- // THEN alpha is 255 (b/c udfps bouncer is requested)
- verify(mView).setUnpausedAlpha(255);
- }
-
- @Test
public void testUpdatePanelExpansion_pauseAuth() {
// GIVEN view is attached + on the keyguard
mController.onViewAttached();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 2d412dc..3b4f7e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -21,26 +21,35 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.repository.BiometricRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.KeyguardBouncer
import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.time.SystemClock
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.yield
+import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -57,6 +66,7 @@
keyguardBouncerRepository =
KeyguardBouncerRepository(
mock(com.android.keyguard.ViewMediatorCallback::class.java),
+ FakeSystemClock(),
TestCoroutineScope(),
bouncerLogger,
)
@@ -77,15 +87,43 @@
mock(KeyguardBypassController::class.java),
mKeyguardUpdateMonitor
)
+ mAlternateBouncerInteractor =
+ AlternateBouncerInteractor(
+ keyguardBouncerRepository,
+ mock(BiometricRepository::class.java),
+ mock(SystemClock::class.java),
+ mock(KeyguardUpdateMonitor::class.java),
+ mock(FeatureFlags::class.java)
+ )
return createUdfpsKeyguardViewController(
/* useModernBouncer */ true, /* useExpandedOverlay */
false
)
}
- /** After migration, replaces LockIconViewControllerTest version */
@Test
- fun testShouldPauseAuthBouncerShowing() =
+ fun shadeLocked_showAlternateBouncer_unpauseAuth() =
+ runBlocking(IMMEDIATE) {
+ // GIVEN view is attached + on the SHADE_LOCKED (udfps view not showing)
+ mController.onViewAttached()
+ captureStatusBarStateListeners()
+ sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED)
+
+ // WHEN alternate bouncer is requested
+ val job = mController.listenForAlternateBouncerVisibility(this)
+ keyguardBouncerRepository.setAlternateVisible(true)
+ yield()
+
+ // THEN udfps view will animate in & pause auth is updated to NOT pause
+ verify(mView).animateInUdfpsBouncer(any())
+ assertFalse(mController.shouldPauseAuth())
+
+ job.cancel()
+ }
+
+ /** After migration to MODERN_BOUNCER, replaces UdfpsKeyguardViewControllerTest version */
+ @Test
+ fun shouldPauseAuthBouncerShowing() =
runBlocking(IMMEDIATE) {
// GIVEN view attached and we're on the keyguard
mController.onViewAttached()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index d550b92..8255a14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -80,15 +80,6 @@
}
@Test
- fun forwardsEvents() {
- view.dozeTimeTick()
- verify(animationViewController).dozeTimeTick()
-
- view.onTouchOutsideView()
- verify(animationViewController).onTouchOutsideView()
- }
-
- @Test
fun layoutSizeFitsSensor() {
val params = withArgCaptor<RectF> {
verify(animationViewController).onSensorRectUpdated(capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
new file mode 100644
index 0000000..af46d9b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.biometrics.udfps
+
+import android.graphics.Point
+import android.graphics.Rect
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.`when` as whenEver
+
+@SmallTest
+@RunWith(Parameterized::class)
+class EllipseOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() {
+ val underTest = spy(EllipseOverlapDetector(neededPoints = 1))
+
+ @Before
+ fun setUp() {
+ // Use one single center point for testing, required or total number of points may change
+ whenEver(underTest.calculateSensorPoints(SENSOR))
+ .thenReturn(listOf(Point(SENSOR.centerX(), SENSOR.centerY())))
+ }
+
+ @Test
+ fun isGoodOverlap() {
+ val touchData =
+ TOUCH_DATA.copy(
+ x = testCase.x.toFloat(),
+ y = testCase.y.toFloat(),
+ minor = testCase.minor,
+ major = testCase.major
+ )
+ val actual = underTest.isGoodOverlap(touchData, SENSOR)
+
+ assertThat(actual).isEqualTo(testCase.expected)
+ }
+
+ data class TestCase(
+ val x: Int,
+ val y: Int,
+ val minor: Float,
+ val major: Float,
+ val expected: Boolean
+ )
+
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun data(): List<TestCase> =
+ listOf(
+ genTestCases(
+ innerXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()),
+ innerYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY()),
+ outerXs = listOf(SENSOR.left - 1, SENSOR.right + 1),
+ outerYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1),
+ minor = 300f,
+ major = 300f,
+ expected = true
+ ),
+ genTestCases(
+ innerXs = listOf(SENSOR.left, SENSOR.right),
+ innerYs = listOf(SENSOR.top, SENSOR.bottom),
+ outerXs = listOf(SENSOR.left - 1, SENSOR.right + 1),
+ outerYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1),
+ minor = 100f,
+ major = 100f,
+ expected = false
+ )
+ )
+ .flatten()
+ }
+}
+
+/* Placeholder touch parameters. */
+private const val POINTER_ID = 42
+private const val NATIVE_MINOR = 2.71828f
+private const val NATIVE_MAJOR = 3.14f
+private const val ORIENTATION = 0f // used for perfect circles
+private const val TIME = 12345699L
+private const val GESTURE_START = 12345600L
+
+/* Template [NormalizedTouchData]. */
+private val TOUCH_DATA =
+ NormalizedTouchData(
+ POINTER_ID,
+ x = 0f,
+ y = 0f,
+ NATIVE_MINOR,
+ NATIVE_MAJOR,
+ ORIENTATION,
+ TIME,
+ GESTURE_START
+ )
+
+private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 400 /* bottom */)
+
+private fun genTestCases(
+ innerXs: List<Int>,
+ innerYs: List<Int>,
+ outerXs: List<Int>,
+ outerYs: List<Int>,
+ minor: Float,
+ major: Float,
+ expected: Boolean
+): List<EllipseOverlapDetectorTest.TestCase> {
+ return (innerXs + outerXs).flatMap { x ->
+ (innerYs + outerYs).map { y ->
+ EllipseOverlapDetectorTest.TestCase(x, y, minor, major, expected)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
index 95c53b4..56043e30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
@@ -221,6 +221,14 @@
private const val ROTATION_0_NATIVE_DISPLAY_WIDTH = 400
private const val ROTATION_0_NATIVE_DISPLAY_HEIGHT = 600
+/* Placeholder touch parameters. */
+private const val POINTER_ID = 42
+private const val NATIVE_MINOR = 2.71828f
+private const val NATIVE_MAJOR = 3.14f
+private const val ORIENTATION = 1.2345f
+private const val TIME = 12345699L
+private const val GESTURE_START = 12345600L
+
/*
* ROTATION_0 map:
* _ _ _ _
@@ -244,6 +252,7 @@
private val ROTATION_0_INPUTS =
OrientationBasedInputs(
rotation = Surface.ROTATION_0,
+ nativeOrientation = ORIENTATION,
nativeXWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterX(),
nativeYWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterY(),
nativeXOutsideSensor = 250f,
@@ -271,6 +280,7 @@
private val ROTATION_90_INPUTS =
OrientationBasedInputs(
rotation = Surface.ROTATION_90,
+ nativeOrientation = (ORIENTATION - Math.PI.toFloat() / 2),
nativeXWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterX(),
nativeYWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterY(),
nativeXOutsideSensor = 150f,
@@ -304,20 +314,13 @@
private val ROTATION_270_INPUTS =
OrientationBasedInputs(
rotation = Surface.ROTATION_270,
+ nativeOrientation = (ORIENTATION + Math.PI.toFloat() / 2),
nativeXWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterX(),
nativeYWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterY(),
nativeXOutsideSensor = 450f,
nativeYOutsideSensor = 250f,
)
-/* Placeholder touch parameters. */
-private const val POINTER_ID = 42
-private const val NATIVE_MINOR = 2.71828f
-private const val NATIVE_MAJOR = 3.14f
-private const val ORIENTATION = 1.23f
-private const val TIME = 12345699L
-private const val GESTURE_START = 12345600L
-
/* Template [MotionEvent]. */
private val MOTION_EVENT =
obtainMotionEvent(
@@ -352,6 +355,7 @@
*/
private data class OrientationBasedInputs(
@Rotation val rotation: Int,
+ val nativeOrientation: Float,
val nativeXWithinSensor: Float,
val nativeYWithinSensor: Float,
val nativeXOutsideSensor: Float,
@@ -404,6 +408,7 @@
y = nativeY * scaleFactor,
minor = NATIVE_MINOR * scaleFactor,
major = NATIVE_MAJOR * scaleFactor,
+ orientation = orientation.nativeOrientation
)
val expectedTouchData =
NORMALIZED_TOUCH_DATA.copy(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index 0fadc13..e4df754 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -106,6 +106,7 @@
mClassifiers.add(mClassifierB);
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mFalsingDataProvider.isFolded()).thenReturn(true);
mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier,
mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
@@ -121,6 +122,7 @@
mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
mFakeFeatureFlags.set(Flags.MEDIA_FALSING_PENALTY, true);
+ mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index 4281ee0..ae38eb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -89,25 +89,27 @@
mClassifiers.add(mClassifierA);
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mFalsingDataProvider.isFolded()).thenReturn(true);
mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier,
mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
mAccessibilityManager, false, mFakeFeatureFlags);
mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
+ mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true);
}
@Test
public void testA11yDisablesGesture() {
- assertThat(mBrightLineFalsingManager.isFalseTap(1)).isTrue();
+ assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
- assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse();
+ assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
}
@Test
public void testA11yDisablesTap() {
- assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
+ assertThat(mBrightLineFalsingManager.isFalseTap(1)).isTrue();
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
- assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
+ assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse();
}
@@ -179,4 +181,11 @@
when(mFalsingDataProvider.isA11yAction()).thenReturn(true);
assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse();
}
+
+ @Test
+ public void testSkipUnfolded() {
+ assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
+ when(mFalsingDataProvider.isFolded()).thenReturn(false);
+ assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
index 5fa7214..94cf384 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.classifier;
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
@@ -38,6 +39,7 @@
private float mOffsetY = 0;
@Mock
private BatteryController mBatteryController;
+ private FoldStateListener mFoldStateListener = new FoldStateListener(mContext);
private final DockManagerFake mDockManager = new DockManagerFake();
public void setup() {
@@ -47,7 +49,8 @@
displayMetrics.ydpi = 100;
displayMetrics.widthPixels = 1000;
displayMetrics.heightPixels = 1000;
- mDataProvider = new FalsingDataProvider(displayMetrics, mBatteryController, mDockManager);
+ mDataProvider = new FalsingDataProvider(
+ displayMetrics, mBatteryController, mFoldStateListener, mDockManager);
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index d315c2d..c451a1e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
@@ -50,6 +51,8 @@
private FalsingDataProvider mDataProvider;
@Mock
private BatteryController mBatteryController;
+ @Mock
+ private FoldStateListener mFoldStateListener;
private final DockManagerFake mDockManager = new DockManagerFake();
@Before
@@ -61,7 +64,8 @@
displayMetrics.ydpi = 100;
displayMetrics.widthPixels = 1000;
displayMetrics.heightPixels = 1000;
- mDataProvider = new FalsingDataProvider(displayMetrics, mBatteryController, mDockManager);
+ mDataProvider = new FalsingDataProvider(
+ displayMetrics, mBatteryController, mFoldStateListener, mDockManager);
}
@After
@@ -316,4 +320,16 @@
mDataProvider.onA11yAction();
assertThat(mDataProvider.isA11yAction()).isTrue();
}
+
+ @Test
+ public void test_FoldedState_Folded() {
+ when(mFoldStateListener.getFolded()).thenReturn(true);
+ assertThat(mDataProvider.isFolded()).isTrue();
+ }
+
+ @Test
+ public void test_FoldedState_Unfolded() {
+ when(mFoldStateListener.getFolded()).thenReturn(false);
+ assertThat(mDataProvider.isFolded()).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 4659766..0a03b2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -51,6 +51,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
@@ -86,9 +87,9 @@
@Mock private lateinit var backgroundHandler: Handler
@Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
+ @Mock private lateinit var commandQueue: CommandQueue
private lateinit var underTest: CustomizationProvider
-
private lateinit var testScope: TestScope
@Before
@@ -160,6 +161,7 @@
keyguardInteractor =
KeyguardInteractor(
repository = FakeKeyguardRepository(),
+ commandQueue = commandQueue,
),
registry = mock(),
lockPatternUtils = lockPatternUtils,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 9b0d8db..f55b866 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -29,6 +29,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -66,6 +67,7 @@
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -135,6 +137,7 @@
private @Mock AuthController mAuthController;
private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
private @Mock ShadeWindowLogger mShadeWindowLogger;
+ private @Mock FeatureFlags mFeatureFlags;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -483,6 +486,38 @@
assertTrue(mViewMediator.isShowingAndNotOccluded());
}
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testDoKeyguardWhileInteractive_resets() {
+ mViewMediator.setShowingLocked(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ TestableLooper.get(this).processAllMessages();
+
+ when(mPowerManager.isInteractive()).thenReturn(true);
+
+ mViewMediator.onSystemReady();
+ TestableLooper.get(this).processAllMessages();
+
+ assertTrue(mViewMediator.isShowingAndNotOccluded());
+ verify(mStatusBarKeyguardViewManager).reset(anyBoolean());
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testDoKeyguardWhileNotInteractive_showsInsteadOfResetting() {
+ mViewMediator.setShowingLocked(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ TestableLooper.get(this).processAllMessages();
+
+ when(mPowerManager.isInteractive()).thenReturn(false);
+
+ mViewMediator.onSystemReady();
+ TestableLooper.get(this).processAllMessages();
+
+ assertTrue(mViewMediator.isShowingAndNotOccluded());
+ verify(mStatusBarKeyguardViewManager, never()).reset(anyBoolean());
+ }
+
private void createAndStartViewMediator() {
mViewMediator = new KeyguardViewMediator(
mContext,
@@ -510,6 +545,7 @@
mScreenOnCoordinator,
mInteractionJankMonitor,
mDreamOverlayStateController,
+ mFeatureFlags,
() -> mShadeController,
() -> mNotificationShadeWindowController,
() -> mActivityLaunchAnimator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
index f32d76b..39a453d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
@@ -30,6 +30,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -51,7 +52,12 @@
public void setUp() throws Exception {
mWallpaperManager = mock(IWallpaperManager.class);
mWakefulness =
- new WakefulnessLifecycle(mContext, mWallpaperManager, mock(DumpManager.class));
+ new WakefulnessLifecycle(
+ mContext,
+ mWallpaperManager,
+ new FakeSystemClock(),
+ mock(DumpManager.class)
+ );
mWakefulnessObserver = mock(WakefulnessLifecycle.Observer.class);
mWakefulness.addObserver(mWakefulnessObserver);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt
new file mode 100644
index 0000000..a92dd3b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.admin.DevicePolicyManager
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+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
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+class BiometricRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: BiometricRepository
+
+ @Mock private lateinit var authController: AuthController
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ private lateinit var userRepository: FakeUserRepository
+
+ private lateinit var testDispatcher: TestDispatcher
+ private lateinit var testScope: TestScope
+ private var testableLooper: TestableLooper? = null
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+ testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ userRepository = FakeUserRepository()
+ }
+
+ private suspend fun createBiometricRepository() {
+ userRepository.setUserInfos(listOf(PRIMARY_USER))
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ underTest =
+ BiometricRepositoryImpl(
+ context = context,
+ lockPatternUtils = lockPatternUtils,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ authController = authController,
+ userRepository = userRepository,
+ devicePolicyManager = devicePolicyManager,
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
+ looper = testableLooper!!.looper,
+ )
+ }
+
+ @Test
+ fun fingerprintEnrollmentChange() =
+ testScope.runTest {
+ createBiometricRepository()
+ val fingerprintEnabledByDevicePolicy = collectLastValue(underTest.isFingerprintEnrolled)
+ runCurrent()
+
+ val captor = argumentCaptor<AuthController.Callback>()
+ verify(authController).addCallback(captor.capture())
+ whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(true)
+ captor.value.onEnrollmentsChanged(
+ BiometricType.UNDER_DISPLAY_FINGERPRINT,
+ PRIMARY_USER_ID,
+ true
+ )
+ assertThat(fingerprintEnabledByDevicePolicy()).isTrue()
+
+ whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(false)
+ captor.value.onEnrollmentsChanged(
+ BiometricType.UNDER_DISPLAY_FINGERPRINT,
+ PRIMARY_USER_ID,
+ false
+ )
+ assertThat(fingerprintEnabledByDevicePolicy()).isFalse()
+ }
+
+ @Test
+ fun strongBiometricAllowedChange() =
+ testScope.runTest {
+ createBiometricRepository()
+ val strongBiometricAllowed = collectLastValue(underTest.isStrongBiometricAllowed)
+ runCurrent()
+
+ val captor = argumentCaptor<LockPatternUtils.StrongAuthTracker>()
+ verify(lockPatternUtils).registerStrongAuthTracker(captor.capture())
+
+ captor.value
+ .getStub()
+ .onStrongAuthRequiredChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
+ testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper
+ assertThat(strongBiometricAllowed()).isTrue()
+
+ captor.value
+ .getStub()
+ .onStrongAuthRequiredChanged(STRONG_AUTH_REQUIRED_AFTER_BOOT, PRIMARY_USER_ID)
+ testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper
+ assertThat(strongBiometricAllowed()).isFalse()
+ }
+
+ @Test
+ fun fingerprintDisabledByDpmChange() =
+ testScope.runTest {
+ createBiometricRepository()
+ val fingerprintEnabledByDevicePolicy =
+ collectLastValue(underTest.isFingerprintEnabledByDevicePolicy)
+ runCurrent()
+
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT)
+ broadcastDPMStateChange()
+ assertThat(fingerprintEnabledByDevicePolicy()).isFalse()
+
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt())).thenReturn(0)
+ broadcastDPMStateChange()
+ assertThat(fingerprintEnabledByDevicePolicy()).isTrue()
+ }
+
+ private fun broadcastDPMStateChange() {
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(
+ context,
+ Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)
+ )
+ }
+ }
+
+ companion object {
+ private const val PRIMARY_USER_ID = 0
+ private val PRIMARY_USER =
+ UserInfo(
+ /* id= */ PRIMARY_USER_ID,
+ /* name= */ "primary user",
+ /* flags= */ UserInfo.FLAG_PRIMARY
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
index 9970a67..969537d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -20,6 +20,7 @@
import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.util.time.SystemClock
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineScope
import org.junit.Before
@@ -34,6 +35,7 @@
@RunWith(JUnit4::class)
class KeyguardBouncerRepositoryTest : SysuiTestCase() {
+ @Mock private lateinit var systemClock: SystemClock
@Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
@Mock private lateinit var bouncerLogger: TableLogBuffer
lateinit var underTest: KeyguardBouncerRepository
@@ -43,7 +45,12 @@
MockitoAnnotations.initMocks(this)
val testCoroutineScope = TestCoroutineScope()
underTest =
- KeyguardBouncerRepository(viewMediatorCallback, testCoroutineScope, bouncerLogger)
+ KeyguardBouncerRepository(
+ viewMediatorCallback,
+ systemClock,
+ testCoroutineScope,
+ bouncerLogger,
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index be712f6..f997d18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.common.shared.model.Position
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.DozeHost
import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
@@ -38,14 +39,17 @@
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
+import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -68,6 +72,7 @@
@Mock private lateinit var authController: AuthController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
+ @Mock private lateinit var dozeParameters: DozeParameters
private lateinit var underTest: KeyguardRepositoryImpl
@@ -84,6 +89,7 @@
keyguardStateController,
keyguardUpdateMonitor,
dozeTransitionListener,
+ dozeParameters,
authController,
dreamOverlayCallbackController,
)
@@ -170,6 +176,26 @@
}
@Test
+ fun isAodAvailable() = runTest {
+ val flow = underTest.isAodAvailable
+ var isAodAvailable = collectLastValue(flow)
+ runCurrent()
+
+ val callback =
+ withArgCaptor<DozeParameters.Callback> { verify(dozeParameters).addCallback(capture()) }
+
+ whenever(dozeParameters.getAlwaysOn()).thenReturn(false)
+ callback.onAlwaysOnChange()
+ assertThat(isAodAvailable()).isEqualTo(false)
+
+ whenever(dozeParameters.getAlwaysOn()).thenReturn(true)
+ callback.onAlwaysOnChange()
+ assertThat(isAodAvailable()).isEqualTo(true)
+
+ flow.onCompletion { verify(dozeParameters).removeCallback(callback) }
+ }
+
+ @Test
fun isKeyguardOccluded() =
runTest(UnconfinedTestDispatcher()) {
whenever(keyguardStateController.isOccluded).thenReturn(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 5d2f0eb..f8f2a56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -104,7 +104,7 @@
val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
- val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.9))
+ val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.1))
assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
job.cancel()
@@ -201,7 +201,10 @@
)
)
fractions.forEachIndexed { index, fraction ->
- assertThat(steps[index + 1])
+ val step = steps[index + 1]
+ val truncatedValue =
+ BigDecimal(step.value.toDouble()).setScale(2, RoundingMode.HALF_UP).toFloat()
+ assertThat(step.copy(value = truncatedValue))
.isEqualTo(
TransitionStep(
from,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
new file mode 100644
index 0000000..1da7241
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.ViewMediatorCallback
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeBiometricRepository
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScope
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class AlternateBouncerInteractorTest : SysuiTestCase() {
+ private lateinit var underTest: AlternateBouncerInteractor
+ private lateinit var bouncerRepository: KeyguardBouncerRepository
+ private lateinit var biometricRepository: FakeBiometricRepository
+ @Mock private lateinit var systemClock: SystemClock
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var bouncerLogger: TableLogBuffer
+ private lateinit var featureFlags: FakeFeatureFlags
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ bouncerRepository =
+ KeyguardBouncerRepository(
+ mock(ViewMediatorCallback::class.java),
+ FakeSystemClock(),
+ TestCoroutineScope(),
+ bouncerLogger,
+ )
+ biometricRepository = FakeBiometricRepository()
+ featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) }
+ underTest =
+ AlternateBouncerInteractor(
+ bouncerRepository,
+ biometricRepository,
+ systemClock,
+ keyguardUpdateMonitor,
+ featureFlags,
+ )
+ }
+
+ @Test
+ fun canShowAlternateBouncerForFingerprint_givenCanShow() {
+ givenCanShowAlternateBouncer()
+ assertTrue(underTest.canShowAlternateBouncerForFingerprint())
+ }
+
+ @Test
+ fun canShowAlternateBouncerForFingerprint_alternateBouncerUIUnavailable() {
+ givenCanShowAlternateBouncer()
+ bouncerRepository.setAlternateBouncerUIAvailable(false)
+
+ assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+ }
+
+ @Test
+ fun canShowAlternateBouncerForFingerprint_noFingerprintsEnrolled() {
+ givenCanShowAlternateBouncer()
+ biometricRepository.setFingerprintEnrolled(false)
+
+ assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+ }
+
+ @Test
+ fun canShowAlternateBouncerForFingerprint_strongBiometricNotAllowed() {
+ givenCanShowAlternateBouncer()
+ biometricRepository.setStrongBiometricAllowed(false)
+
+ assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+ }
+
+ @Test
+ fun canShowAlternateBouncerForFingerprint_devicePolicyDoesNotAllowFingerprint() {
+ givenCanShowAlternateBouncer()
+ biometricRepository.setFingerprintEnabledByDevicePolicy(false)
+
+ assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+ }
+
+ @Test
+ fun show_whenCanShow() {
+ givenCanShowAlternateBouncer()
+
+ assertTrue(underTest.show())
+ assertTrue(bouncerRepository.isAlternateBouncerVisible.value)
+ }
+
+ @Test
+ fun show_whenCannotShow() {
+ givenCannotShowAlternateBouncer()
+
+ assertFalse(underTest.show())
+ assertFalse(bouncerRepository.isAlternateBouncerVisible.value)
+ }
+
+ @Test
+ fun hide_wasPreviouslyShowing() {
+ bouncerRepository.setAlternateVisible(true)
+
+ assertTrue(underTest.hide())
+ assertFalse(bouncerRepository.isAlternateBouncerVisible.value)
+ }
+
+ @Test
+ fun hide_wasNotPreviouslyShowing() {
+ bouncerRepository.setAlternateVisible(false)
+
+ assertFalse(underTest.hide())
+ assertFalse(bouncerRepository.isAlternateBouncerVisible.value)
+ }
+
+ private fun givenCanShowAlternateBouncer() {
+ bouncerRepository.setAlternateBouncerUIAvailable(true)
+ biometricRepository.setFingerprintEnrolled(true)
+ biometricRepository.setStrongBiometricAllowed(true)
+ biometricRepository.setFingerprintEnabledByDevicePolicy(true)
+ }
+
+ private fun givenCannotShowAlternateBouncer() {
+ biometricRepository.setFingerprintEnrolled(false)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
new file mode 100644
index 0000000..68d13d3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.app.StatusBarManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.CommandQueue.Callbacks
+import com.android.systemui.util.mockito.argumentCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.onCompletion
+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.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardInteractorTest : SysuiTestCase() {
+ @Mock private lateinit var commandQueue: CommandQueue
+
+ private lateinit var underTest: KeyguardInteractor
+ private lateinit var repository: FakeKeyguardRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ repository = FakeKeyguardRepository()
+ underTest = KeyguardInteractor(repository, commandQueue)
+ }
+
+ @Test
+ fun onCameraLaunchDetected() = runTest {
+ val flow = underTest.onCameraLaunchDetected
+ var cameraLaunchSource = collectLastValue(flow)
+ runCurrent()
+
+ val captor = argumentCaptor<CommandQueue.Callbacks>()
+ verify(commandQueue).addCallback(captor.capture())
+
+ captor.value.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)
+ assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.WIGGLE)
+
+ captor.value.onCameraLaunchGestureDetected(
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
+ )
+ assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.POWER_DOUBLE_TAP)
+
+ captor.value.onCameraLaunchGestureDetected(
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER
+ )
+ assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.LIFT_TRIGGER)
+
+ captor.value.onCameraLaunchGestureDetected(
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE
+ )
+ assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.QUICK_AFFORDANCE)
+
+ flow.onCompletion { verify(commandQueue).removeCallback(captor.value) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 14b7c31..43287b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -44,6 +44,7 @@
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
@@ -216,6 +217,7 @@
@Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
@Mock private lateinit var expandable: Expandable
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
+ @Mock private lateinit var commandQueue: CommandQueue
private lateinit var underTest: KeyguardQuickAffordanceInteractor
@@ -286,7 +288,11 @@
)
underTest =
KeyguardQuickAffordanceInteractor(
- keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
+ keyguardInteractor =
+ KeyguardInteractor(
+ repository = FakeKeyguardRepository(),
+ commandQueue = commandQueue
+ ),
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 972919a..b75a15d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -46,6 +46,7 @@
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.mock
@@ -75,6 +76,7 @@
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
+ @Mock private lateinit var commandQueue: CommandQueue
private lateinit var underTest: KeyguardQuickAffordanceInteractor
@@ -152,7 +154,8 @@
underTest =
KeyguardQuickAffordanceInteractor(
- keyguardInteractor = KeyguardInteractor(repository = repository),
+ keyguardInteractor =
+ KeyguardInteractor(repository = repository, commandQueue = commandQueue),
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index d2b7838..b3cee22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.keyguard.util.KeyguardTransitionRunner
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.cancelChildren
@@ -66,9 +67,14 @@
// Used to verify transition requests for test output
@Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository
+ @Mock private lateinit var commandQueue: CommandQueue
private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
+ private lateinit var fromDozingTransitionInteractor: FromDozingTransitionInteractor
+ private lateinit var fromOccludedTransitionInteractor: FromOccludedTransitionInteractor
+ private lateinit var fromGoneTransitionInteractor: FromGoneTransitionInteractor
+ private lateinit var fromAodTransitionInteractor: FromAodTransitionInteractor
@Before
fun setUp() {
@@ -85,7 +91,7 @@
fromLockscreenTransitionInteractor =
FromLockscreenTransitionInteractor(
scope = testScope,
- keyguardInteractor = KeyguardInteractor(keyguardRepository),
+ keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
shadeRepository = shadeRepository,
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
@@ -95,11 +101,47 @@
fromDreamingTransitionInteractor =
FromDreamingTransitionInteractor(
scope = testScope,
- keyguardInteractor = KeyguardInteractor(keyguardRepository),
+ keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
)
fromDreamingTransitionInteractor.start()
+
+ fromAodTransitionInteractor =
+ FromAodTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardTransitionRepository = mockTransitionRepository,
+ keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+ )
+ fromAodTransitionInteractor.start()
+
+ fromGoneTransitionInteractor =
+ FromGoneTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardTransitionRepository = mockTransitionRepository,
+ keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+ )
+ fromGoneTransitionInteractor.start()
+
+ fromDozingTransitionInteractor =
+ FromDozingTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardTransitionRepository = mockTransitionRepository,
+ keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+ )
+ fromDozingTransitionInteractor.start()
+
+ fromOccludedTransitionInteractor =
+ FromOccludedTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardTransitionRepository = mockTransitionRepository,
+ keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+ )
+ fromOccludedTransitionInteractor.start()
}
@Test
@@ -190,6 +232,289 @@
coroutineContext.cancelChildren()
}
+ @Test
+ fun `OCCLUDED to DOZING`() =
+ testScope.runTest {
+ // GIVEN a device with AOD not available
+ keyguardRepository.setAodAvailable(false)
+ runCurrent()
+
+ // GIVEN a prior transition has run to OCCLUDED
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN the device begins to sleep
+ keyguardRepository.setWakefulnessModel(startingToSleep())
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DOZING should occur
+ assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.to).isEqualTo(KeyguardState.DOZING)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `OCCLUDED to AOD`() =
+ testScope.runTest {
+ // GIVEN a device with AOD available
+ keyguardRepository.setAodAvailable(true)
+ runCurrent()
+
+ // GIVEN a prior transition has run to OCCLUDED
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN the device begins to sleep
+ keyguardRepository.setWakefulnessModel(startingToSleep())
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DOZING should occur
+ assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.to).isEqualTo(KeyguardState.AOD)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `LOCKSCREEN to DOZING`() =
+ testScope.runTest {
+ // GIVEN a device with AOD not available
+ keyguardRepository.setAodAvailable(false)
+ runCurrent()
+
+ // GIVEN a prior transition has run to LOCKSCREEN
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN the device begins to sleep
+ keyguardRepository.setWakefulnessModel(startingToSleep())
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DOZING should occur
+ assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.to).isEqualTo(KeyguardState.DOZING)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `LOCKSCREEN to AOD`() =
+ testScope.runTest {
+ // GIVEN a device with AOD available
+ keyguardRepository.setAodAvailable(true)
+ runCurrent()
+
+ // GIVEN a prior transition has run to LOCKSCREEN
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN the device begins to sleep
+ keyguardRepository.setWakefulnessModel(startingToSleep())
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DOZING should occur
+ assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.to).isEqualTo(KeyguardState.AOD)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `DOZING to LOCKSCREEN`() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to DOZING
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN the device begins to wake
+ keyguardRepository.setWakefulnessModel(startingToWake())
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DOZING should occur
+ assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.DOZING)
+ assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `GONE to DOZING`() =
+ testScope.runTest {
+ // GIVEN a device with AOD not available
+ keyguardRepository.setAodAvailable(false)
+ runCurrent()
+
+ // GIVEN a prior transition has run to GONE
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN the device begins to sleep
+ keyguardRepository.setWakefulnessModel(startingToSleep())
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DOZING should occur
+ assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.GONE)
+ assertThat(info.to).isEqualTo(KeyguardState.DOZING)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `GONE to AOD`() =
+ testScope.runTest {
+ // GIVEN a device with AOD available
+ keyguardRepository.setAodAvailable(true)
+ runCurrent()
+
+ // GIVEN a prior transition has run to GONE
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN the device begins to sleep
+ keyguardRepository.setWakefulnessModel(startingToSleep())
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DOZING should occur
+ assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.GONE)
+ assertThat(info.to).isEqualTo(KeyguardState.AOD)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
private fun startingToWake() =
WakefulnessModel(
WakefulnessState.STARTING_TO_WAKE,
@@ -197,4 +522,12 @@
WakeSleepReason.OTHER,
WakeSleepReason.OTHER
)
+
+ private fun startingToSleep() =
+ WakefulnessModel(
+ WakefulnessState.STARTING_TO_SLEEP,
+ true,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index a2c2f71..022afdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -47,6 +47,7 @@
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
@@ -84,6 +85,7 @@
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
+ @Mock private lateinit var commandQueue: CommandQueue
private lateinit var underTest: KeyguardBottomAreaViewModel
@@ -124,7 +126,8 @@
)
repository = FakeKeyguardRepository()
- val keyguardInteractor = KeyguardInteractor(repository = repository)
+ val keyguardInteractor =
+ KeyguardInteractor(repository = repository, commandQueue = commandQueue)
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
new file mode 100644
index 0000000..7390591
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.AnimationParams
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: LockscreenToDreamingTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardTransitionRepository()
+ val interactor = KeyguardTransitionInteractor(repository)
+ underTest = LockscreenToDreamingTransitionViewModel(interactor)
+ }
+
+ @Test
+ fun lockscreenFadeOut() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
+
+ // Should start running here...
+ repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0.1f))
+ repository.sendTransitionStep(step(0.2f))
+ // ...up to here
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(1f))
+
+ // Only three values should be present, since the dream overlay runs for a small
+ // fraction
+ // of the overall animation time
+ assertThat(values.size).isEqualTo(3)
+ assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
+ assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
+ assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA))
+
+ job.cancel()
+ }
+
+ @Test
+ fun lockscreenTranslationY() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val pixels = 100
+ val job =
+ underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+
+ // Should start running here...
+ repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.5f))
+ // ...up to here
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(3)
+ assertThat(values[0])
+ .isEqualTo(
+ EMPHASIZED_ACCELERATE.getInterpolation(
+ animValue(0f, LOCKSCREEN_TRANSLATION_Y)
+ ) * pixels
+ )
+ assertThat(values[1])
+ .isEqualTo(
+ EMPHASIZED_ACCELERATE.getInterpolation(
+ animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
+ ) * pixels
+ )
+ assertThat(values[2])
+ .isEqualTo(
+ EMPHASIZED_ACCELERATE.getInterpolation(
+ animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
+ ) * pixels
+ )
+ job.cancel()
+ }
+
+ private fun animValue(stepValue: Float, params: AnimationParams): Float {
+ val totalDuration = TO_DREAMING_DURATION
+ val startValue = (params.startTime / totalDuration).toFloat()
+
+ val multiplier = (totalDuration / params.duration).toFloat()
+ return (stepValue - startValue) * multiplier
+ }
+
+ private fun step(value: Float): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ value = value,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "LockscreenToDreamingTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
new file mode 100644
index 0000000..759345f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.AnimationParams
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_ALPHA
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: LockscreenToOccludedTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardTransitionRepository()
+ val interactor = KeyguardTransitionInteractor(repository)
+ underTest = LockscreenToOccludedTransitionViewModel(interactor)
+ }
+
+ @Test
+ fun lockscreenFadeOut() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
+
+ // Should start running here...
+ repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0.1f))
+ repository.sendTransitionStep(step(0.4f))
+ // ...up to here
+ repository.sendTransitionStep(step(0.7f))
+ repository.sendTransitionStep(step(1f))
+
+ // Only 3 values should be present, since the dream overlay runs for a small fraction
+ // of the overall animation time
+ assertThat(values.size).isEqualTo(3)
+ assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
+ assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
+ assertThat(values[2]).isEqualTo(1f - animValue(0.4f, LOCKSCREEN_ALPHA))
+
+ job.cancel()
+ }
+
+ @Test
+ fun lockscreenTranslationY() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val pixels = 100
+ val job =
+ underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+
+ // Should start running here...
+ repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(1f))
+ // ...up to here
+
+ assertThat(values.size).isEqualTo(4)
+ assertThat(values[0])
+ .isEqualTo(
+ EMPHASIZED_ACCELERATE.getInterpolation(
+ animValue(0f, LOCKSCREEN_TRANSLATION_Y)
+ ) * pixels
+ )
+ assertThat(values[1])
+ .isEqualTo(
+ EMPHASIZED_ACCELERATE.getInterpolation(
+ animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
+ ) * pixels
+ )
+ assertThat(values[2])
+ .isEqualTo(
+ EMPHASIZED_ACCELERATE.getInterpolation(
+ animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
+ ) * pixels
+ )
+ assertThat(values[3])
+ .isEqualTo(
+ EMPHASIZED_ACCELERATE.getInterpolation(
+ animValue(1f, LOCKSCREEN_TRANSLATION_Y)
+ ) * pixels
+ )
+ job.cancel()
+ }
+
+ private fun animValue(stepValue: Float, params: AnimationParams): Float {
+ val totalDuration = TO_OCCLUDED_DURATION
+ val startValue = (params.startTime / totalDuration).toFloat()
+
+ val multiplier = (totalDuration / params.duration).toFloat()
+ return (stepValue - startValue) * multiplier
+ }
+
+ private fun step(value: Float): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ value = value,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "LockscreenToOccludedTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt
new file mode 100644
index 0000000..411b1bd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.table
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class TableLogBufferFactoryTest : SysuiTestCase() {
+ private val dumpManager: DumpManager = mock()
+ private val systemClock = FakeSystemClock()
+ private val underTest = TableLogBufferFactory(dumpManager, systemClock)
+
+ @Test
+ fun `create - always creates new instance`() {
+ val b1 = underTest.create(NAME_1, SIZE)
+ val b1_copy = underTest.create(NAME_1, SIZE)
+ val b2 = underTest.create(NAME_2, SIZE)
+ val b2_copy = underTest.create(NAME_2, SIZE)
+
+ assertThat(b1).isNotSameInstanceAs(b1_copy)
+ assertThat(b1).isNotSameInstanceAs(b2)
+ assertThat(b2).isNotSameInstanceAs(b2_copy)
+ }
+
+ @Test
+ fun `getOrCreate - reuses instance`() {
+ val b1 = underTest.getOrCreate(NAME_1, SIZE)
+ val b1_copy = underTest.getOrCreate(NAME_1, SIZE)
+ val b2 = underTest.getOrCreate(NAME_2, SIZE)
+ val b2_copy = underTest.getOrCreate(NAME_2, SIZE)
+
+ assertThat(b1).isSameInstanceAs(b1_copy)
+ assertThat(b2).isSameInstanceAs(b2_copy)
+ assertThat(b1).isNotSameInstanceAs(b2)
+ assertThat(b1_copy).isNotSameInstanceAs(b2_copy)
+ }
+
+ companion object {
+ const val NAME_1 = "name 1"
+ const val NAME_2 = "name 2"
+
+ const val SIZE = 8
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 039dd4d..e4e95e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -20,6 +20,7 @@
import android.content.res.Configuration
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import android.util.MathUtils.abs
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
@@ -31,14 +32,11 @@
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.PAGINATION_DELAY
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.TRANSFORM_BEZIER
import com.android.systemui.media.controls.ui.MediaHierarchyManager.Companion.LOCATION_QS
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.PageIndicator
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -56,6 +54,7 @@
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.floatThat
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -86,6 +85,8 @@
@Mock lateinit var debugLogger: MediaCarouselControllerLogger
@Mock lateinit var mediaViewController: MediaViewController
@Mock lateinit var smartspaceMediaData: SmartspaceMediaData
+ @Mock lateinit var mediaCarousel: MediaScrollView
+ @Mock lateinit var pageIndicator: PageIndicator
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
@Captor
lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener>
@@ -647,25 +648,22 @@
@Test
fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
val delta = 0.0001F
- val paginationSquishMiddle =
- TRANSFORM_BEZIER.getInterpolation(
- (PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
- )
- val paginationSquishEnd =
- TRANSFORM_BEZIER.getInterpolation(
- (PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION
- )
+ mediaCarouselController.mediaCarousel = mediaCarousel
+ mediaCarouselController.pageIndicator = pageIndicator
+ whenever(mediaCarousel.measuredHeight).thenReturn(100)
+ whenever(pageIndicator.translationY).thenReturn(80F)
+ whenever(pageIndicator.height).thenReturn(10)
whenever(mediaHostStatesManager.mediaHostStates)
.thenReturn(mutableMapOf(LOCATION_QS to mediaHostState))
whenever(mediaHostState.visible).thenReturn(true)
mediaCarouselController.currentEndLocation = LOCATION_QS
- whenever(mediaHostState.squishFraction).thenReturn(paginationSquishMiddle)
+ whenever(mediaHostState.squishFraction).thenReturn(0.938F)
mediaCarouselController.updatePageIndicatorAlpha()
- assertEquals(mediaCarouselController.pageIndicator.alpha, 0.5F, delta)
+ verify(pageIndicator).alpha = floatThat { abs(it - 0.5F) < delta }
- whenever(mediaHostState.squishFraction).thenReturn(paginationSquishEnd)
+ whenever(mediaHostState.squishFraction).thenReturn(1.0F)
mediaCarouselController.updatePageIndicatorAlpha()
- assertEquals(mediaCarouselController.pageIndicator.alpha, 1.0F, delta)
+ verify(pageIndicator).alpha = floatThat { abs(it - 1.0F) < delta }
}
@Ignore("b/253229241")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 920801f..a579518 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
@@ -76,6 +77,7 @@
@Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@Mock private lateinit var keyguardViewController: KeyguardViewController
+ @Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView
@Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController
@Captor
@@ -110,6 +112,7 @@
keyguardStateController,
bypassController,
mediaCarouselController,
+ mediaDataManager,
keyguardViewController,
dreamOverlayStateController,
configurationController,
@@ -125,6 +128,7 @@
setupHost(qsHost, MediaHierarchyManager.LOCATION_QS, QS_TOP)
setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP)
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+ whenever(mediaDataManager.hasActiveMedia()).thenReturn(true)
whenever(mediaCarouselController.mediaCarouselScrollHandler)
.thenReturn(mediaCarouselScrollHandler)
val observer = wakefullnessObserver.value
@@ -357,17 +361,31 @@
}
@Test
- fun isCurrentlyInGuidedTransformation_hostNotVisible_returnsTrue() {
+ fun isCurrentlyInGuidedTransformation_hostNotVisible_returnsFalse_with_active() {
goToLockscreen()
enterGuidedTransformation()
whenever(lockHost.visible).thenReturn(false)
whenever(qsHost.visible).thenReturn(true)
whenever(qqsHost.visible).thenReturn(true)
+ whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse()
}
@Test
+ fun isCurrentlyInGuidedTransformation_hostNotVisible_returnsTrue_without_active() {
+ // To keep the appearing behavior, we need to be in a guided transition
+ goToLockscreen()
+ enterGuidedTransformation()
+ whenever(lockHost.visible).thenReturn(false)
+ whenever(qsHost.visible).thenReturn(true)
+ whenever(qqsHost.visible).thenReturn(true)
+ whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
+
+ assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isTrue()
+ }
+
+ @Test
fun testDream() {
goToDream()
setMediaDreamComplicationEnabled(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 35b0eb6..4ed6d7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -22,13 +22,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.CONTROLS_DELAY
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DETAILS_DELAY
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIATITLES_DELAY
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.TRANSFORM_BEZIER
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionViewState
@@ -60,9 +53,10 @@
@Mock private lateinit var controlWidgetState: WidgetState
@Mock private lateinit var bgWidgetState: WidgetState
@Mock private lateinit var mediaTitleWidgetState: WidgetState
+ @Mock private lateinit var mediaSubTitleWidgetState: WidgetState
@Mock private lateinit var mediaContainerWidgetState: WidgetState
- val delta = 0.0001F
+ val delta = 0.1F
private lateinit var mediaViewController: MediaViewController
@@ -76,10 +70,11 @@
@Test
fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() {
mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
- player.measureState = TransitionViewState().apply {
- this.height = 100
- this.measureHeight = 100
- }
+ player.measureState =
+ TransitionViewState().apply {
+ this.height = 100
+ this.measureHeight = 100
+ }
mediaHostStateHolder.expansion = 1f
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
@@ -128,29 +123,21 @@
R.id.header_artist to detailWidgetState
)
)
-
- val detailSquishMiddle =
- TRANSFORM_BEZIER.getInterpolation(
- (DETAILS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
- )
- mediaViewController.squishViewState(mockViewState, detailSquishMiddle)
+ whenever(mockCopiedState.measureHeight).thenReturn(200)
+ // detail widgets occupy [90, 100]
+ whenever(detailWidgetState.y).thenReturn(90F)
+ whenever(detailWidgetState.height).thenReturn(10)
+ // control widgets occupy [150, 170]
+ whenever(controlWidgetState.y).thenReturn(150F)
+ whenever(controlWidgetState.height).thenReturn(20)
+ // in current beizer, when the progress reach 0.38, the result will be 0.5
+ mediaViewController.squishViewState(mockViewState, 119F / 200F)
verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
-
- val detailSquishEnd =
- TRANSFORM_BEZIER.getInterpolation((DETAILS_DELAY + DURATION) / ANIMATION_BASE_DURATION)
- mediaViewController.squishViewState(mockViewState, detailSquishEnd)
+ mediaViewController.squishViewState(mockViewState, 150F / 200F)
verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
-
- val controlSquishMiddle =
- TRANSFORM_BEZIER.getInterpolation(
- (CONTROLS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
- )
- mediaViewController.squishViewState(mockViewState, controlSquishMiddle)
+ mediaViewController.squishViewState(mockViewState, 181.4F / 200F)
verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
-
- val controlSquishEnd =
- TRANSFORM_BEZIER.getInterpolation((CONTROLS_DELAY + DURATION) / ANIMATION_BASE_DURATION)
- mediaViewController.squishViewState(mockViewState, controlSquishEnd)
+ mediaViewController.squishViewState(mockViewState, 200F / 200F)
verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
}
@@ -161,36 +148,33 @@
.thenReturn(
mutableMapOf(
R.id.media_title1 to mediaTitleWidgetState,
+ R.id.media_subtitle1 to mediaSubTitleWidgetState,
R.id.media_cover1_container to mediaContainerWidgetState
)
)
+ whenever(mockCopiedState.measureHeight).thenReturn(360)
+ // media container widgets occupy [20, 300]
+ whenever(mediaContainerWidgetState.y).thenReturn(20F)
+ whenever(mediaContainerWidgetState.height).thenReturn(280)
+ // media title widgets occupy [320, 330]
+ whenever(mediaTitleWidgetState.y).thenReturn(320F)
+ whenever(mediaTitleWidgetState.height).thenReturn(10)
+ // media subtitle widgets occupy [340, 350]
+ whenever(mediaSubTitleWidgetState.y).thenReturn(340F)
+ whenever(mediaSubTitleWidgetState.height).thenReturn(10)
- val containerSquishMiddle =
- TRANSFORM_BEZIER.getInterpolation(
- (MEDIACONTAINERS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
- )
- mediaViewController.squishViewState(mockViewState, containerSquishMiddle)
+ // in current beizer, when the progress reach 0.38, the result will be 0.5
+ mediaViewController.squishViewState(mockViewState, 307.6F / 360F)
verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
-
- val containerSquishEnd =
- TRANSFORM_BEZIER.getInterpolation(
- (MEDIACONTAINERS_DELAY + DURATION) / ANIMATION_BASE_DURATION
- )
- mediaViewController.squishViewState(mockViewState, containerSquishEnd)
+ mediaViewController.squishViewState(mockViewState, 320F / 360F)
verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
-
- val titleSquishMiddle =
- TRANSFORM_BEZIER.getInterpolation(
- (MEDIATITLES_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
- )
- mediaViewController.squishViewState(mockViewState, titleSquishMiddle)
+ // media title and media subtitle are in same widget group, should be calculate together and
+ // have same alpha
+ mediaViewController.squishViewState(mockViewState, 353.8F / 360F)
verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
-
- val titleSquishEnd =
- TRANSFORM_BEZIER.getInterpolation(
- (MEDIATITLES_DELAY + DURATION) / ANIMATION_BASE_DURATION
- )
- mediaViewController.squishViewState(mockViewState, titleSquishEnd)
+ verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
+ mediaViewController.squishViewState(mockViewState, 360F / 360F)
verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
+ verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 2ef2c5e..f5432e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -102,8 +102,6 @@
private MediaOutputController.Callback mCb = mock(MediaOutputController.Callback.class);
private MediaDevice mMediaDevice1 = mock(MediaDevice.class);
private MediaDevice mMediaDevice2 = mock(MediaDevice.class);
- private MediaItem mMediaItem1 = mock(MediaItem.class);
- private MediaItem mMediaItem2 = mock(MediaItem.class);
private NearbyDevice mNearbyDevice1 = mock(NearbyDevice.class);
private NearbyDevice mNearbyDevice2 = mock(NearbyDevice.class);
private MediaMetadata mMediaMetadata = mock(MediaMetadata.class);
@@ -127,7 +125,6 @@
private LocalMediaManager mLocalMediaManager;
private List<MediaController> mMediaControllers = new ArrayList<>();
private List<MediaDevice> mMediaDevices = new ArrayList<>();
- private List<MediaItem> mMediaItemList = new ArrayList<>();
private List<NearbyDevice> mNearbyDevices = new ArrayList<>();
private MediaDescription mMediaDescription;
private List<RoutingSessionInfo> mRoutingSessionInfos = new ArrayList<>();
@@ -162,10 +159,6 @@
when(mMediaDevice2.getId()).thenReturn(TEST_DEVICE_2_ID);
mMediaDevices.add(mMediaDevice1);
mMediaDevices.add(mMediaDevice2);
- when(mMediaItem1.getMediaDevice()).thenReturn(Optional.of(mMediaDevice1));
- when(mMediaItem2.getMediaDevice()).thenReturn(Optional.of(mMediaDevice2));
- mMediaItemList.add(mMediaItem1);
- mMediaItemList.add(mMediaItem2);
when(mNearbyDevice1.getMediaRoute2Id()).thenReturn(TEST_DEVICE_1_ID);
@@ -337,6 +330,35 @@
assertThat(devices.containsAll(mMediaDevices)).isTrue();
assertThat(devices.size()).isEqualTo(mMediaDevices.size());
+ assertThat(mMediaOutputController.getMediaItemList().size()).isEqualTo(
+ mMediaDevices.size() + 2);
+ verify(mCb).onDeviceListChanged();
+ }
+
+ @Test
+ public void advanced_categorizeMediaItems_withSuggestedDevice_verifyDeviceListSize() {
+ when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(true);
+ when(mMediaDevice1.isSuggestedDevice()).thenReturn(true);
+ when(mMediaDevice2.isSuggestedDevice()).thenReturn(false);
+
+ mMediaOutputController.start(mCb);
+ reset(mCb);
+ mMediaOutputController.getMediaItemList().clear();
+ mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ final List<MediaDevice> devices = new ArrayList<>();
+ int dividerSize = 0;
+ for (MediaItem item : mMediaOutputController.getMediaItemList()) {
+ if (item.getMediaDevice().isPresent()) {
+ devices.add(item.getMediaDevice().get());
+ }
+ if (item.getMediaItemType() == MediaItem.MediaItemType.TYPE_GROUP_DIVIDER) {
+ dividerSize++;
+ }
+ }
+
+ assertThat(devices.containsAll(mMediaDevices)).isTrue();
+ assertThat(devices.size()).isEqualTo(mMediaDevices.size());
+ assertThat(dividerSize).isEqualTo(2);
verify(mCb).onDeviceListChanged();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index 4cc12c7..f5b3959 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -206,6 +206,21 @@
}
@Test
+ fun commandQueueCallback_almostCloseToStartCast_deviceNameBlank_showsDefaultDeviceName() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfoWithBlankDeviceName,
+ null,
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getChipText())
+ .contains(context.getString(R.string.media_ttt_default_device_type))
+ assertThat(chipbarView.getChipText())
+ .isNotEqualTo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText())
+ }
+
+ @Test
fun commandQueueCallback_almostCloseToEndCast_triggersCorrectChip() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
@@ -248,6 +263,21 @@
}
@Test
+ fun commandQueueCallback_transferToReceiverTriggered_deviceNameBlank_showsDefaultDeviceName() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfoWithBlankDeviceName,
+ null,
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getChipText())
+ .contains(context.getString(R.string.media_ttt_default_device_type))
+ assertThat(chipbarView.getChipText())
+ .isNotEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
+ }
+
+ @Test
fun commandQueueCallback_transferToThisDeviceTriggered_triggersCorrectChip() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
@@ -934,6 +964,7 @@
private const val APP_NAME = "Fake app name"
private const val OTHER_DEVICE_NAME = "My Tablet"
+private const val BLANK_DEVICE_NAME = " "
private const val PACKAGE_NAME = "com.android.systemui"
private const val TIMEOUT = 10000
@@ -942,3 +973,9 @@
.addFeature("feature")
.setClientPackageName(PACKAGE_NAME)
.build()
+
+private val routeInfoWithBlankDeviceName =
+ MediaRoute2Info.Builder("id", BLANK_DEVICE_NAME)
+ .addFeature("feature")
+ .setClientPackageName(PACKAGE_NAME)
+ .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 4a9c750..fc90c1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -93,7 +93,7 @@
createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context).startActivity(notesIntent)
- verify(bubbles, never()).showAppBubble(notesIntent)
+ verify(bubbles, never()).showOrHideAppBubble(notesIntent)
}
@Test
@@ -102,7 +102,7 @@
createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
- verify(bubbles).showAppBubble(notesIntent)
+ verify(bubbles).showOrHideAppBubble(notesIntent)
verify(context, never()).startActivity(notesIntent)
}
@@ -113,7 +113,7 @@
createNoteTaskController().showNoteTask(isInMultiWindowMode = true)
verify(context).startActivity(notesIntent)
- verify(bubbles, never()).showAppBubble(notesIntent)
+ verify(bubbles, never()).showOrHideAppBubble(notesIntent)
}
@Test
@@ -123,7 +123,7 @@
createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context, never()).startActivity(notesIntent)
- verify(bubbles, never()).showAppBubble(notesIntent)
+ verify(bubbles, never()).showOrHideAppBubble(notesIntent)
}
@Test
@@ -133,7 +133,7 @@
createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context, never()).startActivity(notesIntent)
- verify(bubbles, never()).showAppBubble(notesIntent)
+ verify(bubbles, never()).showOrHideAppBubble(notesIntent)
}
@Test
@@ -143,7 +143,7 @@
createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context, never()).startActivity(notesIntent)
- verify(bubbles, never()).showAppBubble(notesIntent)
+ verify(bubbles, never()).showOrHideAppBubble(notesIntent)
}
@Test
@@ -153,7 +153,7 @@
createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context, never()).startActivity(notesIntent)
- verify(bubbles, never()).showAppBubble(notesIntent)
+ verify(bubbles, never()).showOrHideAppBubble(notesIntent)
}
@Test
@@ -161,7 +161,7 @@
createNoteTaskController(isEnabled = false).showNoteTask()
verify(context, never()).startActivity(notesIntent)
- verify(bubbles, never()).showAppBubble(notesIntent)
+ verify(bubbles, never()).showOrHideAppBubble(notesIntent)
}
@Test
@@ -171,7 +171,7 @@
createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context, never()).startActivity(notesIntent)
- verify(bubbles, never()).showAppBubble(notesIntent)
+ verify(bubbles, never()).showOrHideAppBubble(notesIntent)
}
// endregion
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
index ca3182a..3281fa9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -28,7 +28,6 @@
import com.android.systemui.qs.tiles.BluetoothTile
import com.android.systemui.qs.tiles.CameraToggleTile
import com.android.systemui.qs.tiles.CastTile
-import com.android.systemui.qs.tiles.CellularTile
import com.android.systemui.qs.tiles.ColorCorrectionTile
import com.android.systemui.qs.tiles.ColorInversionTile
import com.android.systemui.qs.tiles.DataSaverTile
@@ -49,7 +48,6 @@
import com.android.systemui.qs.tiles.RotationLockTile
import com.android.systemui.qs.tiles.ScreenRecordTile
import com.android.systemui.qs.tiles.UiModeNightTile
-import com.android.systemui.qs.tiles.WifiTile
import com.android.systemui.qs.tiles.WorkModeTile
import com.android.systemui.util.leak.GarbageMonitor
import com.google.common.truth.Truth.assertThat
@@ -63,10 +61,8 @@
import org.mockito.Mockito.`when` as whenever
private val specMap = mapOf(
- "wifi" to WifiTile::class.java,
"internet" to InternetTile::class.java,
"bt" to BluetoothTile::class.java,
- "cell" to CellularTile::class.java,
"dnd" to DndTile::class.java,
"inversion" to ColorInversionTile::class.java,
"airplane" to AirplaneModeTile::class.java,
@@ -102,10 +98,8 @@
@Mock(answer = Answers.RETURNS_SELF) private lateinit var customTileBuilder: CustomTile.Builder
@Mock private lateinit var customTile: CustomTile
- @Mock private lateinit var wifiTile: WifiTile
@Mock private lateinit var internetTile: InternetTile
@Mock private lateinit var bluetoothTile: BluetoothTile
- @Mock private lateinit var cellularTile: CellularTile
@Mock private lateinit var dndTile: DndTile
@Mock private lateinit var colorInversionTile: ColorInversionTile
@Mock private lateinit var airplaneTile: AirplaneModeTile
@@ -146,10 +140,8 @@
factory = QSFactoryImpl(
{ qsHost },
{ customTileBuilder },
- { wifiTile },
{ internetTile },
{ bluetoothTile },
- { cellularTile },
{ dndTile },
{ colorInversionTile },
{ airplaneTile },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index 08a90b7..18e40f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -30,7 +30,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.qs.QSUserSwitcherEvent
-import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.user.data.source.UserRecord
import org.junit.Assert.assertEquals
@@ -42,7 +41,6 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -152,15 +150,6 @@
assertNull(adapter.users.find { it.isManageUsers })
}
- @Test
- fun clickDismissDialog() {
- val shower: UserSwitchDialogController.DialogShower =
- mock(UserSwitchDialogController.DialogShower::class.java)
- adapter.injectDialogShower(shower)
- adapter.onUserListItemClicked(createUserRecord(current = true, guest = false), shower)
- verify(shower).dismiss()
- }
-
private fun createUserRecord(current: Boolean, guest: Boolean) =
UserRecord(
UserInfo(0 /* id */, "name", 0 /* flags */),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index fa1fedb..99c79b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -39,7 +39,6 @@
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR
import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW
@@ -101,7 +100,6 @@
}.`when`(requestProcessor).processAsync(/* request= */ any(), /* callback= */ any())
// Flipped in selected test cases
- flags.set(SCREENSHOT_REQUEST_PROCESSOR, false)
flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false)
service.attach(
@@ -149,31 +147,6 @@
}
@Test
- fun takeScreenshot_requestProcessorEnabled() {
- flags.set(SCREENSHOT_REQUEST_PROCESSOR, true)
-
- val request = ScreenshotRequest(
- TAKE_SCREENSHOT_FULLSCREEN,
- SCREENSHOT_KEY_CHORD,
- topComponent)
-
- service.handleRequest(request, { /* onSaved */ }, callback)
-
- verify(controller, times(1)).takeScreenshotFullscreen(
- eq(topComponent),
- /* onSavedListener = */ any(),
- /* requestCallback = */ any())
-
- assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
- val logEvent = eventLogger.get(0)
-
- assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
- logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id)
- assertEquals("Expected supplied package name",
- topComponent.packageName, eventLogger.get(0).packageName)
- }
-
- @Test
fun takeScreenshotProvidedImage() {
val bounds = Rect(50, 50, 150, 150)
val bitmap = makeHardwareBitmap(100, 100)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
new file mode 100644
index 0000000..bd04b3c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.util.FakeSharedPreferences;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class WorkProfileMessageControllerTest {
+ private static final String DEFAULT_LABEL = "default label";
+ private static final String BADGED_DEFAULT_LABEL = "badged default label";
+ private static final String APP_LABEL = "app label";
+ private static final String BADGED_APP_LABEL = "badged app label";
+ private static final UserHandle NON_WORK_USER = UserHandle.of(0);
+ private static final UserHandle WORK_USER = UserHandle.of(10);
+
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private Context mContext;
+ @Mock
+ private WorkProfileMessageController.WorkProfileMessageDisplay mMessageDisplay;
+ @Mock
+ private Drawable mActivityIcon;
+ @Mock
+ private Drawable mBadgedActivityIcon;
+ @Mock
+ private ActivityInfo mActivityInfo;
+ @Captor
+ private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
+
+ private FakeSharedPreferences mSharedPreferences = new FakeSharedPreferences();
+
+ private WorkProfileMessageController mMessageController;
+
+ @Before
+ public void setup() throws PackageManager.NameNotFoundException {
+ MockitoAnnotations.initMocks(this);
+
+ when(mUserManager.isManagedProfile(eq(WORK_USER.getIdentifier()))).thenReturn(true);
+ when(mContext.getSharedPreferences(
+ eq(WorkProfileMessageController.SHARED_PREFERENCES_NAME),
+ eq(Context.MODE_PRIVATE))).thenReturn(mSharedPreferences);
+ when(mContext.getString(ArgumentMatchers.anyInt())).thenReturn(DEFAULT_LABEL);
+ when(mPackageManager.getUserBadgedLabel(eq(DEFAULT_LABEL), any()))
+ .thenReturn(BADGED_DEFAULT_LABEL);
+ when(mPackageManager.getUserBadgedLabel(eq(APP_LABEL), any()))
+ .thenReturn(BADGED_APP_LABEL);
+ when(mPackageManager.getActivityIcon(any(ComponentName.class)))
+ .thenReturn(mActivityIcon);
+ when(mPackageManager.getUserBadgedIcon(
+ any(), any())).thenReturn(mBadgedActivityIcon);
+ when(mPackageManager.getActivityInfo(any(),
+ any(PackageManager.ComponentInfoFlags.class))).thenReturn(mActivityInfo);
+ when(mActivityInfo.loadLabel(eq(mPackageManager))).thenReturn(APP_LABEL);
+
+ mSharedPreferences.edit().putBoolean(
+ WorkProfileMessageController.PREFERENCE_KEY, false).apply();
+
+ mMessageController = new WorkProfileMessageController(mContext, mUserManager,
+ mPackageManager);
+ }
+
+ @Test
+ public void testOnScreenshotTaken_notManaged() {
+ mMessageController.onScreenshotTaken(NON_WORK_USER, mMessageDisplay);
+
+ verify(mMessageDisplay, never())
+ .showWorkProfileMessage(any(), nullable(Drawable.class), any());
+ }
+
+ @Test
+ public void testOnScreenshotTaken_alreadyDismissed() {
+ mSharedPreferences.edit().putBoolean(
+ WorkProfileMessageController.PREFERENCE_KEY, true).apply();
+
+ mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay);
+
+ verify(mMessageDisplay, never())
+ .showWorkProfileMessage(any(), nullable(Drawable.class), any());
+ }
+
+ @Test
+ public void testOnScreenshotTaken_packageNotFound()
+ throws PackageManager.NameNotFoundException {
+ when(mPackageManager.getActivityInfo(any(),
+ any(PackageManager.ComponentInfoFlags.class))).thenThrow(
+ new PackageManager.NameNotFoundException());
+
+ mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay);
+
+ verify(mMessageDisplay).showWorkProfileMessage(
+ eq(BADGED_DEFAULT_LABEL), eq(null), any());
+ }
+
+ @Test
+ public void testOnScreenshotTaken() {
+ mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay);
+
+ verify(mMessageDisplay).showWorkProfileMessage(
+ eq(BADGED_APP_LABEL), eq(mBadgedActivityIcon), mRunnableArgumentCaptor.capture());
+
+ // Dismiss hasn't been tapped, preference untouched.
+ assertFalse(
+ mSharedPreferences.getBoolean(WorkProfileMessageController.PREFERENCE_KEY, false));
+
+ mRunnableArgumentCaptor.getValue().run();
+
+ // After dismiss has been tapped, the setting should be updated.
+ assertTrue(
+ mSharedPreferences.getBoolean(WorkProfileMessageController.PREFERENCE_KEY, false));
+ }
+}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
new file mode 100644
index 0000000..3710281
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
@@ -0,0 +1,100 @@
+package com.android.systemui.settings
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.os.Handler
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.concurrent.futures.DirectExecutor
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+class UserTrackerImplReceiveTest : SysuiTestCase() {
+
+ companion object {
+
+ @JvmStatic
+ @Parameterized.Parameters
+ fun data(): Iterable<String> =
+ listOf(
+ Intent.ACTION_USER_INFO_CHANGED,
+ Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
+ Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
+ Intent.ACTION_MANAGED_PROFILE_ADDED,
+ Intent.ACTION_MANAGED_PROFILE_REMOVED,
+ Intent.ACTION_MANAGED_PROFILE_UNLOCKED
+ )
+ }
+
+ private val executor: Executor = DirectExecutor.INSTANCE
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var userManager: UserManager
+ @Mock(stubOnly = true) private lateinit var dumpManager: DumpManager
+ @Mock(stubOnly = true) private lateinit var handler: Handler
+
+ @Parameterized.Parameter lateinit var intentAction: String
+ @Mock private lateinit var callback: UserTracker.Callback
+ @Captor private lateinit var captor: ArgumentCaptor<List<UserInfo>>
+
+ private lateinit var tracker: UserTrackerImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(context.user).thenReturn(UserHandle.SYSTEM)
+ `when`(context.createContextAsUser(ArgumentMatchers.any(), anyInt())).thenReturn(context)
+
+ tracker = UserTrackerImpl(context, userManager, dumpManager, handler)
+ }
+
+ @Test
+ fun `calls callback and updates profiles when an intent received`() {
+ tracker.initialize(0)
+ tracker.addCallback(callback, executor)
+ val profileID = tracker.userId + 10
+
+ `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ val id = invocation.getArgument<Int>(0)
+ val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+ val infoProfile =
+ UserInfo(
+ id + 10,
+ "",
+ "",
+ UserInfo.FLAG_MANAGED_PROFILE,
+ UserManager.USER_TYPE_PROFILE_MANAGED
+ )
+ infoProfile.profileGroupId = id
+ listOf(info, infoProfile)
+ }
+
+ tracker.onReceive(context, Intent(intentAction))
+
+ verify(callback, times(0)).onUserChanged(anyInt(), any())
+ verify(callback, times(1)).onProfilesChanged(capture(captor))
+ assertThat(captor.value.map { it.id }).containsExactly(0, profileID)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index 52462c7..e65bbb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -124,6 +124,16 @@
verify(context).registerReceiverForAllUsers(
eq(tracker), capture(captor), isNull(), eq(handler))
+ with(captor.value) {
+ assertThat(countActions()).isEqualTo(7)
+ assertThat(hasAction(Intent.ACTION_USER_SWITCHED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_ADDED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)).isTrue()
+ }
}
@Test
@@ -280,37 +290,6 @@
}
@Test
- fun testCallbackCalledOnProfileChanged() {
- tracker.initialize(0)
- val callback = TestCallback()
- tracker.addCallback(callback, executor)
- val profileID = tracker.userId + 10
-
- `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
- val id = invocation.getArgument<Int>(0)
- val info = UserInfo(id, "", UserInfo.FLAG_FULL)
- val infoProfile = UserInfo(
- id + 10,
- "",
- "",
- UserInfo.FLAG_MANAGED_PROFILE,
- UserManager.USER_TYPE_PROFILE_MANAGED
- )
- infoProfile.profileGroupId = id
- listOf(info, infoProfile)
- }
-
- val intent = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
- .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
-
- tracker.onReceive(context, intent)
-
- assertThat(callback.calledOnUserChanged).isEqualTo(0)
- assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
- assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID)
- }
-
- @Test
fun testCallbackCalledOnUserInfoChanged() {
tracker.initialize(0)
val callback = TestCallback()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index 88651c1..f802a5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade
import android.testing.AndroidTestingRunner
+import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
@@ -92,12 +93,12 @@
assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
assertThat(getConstraint(R.id.date).layout.startToStart).isEqualTo(PARENT_ID)
- assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0f)
+ assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0.5f)
assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd)
.isEqualTo(PARENT_ID)
assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias)
- .isEqualTo(1f)
+ .isEqualTo(0.5f)
assertThat(getConstraint(R.id.privacy_container).layout.endToEnd)
.isEqualTo(R.id.end_guide)
@@ -331,10 +332,8 @@
val views = mapOf(
R.id.clock to "clock",
R.id.date to "date",
- R.id.statusIcons to "icons",
R.id.privacy_container to "privacy",
R.id.carrier_group to "carriers",
- R.id.batteryRemainingIcon to "battery",
)
views.forEach { (id, name) ->
assertWithMessage("$name has 0 height in qqs")
@@ -352,11 +351,8 @@
fun testCheckViewsDontChangeSizeBetweenAnimationConstraints() {
val views = mapOf(
R.id.clock to "clock",
- R.id.date to "date",
- R.id.statusIcons to "icons",
R.id.privacy_container to "privacy",
R.id.carrier_group to "carriers",
- R.id.batteryRemainingIcon to "battery",
)
views.forEach { (id, name) ->
expect.withMessage("$name changes height")
@@ -369,8 +365,8 @@
}
private fun Int.fromConstraint() = when (this) {
- -1 -> "MATCH_PARENT"
- -2 -> "WRAP_CONTENT"
+ ViewGroup.LayoutParams.MATCH_PARENT -> "MATCH_PARENT"
+ ViewGroup.LayoutParams.WRAP_CONTENT -> "WRAP_CONTENT"
else -> toString()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index 1d30ad9..f580f5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -182,6 +182,7 @@
null
}
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
+ whenever(view.alpha).thenReturn(1f)
whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index b4c8f98..b568122 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.shade
+import android.animation.ValueAnimator
import android.app.StatusBarManager
import android.content.Context
import android.testing.AndroidTestingRunner
@@ -30,6 +31,7 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -37,6 +39,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Answers
+import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.mock
@@ -75,6 +78,7 @@
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
var viewVisibility = View.GONE
+ var viewAlpha = 1f
private lateinit var mLargeScreenShadeHeaderController: LargeScreenShadeHeaderController
private lateinit var carrierIconSlots: List<String>
@@ -101,6 +105,13 @@
null
}
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
+
+ whenever(view.setAlpha(anyFloat())).then {
+ viewAlpha = it.arguments[0] as Float
+ null
+ }
+ whenever(view.alpha).thenAnswer { _ -> viewAlpha }
+
whenever(variableDateViewControllerFactory.create(any()))
.thenReturn(variableDateViewController)
whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
@@ -155,6 +166,16 @@
}
@Test
+ fun alphaChangesUpdateVisibility() {
+ makeShadeVisible()
+ mLargeScreenShadeHeaderController.shadeExpandedFraction = 0f
+ assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
+
+ mLargeScreenShadeHeaderController.shadeExpandedFraction = 1f
+ assertThat(viewVisibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
fun singleCarrier_enablesCarrierIconsInStatusIcons() {
whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(true)
@@ -239,6 +260,39 @@
}
@Test
+ fun testShadeExpanded_true_alpha_zero_invisible() {
+ view.alpha = 0f
+ mLargeScreenShadeHeaderController.largeScreenActive = true
+ mLargeScreenShadeHeaderController.qsVisible = true
+
+ assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ fun animatorCallsUpdateVisibilityOnUpdate() {
+ val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
+ whenever(view.animate()).thenReturn(animator)
+
+ mLargeScreenShadeHeaderController.startCustomizingAnimation(show = false, 0L)
+
+ val updateCaptor = argumentCaptor<ValueAnimator.AnimatorUpdateListener>()
+ verify(animator).setUpdateListener(capture(updateCaptor))
+
+ mLargeScreenShadeHeaderController.largeScreenActive = true
+ mLargeScreenShadeHeaderController.qsVisible = true
+
+ view.alpha = 1f
+ updateCaptor.value.onAnimationUpdate(mock())
+
+ assertThat(viewVisibility).isEqualTo(View.VISIBLE)
+
+ view.alpha = 0f
+ updateCaptor.value.onAnimationUpdate(mock())
+
+ assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
fun demoMode_attachDemoMode() {
val cb = argumentCaptor<DemoMode>()
verify(demoModeController).addCallback(capture(cb))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 65b2ac0..d0b42ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -103,10 +103,13 @@
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
@@ -293,8 +296,12 @@
@Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
@Mock private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
@Mock private OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel;
+ @Mock private LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel;
+ @Mock private LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel;
+
@Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock private CoroutineDispatcher mMainDispatcher;
+ @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
@Mock private MotionEvent mDownMotionEvent;
@Captor
private ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
@@ -511,8 +518,11 @@
systemClock,
mKeyguardBottomAreaViewModel,
mKeyguardBottomAreaInteractor,
+ mAlternateBouncerInteractor,
mDreamingToLockscreenTransitionViewModel,
mOccludedToLockscreenTransitionViewModel,
+ mLockscreenToDreamingTransitionViewModel,
+ mLockscreenToOccludedTransitionViewModel,
mMainDispatcher,
mKeyguardTransitionInteractor,
mDumpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index c3207c2..4c76825 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -30,6 +30,8 @@
import com.android.systemui.dock.DockManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -97,10 +99,13 @@
private lateinit var pulsingGestureListener: PulsingGestureListener
@Mock
private lateinit var notificationInsetsController: NotificationInsetsController
+ @Mock
+ private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerContainer: ViewGroup
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@Mock lateinit var keyguardHostViewController: KeyguardHostViewController
+ @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
private lateinit var interactionEventHandler: InteractionEventHandler
@@ -132,7 +137,9 @@
pulsingGestureListener,
featureFlags,
keyguardBouncerViewModel,
- keyguardBouncerComponentFactory
+ keyguardBouncerComponentFactory,
+ alternateBouncerInteractor,
+ keyguardTransitionInteractor,
)
underTest.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 4bf00c4..d435624 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -40,6 +40,8 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -93,6 +95,8 @@
@Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
@Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
@Mock private NotificationInsetsController mNotificationInsetsController;
+ @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
+ @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
mInteractionEventHandlerCaptor;
@@ -132,7 +136,9 @@
mPulsingGestureListener,
mFeatureFlags,
mKeyguardBouncerViewModel,
- mKeyguardBouncerComponentFactory
+ mKeyguardBouncerComponentFactory,
+ mAlternateBouncerInteractor,
+ mKeyguardTransitionInteractor
);
mController.setupExpandedStatusBar();
mController.setDragDownHelper(mDragDownHelper);
@@ -155,7 +161,7 @@
// WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(true);
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we should intercept touch
@@ -168,7 +174,7 @@
// WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(false);
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we shouldn't intercept touch
@@ -181,7 +187,7 @@
// WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(true);
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we should handle the touch
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index d2dd433..610bb13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -99,6 +99,7 @@
import com.android.systemui.keyguard.KeyguardIndication;
import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -177,6 +178,8 @@
@Mock
private FaceHelpMessageDeferral mFaceHelpMessageDeferral;
@Mock
+ private AlternateBouncerInteractor mAlternateBouncerInteractor;
+ @Mock
private ScreenLifecycle mScreenLifecycle;
@Mock
private AuthController mAuthController;
@@ -273,7 +276,8 @@
mUserManager, mExecutor, mExecutor, mFalsingManager,
mAuthController, mLockPatternUtils, mScreenLifecycle,
mKeyguardBypassController, mAccessibilityManager,
- mFaceHelpMessageDeferral, mock(KeyguardLogger.class));
+ mFaceHelpMessageDeferral, mock(KeyguardLogger.class),
+ mAlternateBouncerInteractor);
mController.init();
mController.setIndicationArea(mIndicationArea);
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
index 8275c0c..9b3626b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
@@ -127,6 +127,6 @@
NotificationManager.IMPORTANCE_DEFAULT,
null, null,
null, null, null, true, 0, false, -1, false, null, null, false, false,
- false, null, 0, false)
+ false, null, 0, false, 0)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index ca99e24..e41929f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -29,6 +29,7 @@
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
import org.junit.Assert;
import org.junit.Before;
@@ -216,4 +217,29 @@
Assert.assertEquals(1f, mChildrenContainer.getBottomRoundness(), 0.001f);
Assert.assertEquals(1f, notificationRow.getBottomRoundness(), 0.001f);
}
+
+ @Test
+ public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_header() {
+ mChildrenContainer.useRoundnessSourceTypes(true);
+
+ NotificationHeaderViewWrapper header = mChildrenContainer.getNotificationHeaderWrapper();
+ Assert.assertEquals(0f, header.getTopRoundness(), 0.001f);
+
+ mChildrenContainer.requestTopRoundness(1f, SourceType.from(""), false);
+
+ Assert.assertEquals(1f, header.getTopRoundness(), 0.001f);
+ }
+
+ @Test
+ public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_headerLowPriority() {
+ mChildrenContainer.useRoundnessSourceTypes(true);
+ mChildrenContainer.setIsLowPriority(true);
+
+ NotificationHeaderViewWrapper header = mChildrenContainer.getNotificationHeaderWrapper();
+ Assert.assertEquals(0f, header.getTopRoundness(), 0.001f);
+
+ mChildrenContainer.requestTopRoundness(1f, SourceType.from(""), false);
+
+ Assert.assertEquals(1f, header.getTopRoundness(), 0.001f);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 4ccbc6d..091bb54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNotNull;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doReturn;
@@ -74,6 +75,7 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
+import org.mockito.stubbing.Answer;
import java.util.Collections;
import java.util.List;
@@ -115,8 +117,10 @@
@Spy private PackageManager mPackageManager;
private final boolean mIsReduceBrightColorsAvailable = true;
- private AutoTileManager mAutoTileManager;
+ private AutoTileManager mAutoTileManager; // under test
+
private SecureSettings mSecureSettings;
+ private ManagedProfileController.Callback mManagedProfileCallback;
@Before
public void setUp() throws Exception {
@@ -303,7 +307,7 @@
InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
- inOrderManagedProfile.verify(mManagedProfileController, never()).addCallback(any());
+ inOrderManagedProfile.verify(mManagedProfileController).addCallback(any());
if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
@@ -504,6 +508,40 @@
}
@Test
+ public void managedProfileAdded_tileAdded() {
+ when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(false);
+ mAutoTileManager = createAutoTileManager(mContext);
+ Mockito.doAnswer((Answer<Object>) invocation -> {
+ mManagedProfileCallback = invocation.getArgument(0);
+ return null;
+ }).when(mManagedProfileController).addCallback(any());
+ mAutoTileManager.init();
+ when(mManagedProfileController.hasActiveProfile()).thenReturn(true);
+
+ mManagedProfileCallback.onManagedProfileChanged();
+
+ verify(mQsTileHost, times(1)).addTile(eq("work"));
+ verify(mAutoAddTracker, times(1)).setTileAdded(eq("work"));
+ }
+
+ @Test
+ public void managedProfileRemoved_tileRemoved() {
+ when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(true);
+ mAutoTileManager = createAutoTileManager(mContext);
+ Mockito.doAnswer((Answer<Object>) invocation -> {
+ mManagedProfileCallback = invocation.getArgument(0);
+ return null;
+ }).when(mManagedProfileController).addCallback(any());
+ mAutoTileManager.init();
+ when(mManagedProfileController.hasActiveProfile()).thenReturn(false);
+
+ mManagedProfileCallback.onManagedProfileChanged();
+
+ verify(mQsTileHost, times(1)).removeTile(eq("work"));
+ verify(mAutoAddTracker, times(1)).setTileRemoved(eq("work"));
+ }
+
+ @Test
public void testEmptyArray_doesNotCrash() {
mContext.getOrCreateTestableResources().addOverride(
R.array.config_quickSettingsAutoAdd, new String[0]);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 74f8c61..daf7dd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -57,6 +59,7 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -122,6 +125,7 @@
private VibratorHelper mVibratorHelper;
@Mock
private BiometricUnlockLogger mLogger;
+ private final FakeSystemClock mSystemClock = new FakeSystemClock();
private BiometricUnlockController mBiometricUnlockController;
@Before
@@ -144,7 +148,9 @@
mMetricsLogger, mDumpManager, mPowerManager, mLogger,
mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController,
- mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper);
+ mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,
+ mSystemClock
+ );
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener);
when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker);
@@ -207,7 +213,7 @@
verify(mKeyguardViewMediator).onWakeAndUnlocking();
assertThat(mBiometricUnlockController.getMode())
- .isEqualTo(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
+ .isEqualTo(MODE_WAKE_AND_UNLOCK);
}
@Test
@@ -457,4 +463,83 @@
// THEN wakeup the device
verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
}
+
+ @Test
+ public void onSideFingerprintSuccess_recentPowerButtonPress_noHaptic() {
+ // GIVEN side fingerprint enrolled, last wake reason was power button
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+ when(mWakefulnessLifecycle.getLastWakeReason())
+ .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
+
+ // GIVEN last wake time just occurred
+ when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
+
+ // WHEN biometric fingerprint succeeds
+ givenFingerprintModeUnlockCollapsing();
+ mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
+ true);
+
+ // THEN DO NOT vibrate the device
+ verify(mVibratorHelper, never()).vibrateAuthSuccess(anyString());
+ }
+
+ @Test
+ public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() {
+ // GIVEN side fingerprint enrolled, last wake reason was power button
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+ when(mWakefulnessLifecycle.getLastWakeReason())
+ .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
+
+ // GIVEN last wake time was 500ms ago
+ when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
+ mSystemClock.advanceTime(500);
+
+ // WHEN biometric fingerprint succeeds
+ givenFingerprintModeUnlockCollapsing();
+ mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
+ true);
+
+ // THEN vibrate the device
+ verify(mVibratorHelper).vibrateAuthSuccess(anyString());
+ }
+
+ @Test
+ public void onSideFingerprintSuccess_recentGestureWakeUp_playHaptic() {
+ // GIVEN side fingerprint enrolled, wakeup just happened
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+ when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
+
+ // GIVEN last wake reason was from a gesture
+ when(mWakefulnessLifecycle.getLastWakeReason())
+ .thenReturn(PowerManager.WAKE_REASON_GESTURE);
+
+ // WHEN biometric fingerprint succeeds
+ givenFingerprintModeUnlockCollapsing();
+ mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
+ true);
+
+ // THEN vibrate the device
+ verify(mVibratorHelper).vibrateAuthSuccess(anyString());
+ }
+
+ @Test
+ public void onSideFingerprintFail_alwaysPlaysHaptic() {
+ // GIVEN side fingerprint enrolled, last wake reason was recent power button
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+ when(mWakefulnessLifecycle.getLastWakeReason())
+ .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
+ when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
+
+ // WHEN biometric fingerprint fails
+ mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+
+ // THEN always vibrate the device
+ verify(mVibratorHelper).vibrateAuthError(anyString());
+ }
+
+ private void givenFingerprintModeUnlockCollapsing() {
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 09254ad..c8157cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -110,6 +110,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
@@ -177,8 +178,6 @@
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.startingsurface.StartingSurface;
-import dagger.Lazy;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -191,6 +190,8 @@
import java.io.PrintWriter;
import java.util.Optional;
+import dagger.Lazy;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@@ -297,6 +298,7 @@
@Mock private WiredChargingRippleController mWiredChargingRippleController;
@Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
@Mock private CameraLauncher mCameraLauncher;
+ @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
/**
* The process of registering/unregistering a predictive back callback requires a
* ViewRootImpl, which is present IRL, but may be missing during a Mockito unit test.
@@ -378,7 +380,8 @@
}).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
mWakefulnessLifecycle =
- new WakefulnessLifecycle(mContext, mIWallpaperManager, mDumpManager);
+ new WakefulnessLifecycle(mContext, mIWallpaperManager, mFakeSystemClock,
+ mDumpManager);
mWakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
mWakefulnessLifecycle.dispatchFinishedWakingUp();
@@ -504,7 +507,9 @@
mWiredChargingRippleController,
mDreamManager,
mCameraLauncherLazy,
- () -> mLightRevealScrimViewModel) {
+ () -> mLightRevealScrimViewModel,
+ mAlternateBouncerInteractor
+ ) {
@Override
protected ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 077b41a..c843850 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -23,6 +23,10 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
@@ -39,10 +43,9 @@
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.unfold.FoldAodAnimationController;
@@ -52,6 +55,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -69,7 +74,6 @@
@Mock private PowerManager mPowerManager;
@Mock private TunerService mTunerService;
@Mock private BatteryController mBatteryController;
- @Mock private FeatureFlags mFeatureFlags;
@Mock private DumpManager mDumpManager;
@Mock private ScreenOffAnimationController mScreenOffAnimationController;
@Mock private FoldAodAnimationController mFoldAodAnimationController;
@@ -78,6 +82,7 @@
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private ConfigurationController mConfigurationController;
+ @Captor private ArgumentCaptor<BatteryStateChangeCallback> mBatteryStateChangeCallback;
/**
* The current value of PowerManager's dozeAfterScreenOff property.
@@ -113,7 +118,6 @@
mBatteryController,
mTunerService,
mDumpManager,
- mFeatureFlags,
mScreenOffAnimationController,
Optional.of(mSysUIUnfoldComponent),
mUnlockedScreenOffAnimationController,
@@ -122,7 +126,8 @@
mStatusBarStateController
);
- when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(true);
+ verify(mBatteryController).addCallback(mBatteryStateChangeCallback.capture());
+
setAodEnabledForTest(true);
setShouldControlUnlockedScreenOffForTest(true);
setDisplayNeedsBlankingForTest(false);
@@ -173,6 +178,29 @@
assertThat(mDozeParameters.getAlwaysOn()).isFalse();
}
+ @Test
+ public void testGetAlwaysOn_whenBatterySaverCallback() {
+ DozeParameters.Callback callback = mock(DozeParameters.Callback.class);
+ mDozeParameters.addCallback(callback);
+
+ when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mBatteryController.isAodPowerSave()).thenReturn(true);
+
+ // Both lines should trigger an event
+ mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1");
+ mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true);
+
+ verify(callback, times(2)).onAlwaysOnChange();
+ assertThat(mDozeParameters.getAlwaysOn()).isFalse();
+
+ reset(callback);
+ when(mBatteryController.isAodPowerSave()).thenReturn(false);
+ mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true);
+
+ verify(callback).onAlwaysOnChange();
+ assertThat(mDozeParameters.getAlwaysOn()).isTrue();
+ }
+
/**
* PowerManager.setDozeAfterScreenOff(true) means we are not controlling screen off, and calling
* it with false means we are. Confusing, but sure - make sure that we call PowerManager with
@@ -196,17 +224,6 @@
}
@Test
- public void testControlUnlockedScreenOffAnimationDisabled_dozeAfterScreenOff() {
- when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(false);
-
- assertFalse(mDozeParameters.shouldControlUnlockedScreenOff());
-
- // Trigger the setter for the current value.
- mDozeParameters.setControlScreenOffAnimation(mDozeParameters.shouldControlScreenOff());
- assertFalse(mDozeParameters.shouldControlScreenOff());
- }
-
- @Test
public void propagatesAnimateScreenOff_noAlwaysOn() {
setAodEnabledForTest(false);
setDisplayNeedsBlankingForTest(false);
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 14a319b..04a6700 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
@@ -56,6 +56,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.data.BouncerView;
import com.android.systemui.keyguard.data.BouncerViewDelegate;
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -105,7 +106,6 @@
@Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory;
@Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
@Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
- @Mock private StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
@Mock private KeyguardMessageArea mKeyguardMessageArea;
@Mock private ShadeController mShadeController;
@Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
@@ -115,6 +115,7 @@
@Mock private KeyguardSecurityModel mKeyguardSecurityModel;
@Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
@Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
@Mock private BouncerView mBouncerView;
@Mock private BouncerViewDelegate mBouncerViewDelegate;
@@ -163,7 +164,8 @@
mFeatureFlags,
mPrimaryBouncerCallbackInteractor,
mPrimaryBouncerInteractor,
- mBouncerView) {
+ mBouncerView,
+ mAlternateBouncerInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -434,37 +436,35 @@
@Test
public void testShowing_whenAlternateAuthShowing() {
- mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
- when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
assertTrue(
- "Is showing not accurate when alternative auth showing",
+ "Is showing not accurate when alternative bouncer is visible",
mStatusBarKeyguardViewManager.isBouncerShowing());
}
@Test
public void testWillBeShowing_whenAlternateAuthShowing() {
- mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
- when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
assertTrue(
- "Is or will be showing not accurate when alternative auth showing",
+ "Is or will be showing not accurate when alternate bouncer is visible",
mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
}
@Test
- public void testHideAlternateBouncer_onShowBouncer() {
- // GIVEN alt auth is showing
- mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ public void testHideAlternateBouncer_onShowPrimaryBouncer() {
+ reset(mAlternateBouncerInteractor);
+
+ // GIVEN alt bouncer is showing
when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
- when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
- reset(mAlternateBouncer);
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
// WHEN showBouncer is called
mStatusBarKeyguardViewManager.showPrimaryBouncer(true);
// THEN alt bouncer should be hidden
- verify(mAlternateBouncer).hideAlternateBouncer();
+ verify(mAlternateBouncerInteractor).hide();
}
@Test
@@ -479,11 +479,9 @@
@Test
public void testShowAltAuth_unlockingWithBiometricNotAllowed() {
- // GIVEN alt auth exists, unlocking with biometric isn't allowed
- mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ // GIVEN cannot use alternate bouncer
when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
- when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
- .thenReturn(false);
+ when(mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()).thenReturn(false);
// WHEN showGenericBouncer is called
final boolean scrimmed = true;
@@ -491,21 +489,19 @@
// THEN regular bouncer is shown
verify(mPrimaryBouncerInteractor).show(eq(scrimmed));
- verify(mAlternateBouncer, never()).showAlternateBouncer();
}
@Test
public void testShowAlternateBouncer_unlockingWithBiometricAllowed() {
- // GIVEN alt auth exists, unlocking with biometric is allowed
- mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ // GIVEN will show alternate bouncer
when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
- when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ when(mAlternateBouncerInteractor.show()).thenReturn(true);
// WHEN showGenericBouncer is called
mStatusBarKeyguardViewManager.showBouncer(true);
// THEN alt auth bouncer is shown
- verify(mAlternateBouncer).showAlternateBouncer();
+ verify(mAlternateBouncerInteractor).show();
verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
}
@@ -613,7 +609,8 @@
mFeatureFlags,
mPrimaryBouncerCallbackInteractor,
mPrimaryBouncerInteractor,
- mBouncerView) {
+ mBouncerView,
+ mAlternateBouncerInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java
index 96fba39..a9c55fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java
@@ -56,6 +56,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.data.BouncerView;
import com.android.systemui.keyguard.data.BouncerViewDelegate;
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -109,7 +110,6 @@
@Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
@Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
@Mock private KeyguardBouncer mPrimaryBouncer;
- @Mock private StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
@Mock private KeyguardMessageArea mKeyguardMessageArea;
@Mock private ShadeController mShadeController;
@Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
@@ -119,6 +119,7 @@
@Mock private KeyguardSecurityModel mKeyguardSecurityModel;
@Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
@Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
@Mock private BouncerView mBouncerView;
@Mock private BouncerViewDelegate mBouncerViewDelegate;
@@ -169,7 +170,8 @@
mFeatureFlags,
mPrimaryBouncerCallbackInteractor,
mPrimaryBouncerInteractor,
- mBouncerView) {
+ mBouncerView,
+ mAlternateBouncerInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -439,41 +441,6 @@
}
@Test
- public void testShowing_whenAlternateAuthShowing() {
- mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
- when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
- assertTrue(
- "Is showing not accurate when alternative auth showing",
- mStatusBarKeyguardViewManager.isBouncerShowing());
- }
-
- @Test
- public void testWillBeShowing_whenAlternateAuthShowing() {
- mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
- when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
- assertTrue(
- "Is or will be showing not accurate when alternative auth showing",
- mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
- }
-
- @Test
- public void testHideAlternateBouncer_onShowBouncer() {
- // GIVEN alt auth is showing
- mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
- when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
- reset(mAlternateBouncer);
-
- // WHEN showBouncer is called
- mStatusBarKeyguardViewManager.showPrimaryBouncer(true);
-
- // THEN alt bouncer should be hidden
- verify(mAlternateBouncer).hideAlternateBouncer();
- }
-
- @Test
public void testBouncerIsOrWillBeShowing_whenBouncerIsInTransit() {
when(mPrimaryBouncer.isShowing()).thenReturn(false);
when(mPrimaryBouncer.inTransit()).thenReturn(true);
@@ -484,38 +451,6 @@
}
@Test
- public void testShowAltAuth_unlockingWithBiometricNotAllowed() {
- // GIVEN alt auth exists, unlocking with biometric isn't allowed
- mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
- when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
- .thenReturn(false);
-
- // WHEN showGenericBouncer is called
- final boolean scrimmed = true;
- mStatusBarKeyguardViewManager.showBouncer(scrimmed);
-
- // THEN regular bouncer is shown
- verify(mPrimaryBouncer).show(anyBoolean(), eq(scrimmed));
- verify(mAlternateBouncer, never()).showAlternateBouncer();
- }
-
- @Test
- public void testShowAlternateBouncer_unlockingWithBiometricAllowed() {
- // GIVEN alt auth exists, unlocking with biometric is allowed
- mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
- when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
-
- // WHEN showGenericBouncer is called
- mStatusBarKeyguardViewManager.showBouncer(true);
-
- // THEN alt auth bouncer is shown
- verify(mAlternateBouncer).showAlternateBouncer();
- verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
- }
-
- @Test
public void testUpdateResources_delegatesToBouncer() {
mStatusBarKeyguardViewManager.updateResources();
@@ -628,7 +563,8 @@
mFeatureFlags,
mPrimaryBouncerCallbackInteractor,
mPrimaryBouncerInteractor,
- mBouncerView) {
+ mBouncerView,
+ mAlternateBouncerInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 0da15e2..0958970 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -37,6 +37,7 @@
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
@@ -46,6 +47,7 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -94,7 +96,7 @@
}
}
- whenever(logBufferFactory.create(anyString(), anyInt())).thenAnswer { _ ->
+ whenever(logBufferFactory.getOrCreate(anyString(), anyInt())).thenAnswer { _ ->
mock<TableLogBuffer>()
}
@@ -292,13 +294,13 @@
// Get repos to trigger creation
underTest.getRepoForSubId(SUB_1_ID)
verify(logBufferFactory)
- .create(
+ .getOrCreate(
eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_1_ID)),
anyInt(),
)
underTest.getRepoForSubId(SUB_2_ID)
verify(logBufferFactory)
- .create(
+ .getOrCreate(
eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_2_ID)),
anyInt(),
)
@@ -307,6 +309,46 @@
}
@Test
+ fun `connection repository factory - reuses log buffers for same connection`() =
+ runBlocking(IMMEDIATE) {
+ val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock())
+
+ connectionFactory =
+ MobileConnectionRepositoryImpl.Factory(
+ fakeBroadcastDispatcher,
+ context = context,
+ telephonyManager = telephonyManager,
+ bgDispatcher = IMMEDIATE,
+ globalSettings = globalSettings,
+ logger = logger,
+ mobileMappingsProxy = mobileMappings,
+ scope = scope,
+ logFactory = realLoggerFactory,
+ )
+
+ // Create two connections for the same subId. Similar to if the connection appeared
+ // and disappeared from the connectionFactory's perspective
+ val connection1 =
+ connectionFactory.build(
+ 1,
+ NetworkNameModel.Default("default_name"),
+ "-",
+ underTest.globalMobileDataSettingChangedEvent,
+ )
+
+ val connection1_repeat =
+ connectionFactory.build(
+ 1,
+ NetworkNameModel.Default("default_name"),
+ "-",
+ underTest.globalMobileDataSettingChangedEvent,
+ )
+
+ assertThat(connection1.tableLogBuffer)
+ .isSameInstanceAs(connection1_repeat.tableLogBuffer)
+ }
+
+ @Test
fun mobileConnectivity_default() {
assertThat(underTest.defaultMobileNetworkConnectivity.value)
.isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 4b32ee2..0cca7b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -390,19 +390,27 @@
bindController(view, row.getEntry());
view.setVisibility(View.GONE);
- View crossFadeView = new View(mContext);
+ View fadeOutView = new View(mContext);
+ fadeOutView.setId(com.android.internal.R.id.actions_container_layout);
+
+ FrameLayout parent = new FrameLayout(mContext);
+ parent.addView(view);
+ parent.addView(fadeOutView);
// Start focus animation
- view.focusAnimated(crossFadeView);
-
+ view.focusAnimated();
assertTrue(view.isAnimatingAppearance());
- // fast forward to end of animation
- mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD);
+ // fast forward to 1 ms before end of animation and verify fadeOutView has alpha set to 0f
+ mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD - 1);
+ assertEquals(0f, fadeOutView.getAlpha());
- // assert that crossFadeView's alpha is reset to 1f after the animation (hidden behind
+ // fast forward to end of animation
+ mAnimatorTestRule.advanceTimeBy(1);
+
+ // assert that fadeOutView's alpha is reset to 1f after the animation (hidden behind
// RemoteInputView)
- assertEquals(1f, crossFadeView.getAlpha());
+ assertEquals(1f, fadeOutView.getAlpha());
assertFalse(view.isAnimatingAppearance());
assertEquals(View.VISIBLE, view.getVisibility());
assertEquals(1f, view.getAlpha());
@@ -415,20 +423,27 @@
mDependency,
TestableLooper.get(this));
ExpandableNotificationRow row = helper.createRow();
- FrameLayout remoteInputViewParent = new FrameLayout(mContext);
RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
- remoteInputViewParent.addView(view);
bindController(view, row.getEntry());
+ View fadeInView = new View(mContext);
+ fadeInView.setId(com.android.internal.R.id.actions_container_layout);
+
+ FrameLayout parent = new FrameLayout(mContext);
+ parent.addView(view);
+ parent.addView(fadeInView);
+
// Start defocus animation
- view.onDefocus(true, false);
+ view.onDefocus(true /* animate */, false /* logClose */, null /* doAfterDefocus */);
assertEquals(View.VISIBLE, view.getVisibility());
+ assertEquals(0f, fadeInView.getAlpha());
// fast forward to end of animation
mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD);
// assert that RemoteInputView is no longer visible
assertEquals(View.GONE, view.getVisibility());
+ assertEquals(1f, fadeInView.getAlpha());
}
// NOTE: because we're refactoring the RemoteInputView and moving logic into the
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt
new file mode 100644
index 0000000..7e01088
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.stylus
+
+import android.hardware.BatteryState
+
+class FixedCapacityBatteryState(private val capacity: Float) : BatteryState() {
+ override fun getCapacity() = capacity
+ override fun getStatus() = 0
+ override fun isPresent() = true
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusFirstUsageListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusFirstUsageListenerTest.kt
deleted file mode 100644
index 8dd088f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusFirstUsageListenerTest.kt
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.stylus
-
-import android.content.Context
-import android.hardware.BatteryState
-import android.hardware.input.InputManager
-import android.os.Handler
-import android.testing.AndroidTestingRunner
-import android.view.InputDevice
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-@Ignore("TODO(b/20579491): unignore on main")
-class StylusFirstUsageListenerTest : SysuiTestCase() {
- @Mock lateinit var context: Context
- @Mock lateinit var inputManager: InputManager
- @Mock lateinit var stylusManager: StylusManager
- @Mock lateinit var featureFlags: FeatureFlags
- @Mock lateinit var internalStylusDevice: InputDevice
- @Mock lateinit var otherDevice: InputDevice
- @Mock lateinit var externalStylusDevice: InputDevice
- @Mock lateinit var batteryState: BatteryState
- @Mock lateinit var handler: Handler
-
- private lateinit var stylusListener: StylusFirstUsageListener
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- whenever(featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)).thenReturn(true)
- whenever(inputManager.isStylusEverUsed(context)).thenReturn(false)
-
- stylusListener =
- StylusFirstUsageListener(
- context,
- inputManager,
- stylusManager,
- featureFlags,
- EXECUTOR,
- handler
- )
- stylusListener.hasStarted = false
-
- whenever(handler.post(any())).thenAnswer {
- (it.arguments[0] as Runnable).run()
- true
- }
-
- whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false)
- whenever(internalStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
- whenever(internalStylusDevice.isExternal).thenReturn(false)
- whenever(externalStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
- whenever(externalStylusDevice.isExternal).thenReturn(true)
-
- whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf())
- whenever(inputManager.getInputDevice(OTHER_DEVICE_ID)).thenReturn(otherDevice)
- whenever(inputManager.getInputDevice(INTERNAL_STYLUS_DEVICE_ID))
- .thenReturn(internalStylusDevice)
- whenever(inputManager.getInputDevice(EXTERNAL_STYLUS_DEVICE_ID))
- .thenReturn(externalStylusDevice)
- }
-
- @Test
- fun start_flagDisabled_doesNotRegister() {
- whenever(featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)).thenReturn(false)
-
- stylusListener.start()
-
- verify(stylusManager, never()).registerCallback(any())
- verify(inputManager, never()).setStylusEverUsed(context, true)
- }
-
- @Test
- fun start_toggleHasStarted() {
- stylusListener.start()
-
- assert(stylusListener.hasStarted)
- }
-
- @Test
- fun start_hasStarted_doesNotRegister() {
- stylusListener.hasStarted = true
-
- stylusListener.start()
-
- verify(stylusManager, never()).registerCallback(any())
- }
-
- @Test
- fun start_hostDeviceDoesNotSupportStylus_doesNotRegister() {
- whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(OTHER_DEVICE_ID))
-
- stylusListener.start()
-
- verify(stylusManager, never()).registerCallback(any())
- verify(inputManager, never()).setStylusEverUsed(context, true)
- }
-
- @Test
- fun start_stylusEverUsed_doesNotRegister() {
- whenever(inputManager.inputDeviceIds)
- .thenReturn(intArrayOf(OTHER_DEVICE_ID, INTERNAL_STYLUS_DEVICE_ID))
- whenever(inputManager.isStylusEverUsed(context)).thenReturn(true)
-
- stylusListener.start()
-
- verify(stylusManager, never()).registerCallback(any())
- verify(inputManager, never()).setStylusEverUsed(context, true)
- }
-
- @Test
- fun start_hostDeviceSupportsStylus_registersListener() {
- whenever(inputManager.inputDeviceIds)
- .thenReturn(intArrayOf(OTHER_DEVICE_ID, INTERNAL_STYLUS_DEVICE_ID))
-
- stylusListener.start()
-
- verify(stylusManager).registerCallback(any())
- verify(inputManager, never()).setStylusEverUsed(context, true)
- }
-
- @Test
- fun onStylusAdded_hasNotStarted_doesNotRegisterListener() {
- stylusListener.hasStarted = false
-
- stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
-
- verifyZeroInteractions(inputManager)
- }
-
- @Test
- fun onStylusAdded_internalStylus_registersListener() {
- stylusListener.hasStarted = true
-
- stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
-
- verify(inputManager, times(1))
- .addInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, EXECUTOR, stylusListener)
- }
-
- @Test
- fun onStylusAdded_externalStylus_doesNotRegisterListener() {
- stylusListener.hasStarted = true
-
- stylusListener.onStylusAdded(EXTERNAL_STYLUS_DEVICE_ID)
-
- verify(inputManager, never()).addInputDeviceBatteryListener(any(), any(), any())
- }
-
- @Test
- fun onStylusAdded_otherDevice_doesNotRegisterListener() {
- stylusListener.onStylusAdded(OTHER_DEVICE_ID)
-
- verify(inputManager, never()).addInputDeviceBatteryListener(any(), any(), any())
- }
-
- @Test
- fun onStylusRemoved_registeredDevice_unregistersListener() {
- stylusListener.hasStarted = true
- stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
-
- stylusListener.onStylusRemoved(INTERNAL_STYLUS_DEVICE_ID)
-
- verify(inputManager, times(1))
- .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener)
- }
-
- @Test
- fun onStylusRemoved_hasNotStarted_doesNotUnregisterListener() {
- stylusListener.hasStarted = false
- stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
-
- stylusListener.onStylusRemoved(INTERNAL_STYLUS_DEVICE_ID)
-
- verifyZeroInteractions(inputManager)
- }
-
- @Test
- fun onStylusRemoved_unregisteredDevice_doesNotUnregisterListener() {
- stylusListener.hasStarted = true
-
- stylusListener.onStylusRemoved(INTERNAL_STYLUS_DEVICE_ID)
-
- verifyNoMoreInteractions(inputManager)
- }
-
- @Test
- fun onStylusBluetoothConnected_updateStylusFlagAndUnregisters() {
- stylusListener.hasStarted = true
- stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
-
- stylusListener.onStylusBluetoothConnected(EXTERNAL_STYLUS_DEVICE_ID, "ANY")
-
- verify(inputManager).setStylusEverUsed(context, true)
- verify(inputManager, times(1))
- .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener)
- verify(stylusManager).unregisterCallback(stylusListener)
- }
-
- @Test
- fun onStylusBluetoothConnected_hasNotStarted_doesNoting() {
- stylusListener.hasStarted = false
- stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
-
- stylusListener.onStylusBluetoothConnected(EXTERNAL_STYLUS_DEVICE_ID, "ANY")
-
- verifyZeroInteractions(inputManager)
- verifyZeroInteractions(stylusManager)
- }
-
- @Test
- fun onBatteryStateChanged_batteryPresent_updateStylusFlagAndUnregisters() {
- stylusListener.hasStarted = true
- stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
- whenever(batteryState.isPresent).thenReturn(true)
-
- stylusListener.onBatteryStateChanged(0, 1, batteryState)
-
- verify(inputManager).setStylusEverUsed(context, true)
- verify(inputManager, times(1))
- .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener)
- verify(stylusManager).unregisterCallback(stylusListener)
- }
-
- @Test
- fun onBatteryStateChanged_batteryNotPresent_doesNotUpdateFlagOrUnregister() {
- stylusListener.hasStarted = true
- stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
- whenever(batteryState.isPresent).thenReturn(false)
-
- stylusListener.onBatteryStateChanged(0, 1, batteryState)
-
- verifyZeroInteractions(stylusManager)
- verify(inputManager, never())
- .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener)
- }
-
- @Test
- fun onBatteryStateChanged_hasNotStarted_doesNothing() {
- stylusListener.hasStarted = false
- stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
- whenever(batteryState.isPresent).thenReturn(false)
-
- stylusListener.onBatteryStateChanged(0, 1, batteryState)
-
- verifyZeroInteractions(inputManager)
- verifyZeroInteractions(stylusManager)
- }
-
- companion object {
- private const val OTHER_DEVICE_ID = 0
- private const val INTERNAL_STYLUS_DEVICE_ID = 1
- private const val EXTERNAL_STYLUS_DEVICE_ID = 2
- private val EXECUTOR = FakeExecutor(FakeSystemClock())
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
index 984de5b..6d6e40a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
@@ -17,12 +17,15 @@
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
+import android.hardware.BatteryState
import android.hardware.input.InputManager
import android.os.Handler
import android.testing.AndroidTestingRunner
import android.view.InputDevice
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import java.util.concurrent.Executor
@@ -31,30 +34,27 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
-@Ignore("b/257936830 until bt APIs")
class StylusManagerTest : SysuiTestCase() {
@Mock lateinit var inputManager: InputManager
-
@Mock lateinit var stylusDevice: InputDevice
-
@Mock lateinit var btStylusDevice: InputDevice
-
@Mock lateinit var otherDevice: InputDevice
-
+ @Mock lateinit var batteryState: BatteryState
@Mock lateinit var bluetoothAdapter: BluetoothAdapter
-
@Mock lateinit var bluetoothDevice: BluetoothDevice
-
@Mock lateinit var handler: Handler
+ @Mock lateinit var featureFlags: FeatureFlags
@Mock lateinit var stylusCallback: StylusManager.StylusCallback
@@ -75,11 +75,8 @@
true
}
- stylusManager = StylusManager(inputManager, bluetoothAdapter, handler, EXECUTOR)
-
- stylusManager.registerCallback(stylusCallback)
-
- stylusManager.registerBatteryCallback(stylusBatteryCallback)
+ stylusManager =
+ StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags)
whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false)
whenever(stylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
@@ -92,19 +89,47 @@
whenever(inputManager.getInputDevice(STYLUS_DEVICE_ID)).thenReturn(stylusDevice)
whenever(inputManager.getInputDevice(BT_STYLUS_DEVICE_ID)).thenReturn(btStylusDevice)
whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(STYLUS_DEVICE_ID))
+ whenever(inputManager.isStylusEverUsed(mContext)).thenReturn(false)
whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(bluetoothDevice)
whenever(bluetoothDevice.address).thenReturn(STYLUS_BT_ADDRESS)
+
+ whenever(featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)).thenReturn(true)
+
+ stylusManager.startListener()
+ stylusManager.registerCallback(stylusCallback)
+ stylusManager.registerBatteryCallback(stylusBatteryCallback)
+ clearInvocations(inputManager)
}
@Test
- fun startListener_registersInputDeviceListener() {
+ fun startListener_hasNotStarted_registersInputDeviceListener() {
+ stylusManager =
+ StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags)
+
stylusManager.startListener()
verify(inputManager, times(1)).registerInputDeviceListener(any(), any())
}
@Test
+ fun startListener_hasStarted_doesNothing() {
+ stylusManager.startListener()
+
+ verifyZeroInteractions(inputManager)
+ }
+
+ @Test
+ fun onInputDeviceAdded_hasNotStarted_doesNothing() {
+ stylusManager =
+ StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags)
+
+ stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+
+ verifyZeroInteractions(stylusCallback)
+ }
+
+ @Test
fun onInputDeviceAdded_multipleRegisteredCallbacks_callsAll() {
stylusManager.registerCallback(otherStylusCallback)
@@ -117,6 +142,26 @@
}
@Test
+ fun onInputDeviceAdded_internalStylus_registersBatteryListener() {
+ whenever(stylusDevice.isExternal).thenReturn(false)
+
+ stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+
+ verify(inputManager, times(1))
+ .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, EXECUTOR, stylusManager)
+ }
+
+ @Test
+ fun onInputDeviceAdded_externalStylus_doesNotRegisterbatteryListener() {
+ whenever(stylusDevice.isExternal).thenReturn(true)
+
+ stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+
+ verify(inputManager, never())
+ .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, EXECUTOR, stylusManager)
+ }
+
+ @Test
fun onInputDeviceAdded_stylus_callsCallbacksOnStylusAdded() {
stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
@@ -125,6 +170,23 @@
}
@Test
+ @Ignore("b/257936830 until bt APIs")
+ fun onInputDeviceAdded_btStylus_firstUsed_callsCallbacksOnStylusFirstUsed() {
+ stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+ verify(stylusCallback, times(1)).onStylusFirstUsed()
+ }
+
+ @Test
+ @Ignore("b/257936830 until bt APIs")
+ fun onInputDeviceAdded_btStylus_firstUsed_setsFlag() {
+ stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+ verify(inputManager, times(1)).setStylusEverUsed(mContext, true)
+ }
+
+ @Test
+ @Ignore("b/257936830 until bt APIs")
fun onInputDeviceAdded_btStylus_callsCallbacksWithAddress() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
@@ -143,6 +205,17 @@
}
@Test
+ fun onInputDeviceChanged_hasNotStarted_doesNothing() {
+ stylusManager =
+ StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags)
+
+ stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)
+
+ verifyZeroInteractions(stylusCallback)
+ }
+
+ @Test
+ @Ignore("b/257936830 until bt APIs")
fun onInputDeviceChanged_multipleRegisteredCallbacks_callsAll() {
stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
// whenever(stylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)
@@ -157,6 +230,7 @@
}
@Test
+ @Ignore("b/257936830 until bt APIs")
fun onInputDeviceChanged_stylusNewBtConnection_callsCallbacks() {
stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
// whenever(stylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)
@@ -168,6 +242,7 @@
}
@Test
+ @Ignore("b/257936830 until bt APIs")
fun onInputDeviceChanged_stylusLostBtConnection_callsCallbacks() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
// whenever(btStylusDevice.bluetoothAddress).thenReturn(null)
@@ -179,6 +254,7 @@
}
@Test
+ @Ignore("b/257936830 until bt APIs")
fun onInputDeviceChanged_btConnection_stylusAlreadyBtConnected_onlyCallsListenersOnce() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
@@ -189,6 +265,7 @@
}
@Test
+ @Ignore("b/257936830 until bt APIs")
fun onInputDeviceChanged_noBtConnection_stylusNeverBtConnected_doesNotCallCallbacks() {
stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
@@ -198,6 +275,17 @@
}
@Test
+ fun onInputDeviceRemoved_hasNotStarted_doesNothing() {
+ stylusManager =
+ StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags)
+ stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+
+ stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID)
+
+ verifyZeroInteractions(stylusCallback)
+ }
+
+ @Test
fun onInputDeviceRemoved_multipleRegisteredCallbacks_callsAll() {
stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
stylusManager.registerCallback(otherStylusCallback)
@@ -219,6 +307,17 @@
}
@Test
+ fun onInputDeviceRemoved_unregistersBatteryListener() {
+ stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+
+ stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID)
+
+ verify(inputManager, times(1))
+ .removeInputDeviceBatteryListener(STYLUS_DEVICE_ID, stylusManager)
+ }
+
+ @Test
+ @Ignore("b/257936830 until bt APIs")
fun onInputDeviceRemoved_btStylus_callsCallbacks() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
@@ -232,6 +331,7 @@
}
@Test
+ @Ignore("b/257936830 until bt APIs")
fun onStylusBluetoothConnected_registersMetadataListener() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
@@ -239,6 +339,7 @@
}
@Test
+ @Ignore("b/257936830 until bt APIs")
fun onStylusBluetoothConnected_noBluetoothDevice_doesNotRegisterMetadataListener() {
whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(null)
@@ -248,6 +349,7 @@
}
@Test
+ @Ignore("b/257936830 until bt APIs")
fun onStylusBluetoothDisconnected_unregistersMetadataListener() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
@@ -257,6 +359,7 @@
}
@Test
+ @Ignore("b/257936830 until bt APIs")
fun onMetadataChanged_multipleRegisteredBatteryCallbacks_executesAll() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
stylusManager.registerBatteryCallback(otherStylusBatteryCallback)
@@ -274,6 +377,7 @@
}
@Test
+ @Ignore("b/257936830 until bt APIs")
fun onMetadataChanged_chargingStateTrue_executesBatteryCallbacks() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
@@ -288,6 +392,7 @@
}
@Test
+ @Ignore("b/257936830 until bt APIs")
fun onMetadataChanged_chargingStateFalse_executesBatteryCallbacks() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
@@ -302,6 +407,7 @@
}
@Test
+ @Ignore("b/257936830 until bt APIs")
fun onMetadataChanged_chargingStateNoDevice_doesNotExecuteBatteryCallbacks() {
stylusManager.onMetadataChanged(
bluetoothDevice,
@@ -313,6 +419,7 @@
}
@Test
+ @Ignore("b/257936830 until bt APIs")
fun onMetadataChanged_notChargingState_doesNotExecuteBatteryCallbacks() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
@@ -326,6 +433,63 @@
.onStylusBluetoothChargingStateChanged(any(), any(), any())
}
+ @Test
+ @Ignore("TODO(b/261826950): remove on main")
+ fun onBatteryStateChanged_batteryPresent_stylusNeverUsed_updateEverUsedFlag() {
+ whenever(batteryState.isPresent).thenReturn(true)
+
+ stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
+
+ verify(inputManager).setStylusEverUsed(mContext, true)
+ }
+
+ @Test
+ @Ignore("TODO(b/261826950): remove on main")
+ fun onBatteryStateChanged_batteryPresent_stylusNeverUsed_executesStylusFirstUsed() {
+ whenever(batteryState.isPresent).thenReturn(true)
+
+ stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
+
+ verify(stylusCallback, times(1)).onStylusFirstUsed()
+ }
+
+ @Test
+ @Ignore("TODO(b/261826950): remove on main")
+ fun onBatteryStateChanged_batteryPresent_stylusUsed_doesNotUpdateEverUsedFlag() {
+ whenever(inputManager.isStylusEverUsed(mContext)).thenReturn(true)
+ whenever(batteryState.isPresent).thenReturn(true)
+
+ stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
+
+ verify(inputManager, never()).setStylusEverUsed(mContext, true)
+ }
+
+ @Test
+ @Ignore("TODO(b/261826950): remove on main")
+ fun onBatteryStateChanged_batteryNotPresent_doesNotUpdateEverUsedFlag() {
+ whenever(batteryState.isPresent).thenReturn(false)
+
+ stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
+
+ verify(inputManager, never())
+ .removeInputDeviceBatteryListener(STYLUS_DEVICE_ID, stylusManager)
+ }
+
+ @Test
+ fun onBatteryStateChanged_hasNotStarted_doesNothing() {
+ stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
+
+ verifyZeroInteractions(inputManager)
+ }
+
+ @Test
+ fun onBatteryStateChanged_executesBatteryCallbacks() {
+ stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
+
+ verify(stylusBatteryCallback, times(1))
+ .onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
+ }
+
companion object {
private val EXECUTOR = Executor { r -> r.run() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
index ff382a3..1cccd65c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
@@ -25,17 +25,15 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.util.mockito.whenever
-import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -60,7 +58,6 @@
inputManager,
stylusUsiPowerUi,
featureFlags,
- DIRECT_EXECUTOR,
)
whenever(featureFlags.isEnabled(Flags.ENABLE_USI_BATTERY_NOTIFICATIONS)).thenReturn(true)
@@ -79,40 +76,19 @@
}
@Test
- fun start_addsBatteryListenerForInternalStylus() {
+ fun start_hostDeviceDoesNotSupportStylus_doesNotRegister() {
+ whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(EXTERNAL_DEVICE_ID))
+
startable.start()
- verify(inputManager, times(1))
- .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, DIRECT_EXECUTOR, startable)
+ verifyZeroInteractions(stylusManager)
}
@Test
- fun onStylusAdded_internalStylus_addsBatteryListener() {
- startable.onStylusAdded(STYLUS_DEVICE_ID)
+ fun start_initStylusUsiPowerUi() {
+ startable.start()
- verify(inputManager, times(1))
- .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, DIRECT_EXECUTOR, startable)
- }
-
- @Test
- fun onStylusAdded_externalStylus_doesNotAddBatteryListener() {
- startable.onStylusAdded(EXTERNAL_DEVICE_ID)
-
- verify(inputManager, never())
- .addInputDeviceBatteryListener(EXTERNAL_DEVICE_ID, DIRECT_EXECUTOR, startable)
- }
-
- @Test
- fun onStylusRemoved_registeredStylus_removesBatteryListener() {
- startable.onStylusAdded(STYLUS_DEVICE_ID)
- startable.onStylusRemoved(STYLUS_DEVICE_ID)
-
- inOrder(inputManager).let {
- it.verify(inputManager, times(1))
- .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, DIRECT_EXECUTOR, startable)
- it.verify(inputManager, times(1))
- .removeInputDeviceBatteryListener(STYLUS_DEVICE_ID, startable)
- }
+ verify(stylusUsiPowerUi, times(1)).init()
}
@Test
@@ -130,28 +106,34 @@
}
@Test
- fun onBatteryStateChanged_batteryPresent_refreshesNotification() {
- val batteryState = mock(BatteryState::class.java)
- whenever(batteryState.isPresent).thenReturn(true)
+ fun onStylusUsiBatteryStateChanged_batteryPresentValidCapacity_refreshesNotification() {
+ val batteryState = FixedCapacityBatteryState(0.1f)
- startable.onBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState)
+ startable.onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState)
- verify(stylusUsiPowerUi, times(1)).updateBatteryState(batteryState)
+ verify(stylusUsiPowerUi, times(1)).updateBatteryState(STYLUS_DEVICE_ID, batteryState)
}
@Test
- fun onBatteryStateChanged_batteryNotPresent_noop() {
+ fun onStylusUsiBatteryStateChanged_batteryPresentInvalidCapacity_noop() {
+ val batteryState = FixedCapacityBatteryState(0f)
+
+ startable.onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState)
+
+ verifyNoMoreInteractions(stylusUsiPowerUi)
+ }
+
+ @Test
+ fun onStylusUsiBatteryStateChanged_batteryNotPresent_noop() {
val batteryState = mock(BatteryState::class.java)
whenever(batteryState.isPresent).thenReturn(false)
- startable.onBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState)
+ startable.onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState)
verifyNoMoreInteractions(stylusUsiPowerUi)
}
companion object {
- private val DIRECT_EXECUTOR = Executor { r -> r.run() }
-
private const val EXTERNAL_DEVICE_ID = 0
private const val STYLUS_DEVICE_ID = 1
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
index 5987550..96e1159 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
@@ -16,8 +16,12 @@
package com.android.systemui.stylus
-import android.hardware.BatteryState
+import android.app.Notification
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
import android.hardware.input.InputManager
+import android.os.Bundle
import android.os.Handler
import android.testing.AndroidTestingRunner
import android.view.InputDevice
@@ -26,14 +30,22 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertEquals
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.doNothing
import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
@@ -46,13 +58,19 @@
@Mock lateinit var inputManager: InputManager
@Mock lateinit var handler: Handler
@Mock lateinit var btStylusDevice: InputDevice
+ @Captor lateinit var notificationCaptor: ArgumentCaptor<Notification>
private lateinit var stylusUsiPowerUi: StylusUsiPowerUI
+ private lateinit var broadcastReceiver: BroadcastReceiver
+ private lateinit var contextSpy: Context
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ contextSpy = spy(mContext)
+ doNothing().whenever(contextSpy).startActivity(any())
+
whenever(handler.post(any())).thenAnswer {
(it.arguments[0] as Runnable).run()
true
@@ -63,56 +81,77 @@
whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
// whenever(btStylusDevice.bluetoothAddress).thenReturn("SO:ME:AD:DR:ES")
- stylusUsiPowerUi = StylusUsiPowerUI(mContext, notificationManager, inputManager, handler)
+ stylusUsiPowerUi = StylusUsiPowerUI(contextSpy, notificationManager, inputManager, handler)
+ broadcastReceiver = stylusUsiPowerUi.receiver
+ }
+
+ @Test
+ fun updateBatteryState_capacityZero_noop() {
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0f))
+
+ verifyNoMoreInteractions(notificationManager)
}
@Test
fun updateBatteryState_capacityBelowThreshold_notifies() {
- stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f))
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
- verify(notificationManager, times(1)).notify(eq(R.string.stylus_battery_low), any())
+ verify(notificationManager, times(1))
+ .notify(eq(R.string.stylus_battery_low_percentage), any())
verifyNoMoreInteractions(notificationManager)
}
@Test
fun updateBatteryState_capacityAboveThreshold_cancelsNotificattion() {
- stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.8f))
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.8f))
- verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low)
+ verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage)
verifyNoMoreInteractions(notificationManager)
}
@Test
fun updateBatteryState_existingNotification_capacityAboveThreshold_cancelsNotification() {
- stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f))
- stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.8f))
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.8f))
inOrder(notificationManager).let {
- it.verify(notificationManager, times(1)).notify(eq(R.string.stylus_battery_low), any())
- it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low)
+ it.verify(notificationManager, times(1))
+ .notify(eq(R.string.stylus_battery_low_percentage), any())
+ it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage)
it.verifyNoMoreInteractions()
}
}
@Test
fun updateBatteryState_existingNotification_capacityBelowThreshold_updatesNotification() {
- stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f))
- stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.15f))
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.15f))
- verify(notificationManager, times(2)).notify(eq(R.string.stylus_battery_low), any())
+ verify(notificationManager, times(2))
+ .notify(eq(R.string.stylus_battery_low_percentage), notificationCaptor.capture())
+ assertEquals(
+ notificationCaptor.value.extras.getString(Notification.EXTRA_TITLE),
+ context.getString(R.string.stylus_battery_low_percentage, "15%")
+ )
+ assertEquals(
+ notificationCaptor.value.extras.getString(Notification.EXTRA_TEXT),
+ context.getString(R.string.stylus_battery_low_subtitle)
+ )
verifyNoMoreInteractions(notificationManager)
}
@Test
fun updateBatteryState_capacityAboveThenBelowThreshold_hidesThenShowsNotification() {
- stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f))
- stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.5f))
- stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f))
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.5f))
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
inOrder(notificationManager).let {
- it.verify(notificationManager, times(1)).notify(eq(R.string.stylus_battery_low), any())
- it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low)
- it.verify(notificationManager, times(1)).notify(eq(R.string.stylus_battery_low), any())
+ it.verify(notificationManager, times(1))
+ .notify(eq(R.string.stylus_battery_low_percentage), any())
+ it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage)
+ it.verify(notificationManager, times(1))
+ .notify(eq(R.string.stylus_battery_low_percentage), any())
it.verifyNoMoreInteractions()
}
}
@@ -121,19 +160,20 @@
fun updateSuppression_noExistingNotification_cancelsNotification() {
stylusUsiPowerUi.updateSuppression(true)
- verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low)
+ verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage)
verifyNoMoreInteractions(notificationManager)
}
@Test
fun updateSuppression_existingNotification_cancelsNotification() {
- stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f))
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
stylusUsiPowerUi.updateSuppression(true)
inOrder(notificationManager).let {
- it.verify(notificationManager, times(1)).notify(eq(R.string.stylus_battery_low), any())
- it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low)
+ it.verify(notificationManager, times(1))
+ .notify(eq(R.string.stylus_battery_low_percentage), any())
+ it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage)
it.verifyNoMoreInteractions()
}
}
@@ -151,17 +191,35 @@
@Test
@Ignore("TODO(b/257936830): get bt address once input api available")
fun refresh_hasConnectedBluetoothStylus_existingNotification_cancelsNotification() {
- stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f))
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(0))
stylusUsiPowerUi.refresh()
- verify(notificationManager).cancel(R.string.stylus_battery_low)
+ verify(notificationManager).cancel(R.string.stylus_battery_low_percentage)
}
- class FixedCapacityBatteryState(private val capacity: Float) : BatteryState() {
- override fun getCapacity() = capacity
- override fun getStatus() = 0
- override fun isPresent() = true
+ @Test
+ fun broadcastReceiver_clicked_hasInputDeviceId_startsUsiDetailsActivity() {
+ val intent = Intent(StylusUsiPowerUI.ACTION_CLICKED_LOW_BATTERY)
+ val activityIntentCaptor = argumentCaptor<Intent>()
+ stylusUsiPowerUi.updateBatteryState(1, FixedCapacityBatteryState(0.15f))
+ broadcastReceiver.onReceive(contextSpy, intent)
+
+ verify(contextSpy, times(1)).startActivity(activityIntentCaptor.capture())
+ assertThat(activityIntentCaptor.value.action)
+ .isEqualTo(StylusUsiPowerUI.ACTION_STYLUS_USI_DETAILS)
+ val args =
+ activityIntentCaptor.value.getExtra(StylusUsiPowerUI.KEY_SETTINGS_FRAGMENT_ARGS)
+ as Bundle
+ assertThat(args.getInt(StylusUsiPowerUI.KEY_DEVICE_INPUT_ID)).isEqualTo(1)
+ }
+
+ @Test
+ fun broadcastReceiver_clicked_nullInputDeviceId_doesNotStartActivity() {
+ val intent = Intent(StylusUsiPowerUI.ACTION_CLICKED_LOW_BATTERY)
+ broadcastReceiver.onReceive(contextSpy, intent)
+
+ verify(contextSpy, never()).startActivity(any())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
index 89402de..30c4f97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.unfold.util.FoldableDeviceStates
@@ -73,6 +74,8 @@
@Mock lateinit var viewTreeObserver: ViewTreeObserver
+ @Mock private lateinit var commandQueue: CommandQueue
+
@Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
private lateinit var deviceStates: FoldableDeviceStates
@@ -102,7 +105,8 @@
}
keyguardRepository = FakeKeyguardRepository()
- val keyguardInteractor = KeyguardInteractor(repository = keyguardRepository)
+ val keyguardInteractor =
+ KeyguardInteractor(repository = keyguardRepository, commandQueue = commandQueue)
// Needs to be run on the main thread
runBlocking(IMMEDIATE) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 78b0cbe..9bb52be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -36,12 +36,14 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -60,12 +62,12 @@
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineScope
-import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+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
@@ -73,10 +75,12 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class UserInteractorTest : SysuiTestCase() {
@@ -90,10 +94,11 @@
@Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
+ @Mock private lateinit var commandQueue: CommandQueue
private lateinit var underTest: UserInteractor
- private lateinit var testCoroutineScope: TestCoroutineScope
+ private lateinit var testScope: TestScope
private lateinit var userRepository: FakeUserRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var telephonyRepository: FakeTelephonyRepository
@@ -117,11 +122,12 @@
userRepository = FakeUserRepository()
keyguardRepository = FakeKeyguardRepository()
telephonyRepository = FakeTelephonyRepository()
- testCoroutineScope = TestCoroutineScope()
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
val refreshUsersScheduler =
RefreshUsersScheduler(
- applicationScope = testCoroutineScope,
- mainDispatcher = IMMEDIATE,
+ applicationScope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
repository = userRepository,
)
underTest =
@@ -132,23 +138,24 @@
keyguardInteractor =
KeyguardInteractor(
repository = keyguardRepository,
+ commandQueue = commandQueue,
),
manager = manager,
- applicationScope = testCoroutineScope,
+ applicationScope = testScope.backgroundScope,
telephonyInteractor =
TelephonyInteractor(
repository = telephonyRepository,
),
broadcastDispatcher = fakeBroadcastDispatcher,
- backgroundDispatcher = IMMEDIATE,
+ backgroundDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor =
GuestUserInteractor(
applicationContext = context,
- applicationScope = testCoroutineScope,
- mainDispatcher = IMMEDIATE,
- backgroundDispatcher = IMMEDIATE,
+ applicationScope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
+ backgroundDispatcher = testDispatcher,
manager = manager,
repository = userRepository,
deviceProvisionedController = deviceProvisionedController,
@@ -164,7 +171,7 @@
@Test
fun `onRecordSelected - user`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -179,7 +186,7 @@
@Test
fun `onRecordSelected - switch to guest user`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = true)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -193,7 +200,7 @@
@Test
fun `onRecordSelected - enter guest mode`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -202,6 +209,7 @@
whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
+ runCurrent()
verify(dialogShower).dismiss()
verify(manager).createGuest(any())
@@ -210,7 +218,7 @@
@Test
fun `onRecordSelected - action`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = true)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -224,81 +232,72 @@
@Test
fun `users - switcher enabled`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = true)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var value: List<UserModel>? = null
- val job = underTest.users.onEach { value = it }.launchIn(this)
- assertUsers(models = value, count = 3, includeGuest = true)
+ val value = collectLastValue(underTest.users)
- job.cancel()
+ assertUsers(models = value(), count = 3, includeGuest = true)
}
@Test
fun `users - switches to second user`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var value: List<UserModel>? = null
- val job = underTest.users.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.users)
userRepository.setSelectedUserInfo(userInfos[1])
- assertUsers(models = value, count = 2, selectedIndex = 1)
- job.cancel()
+ assertUsers(models = value(), count = 2, selectedIndex = 1)
}
@Test
fun `users - switcher not enabled`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
- var value: List<UserModel>? = null
- val job = underTest.users.onEach { value = it }.launchIn(this)
- assertUsers(models = value, count = 1)
-
- job.cancel()
+ val value = collectLastValue(underTest.users)
+ assertUsers(models = value(), count = 1)
}
@Test
fun selectedUser() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var value: UserModel? = null
- val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
- assertUser(value, id = 0, isSelected = true)
+ val value = collectLastValue(underTest.selectedUser)
+ assertUser(value(), id = 0, isSelected = true)
userRepository.setSelectedUserInfo(userInfos[1])
- assertUser(value, id = 1, isSelected = true)
-
- job.cancel()
+ assertUser(value(), id = 1, isSelected = true)
}
@Test
fun `actions - device unlocked`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.actions)
- assertThat(value)
+ runCurrent()
+
+ assertThat(value())
.isEqualTo(
listOf(
UserActionModel.ENTER_GUEST_MODE,
@@ -307,13 +306,11 @@
UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
)
-
- job.cancel()
}
@Test
fun `actions - device unlocked - full screen`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 2, includeGuest = false)
@@ -321,10 +318,9 @@
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.actions)
- assertThat(value)
+ assertThat(value())
.isEqualTo(
listOf(
UserActionModel.ADD_USER,
@@ -333,46 +329,38 @@
UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
)
-
- job.cancel()
}
@Test
fun `actions - device unlocked user not primary - empty list`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[1])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.actions)
- assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
- job.cancel()
+ assertThat(value()).isEqualTo(emptyList<UserActionModel>())
}
@Test
fun `actions - device unlocked user is guest - empty list`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = true)
assertThat(userInfos[1].isGuest).isTrue()
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[1])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.actions)
- assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
- job.cancel()
+ assertThat(value()).isEqualTo(emptyList<UserActionModel>())
}
@Test
fun `actions - device locked add from lockscreen set - full list`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -383,10 +371,9 @@
)
)
keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.actions)
- assertThat(value)
+ assertThat(value())
.isEqualTo(
listOf(
UserActionModel.ENTER_GUEST_MODE,
@@ -395,13 +382,11 @@
UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
)
-
- job.cancel()
}
@Test
fun `actions - device locked add from lockscreen set - full list - full screen`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
@@ -413,10 +398,9 @@
)
)
keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.actions)
- assertThat(value)
+ assertThat(value())
.isEqualTo(
listOf(
UserActionModel.ADD_USER,
@@ -425,39 +409,33 @@
UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
)
-
- job.cancel()
}
@Test
fun `actions - device locked - only manage user is shown`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
keyguardRepository.setKeyguardShowing(true)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.actions)
- assertThat(value).isEqualTo(listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT))
-
- job.cancel()
+ assertThat(value()).isEqualTo(listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT))
}
@Test
fun `executeAction - add user - dialog shown`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
keyguardRepository.setKeyguardShowing(false)
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+ val dialogRequest = collectLastValue(underTest.dialogShowRequests)
val dialogShower: UserSwitchDialogController.DialogShower = mock()
underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
- assertThat(dialogRequest)
+ assertThat(dialogRequest())
.isEqualTo(
ShowDialogRequestModel.ShowAddUserDialog(
userHandle = userInfos[0].userHandle,
@@ -468,14 +446,12 @@
)
underTest.onDialogShown()
- assertThat(dialogRequest).isNull()
-
- job.cancel()
+ assertThat(dialogRequest()).isNull()
}
@Test
- fun `executeAction - add supervised user - starts activity`() =
- runBlocking(IMMEDIATE) {
+ fun `executeAction - add supervised user - dismisses dialog and starts activity`() =
+ testScope.runTest {
underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
val intentCaptor = kotlinArgumentCaptor<Intent>()
@@ -487,7 +463,7 @@
@Test
fun `executeAction - navigate to manage users`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
val intentCaptor = kotlinArgumentCaptor<Intent>()
@@ -497,7 +473,7 @@
@Test
fun `executeAction - guest mode`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -505,110 +481,103 @@
val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
- val showDialogsJob =
- underTest.dialogShowRequests
- .onEach {
- dialogRequests.add(it)
- if (it != null) {
- underTest.onDialogShown()
- }
+ backgroundScope.launch {
+ underTest.dialogShowRequests.collect {
+ dialogRequests.add(it)
+ if (it != null) {
+ underTest.onDialogShown()
}
- .launchIn(this)
- val dismissDialogsJob =
- underTest.dialogDismissRequests
- .onEach {
- if (it != null) {
- underTest.onDialogDismissed()
- }
+ }
+ }
+ backgroundScope.launch {
+ underTest.dialogDismissRequests.collect {
+ if (it != null) {
+ underTest.onDialogDismissed()
}
- .launchIn(this)
+ }
+ }
underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
+ runCurrent()
assertThat(dialogRequests)
.contains(
ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
)
verify(activityManager).switchUser(guestUserInfo.id)
-
- showDialogsJob.cancel()
- dismissDialogsJob.cancel()
}
@Test
fun `selectUser - already selected guest re-selected - exit guest dialog`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = true)
val guestUserInfo = userInfos[1]
assertThat(guestUserInfo.isGuest).isTrue()
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(guestUserInfo)
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+ val dialogRequest = collectLastValue(underTest.dialogShowRequests)
underTest.selectUser(
newlySelectedUserId = guestUserInfo.id,
dialogShower = dialogShower,
)
- assertThat(dialogRequest)
+ assertThat(dialogRequest())
.isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
verify(dialogShower, never()).dismiss()
- job.cancel()
}
@Test
fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = true)
val guestUserInfo = userInfos[1]
assertThat(guestUserInfo.isGuest).isTrue()
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(guestUserInfo)
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+ val dialogRequest = collectLastValue(underTest.dialogShowRequests)
underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
- assertThat(dialogRequest)
+ assertThat(dialogRequest())
.isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
verify(dialogShower, never()).dismiss()
- job.cancel()
}
@Test
fun `selectUser - not currently guest - switches users`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+ val dialogRequest = collectLastValue(underTest.dialogShowRequests)
underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
- assertThat(dialogRequest).isNull()
+ assertThat(dialogRequest()).isNull()
verify(activityManager).switchUser(userInfos[1].id)
verify(dialogShower).dismiss()
- job.cancel()
}
@Test
fun `Telephony call state changes - refreshes users`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
+ runCurrent()
+
val refreshUsersCallCount = userRepository.refreshUsersCallCount
telephonyRepository.setCallState(1)
+ runCurrent()
assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
}
@Test
fun `User switched broadcast`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -617,9 +586,11 @@
val callback2: UserInteractor.UserCallback = mock()
underTest.addCallback(callback1)
underTest.addCallback(callback2)
+ runCurrent()
val refreshUsersCallCount = userRepository.refreshUsersCallCount
userRepository.setSelectedUserInfo(userInfos[1])
+ runCurrent()
fakeBroadcastDispatcher.registeredReceivers.forEach {
it.onReceive(
context,
@@ -627,16 +598,17 @@
.putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
)
}
+ runCurrent()
- verify(callback1).onUserStateChanged()
- verify(callback2).onUserStateChanged()
+ verify(callback1, atLeastOnce()).onUserStateChanged()
+ verify(callback2, atLeastOnce()).onUserStateChanged()
assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
}
@Test
fun `User info changed broadcast`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -649,12 +621,14 @@
)
}
+ runCurrent()
+
assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
}
@Test
fun `System user unlocked broadcast - refresh users`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -667,13 +641,14 @@
.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
)
}
+ runCurrent()
assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
}
@Test
fun `Non-system user unlocked broadcast - do not refresh users`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -691,14 +666,14 @@
@Test
fun userRecords() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = false)
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
keyguardRepository.setKeyguardShowing(false)
- testCoroutineScope.advanceUntilIdle()
+ runCurrent()
assertRecords(
records = underTest.userRecords.value,
@@ -717,7 +692,7 @@
@Test
fun userRecordsFullScreen() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 3, includeGuest = false)
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
@@ -725,7 +700,7 @@
userRepository.setSelectedUserInfo(userInfos[0])
keyguardRepository.setKeyguardShowing(false)
- testCoroutineScope.advanceUntilIdle()
+ runCurrent()
assertRecords(
records = underTest.userRecords.value,
@@ -744,7 +719,7 @@
@Test
fun selectedUserRecord() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = true)
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
userRepository.setUserInfos(userInfos)
@@ -762,63 +737,54 @@
@Test
fun `users - secondary user - guest user can be switched to`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = true)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[1])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var res: List<UserModel>? = null
- val job = underTest.users.onEach { res = it }.launchIn(this)
- assertThat(res?.size == 3).isTrue()
- assertThat(res?.find { it.isGuest }).isNotNull()
- job.cancel()
+ val res = collectLastValue(underTest.users)
+ assertThat(res()?.size == 3).isTrue()
+ assertThat(res()?.find { it.isGuest }).isNotNull()
}
@Test
fun `users - secondary user - no guest action`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = true)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[1])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var res: List<UserActionModel>? = null
- val job = underTest.actions.onEach { res = it }.launchIn(this)
- assertThat(res?.find { it == UserActionModel.ENTER_GUEST_MODE }).isNull()
- job.cancel()
+ val res = collectLastValue(underTest.actions)
+ assertThat(res()?.find { it == UserActionModel.ENTER_GUEST_MODE }).isNull()
}
@Test
fun `users - secondary user - no guest user record`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = true)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[1])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var res: List<UserRecord>? = null
- val job = underTest.userRecords.onEach { res = it }.launchIn(this)
- assertThat(res?.find { it.isGuest }).isNull()
- job.cancel()
+ assertThat(underTest.userRecords.value.find { it.isGuest }).isNull()
}
@Test
fun `show user switcher - full screen disabled - shows dialog switcher`() =
- runBlocking(IMMEDIATE) {
- var dialogRequest: ShowDialogRequestModel? = null
+ testScope.runTest {
val expandable = mock<Expandable>()
underTest.showUserSwitcher(context, expandable)
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+ val dialogRequest = collectLastValue(underTest.dialogShowRequests)
// Dialog is shown.
- assertThat(dialogRequest).isEqualTo(ShowDialogRequestModel.ShowUserSwitcherDialog)
+ assertThat(dialogRequest())
+ .isEqualTo(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable))
underTest.onDialogShown()
- assertThat(dialogRequest).isNull()
-
- job.cancel()
+ assertThat(dialogRequest()).isNull()
}
@Test
@@ -849,8 +815,8 @@
@Test
fun `users - secondary user - managed profile is not included`() =
- runBlocking(IMMEDIATE) {
- var userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList()
+ testScope.runTest {
+ val userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList()
userInfos.add(
UserInfo(
50,
@@ -863,23 +829,19 @@
userRepository.setSelectedUserInfo(userInfos[1])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var res: List<UserModel>? = null
- val job = underTest.users.onEach { res = it }.launchIn(this)
- assertThat(res?.size == 3).isTrue()
- job.cancel()
+ val res = collectLastValue(underTest.users)
+ assertThat(res()?.size == 3).isTrue()
}
@Test
fun `current user is not primary and user switcher is disabled`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[1])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
- var selectedUser: UserModel? = null
- val job = underTest.selectedUser.onEach { selectedUser = it }.launchIn(this)
- assertThat(selectedUser).isNotNull()
- job.cancel()
+ val selectedUser = collectLastValue(underTest.selectedUser)
+ assertThat(selectedUser()).isNotNull()
}
private fun assertUsers(
@@ -1017,7 +979,6 @@
}
companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
private val GUEST_ICON: Drawable = mock()
private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index 108fa62..9a4ca56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -75,6 +76,7 @@
@Mock private lateinit var uiEventLogger: UiEventLogger
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
+ @Mock private lateinit var commandQueue: CommandQueue
private lateinit var underTest: StatusBarUserChipViewModel
@@ -241,6 +243,7 @@
keyguardInteractor =
KeyguardInteractor(
repository = keyguardRepository,
+ commandQueue = commandQueue,
),
featureFlags =
FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 784a26b..3d4bbdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -76,6 +77,7 @@
@Mock private lateinit var uiEventLogger: UiEventLogger
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
+ @Mock private lateinit var commandQueue: CommandQueue
private lateinit var underTest: UserSwitcherViewModel
@@ -142,6 +144,7 @@
keyguardInteractor =
KeyguardInteractor(
repository = keyguardRepository,
+ commandQueue = commandQueue,
),
featureFlags =
FakeFeatureFlags().apply {
@@ -169,273 +172,295 @@
}
@Test
- fun users() = testScope.runTest {
- val userInfos =
- listOf(
- UserInfo(
- /* id= */ 0,
- /* name= */ "zero",
- /* iconPath= */ "",
- /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
- UserManager.USER_TYPE_FULL_SYSTEM,
- ),
- UserInfo(
- /* id= */ 1,
- /* name= */ "one",
- /* iconPath= */ "",
- /* flags= */ UserInfo.FLAG_FULL,
- UserManager.USER_TYPE_FULL_SYSTEM,
- ),
- UserInfo(
- /* id= */ 2,
- /* name= */ "two",
- /* iconPath= */ "",
- /* flags= */ UserInfo.FLAG_FULL,
- UserManager.USER_TYPE_FULL_SYSTEM,
- ),
- )
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
-
- val userViewModels = mutableListOf<List<UserViewModel>>()
- val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
-
- assertThat(userViewModels.last()).hasSize(3)
- assertUserViewModel(
- viewModel = userViewModels.last()[0],
- viewKey = 0,
- name = Text.Loaded("zero"),
- isSelectionMarkerVisible = true,
- )
- assertUserViewModel(
- viewModel = userViewModels.last()[1],
- viewKey = 1,
- name = Text.Loaded("one"),
- isSelectionMarkerVisible = false,
- )
- assertUserViewModel(
- viewModel = userViewModels.last()[2],
- viewKey = 2,
- name = Text.Loaded("two"),
- isSelectionMarkerVisible = false,
- )
- job.cancel()
- }
-
- @Test
- fun `maximumUserColumns - few users`() = testScope.runTest {
- setUsers(count = 2)
- val values = mutableListOf<Int>()
- val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
-
- assertThat(values.last()).isEqualTo(4)
-
- job.cancel()
- }
-
- @Test
- fun `maximumUserColumns - many users`() = testScope.runTest {
- setUsers(count = 5)
- val values = mutableListOf<Int>()
- val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
-
- assertThat(values.last()).isEqualTo(3)
- job.cancel()
- }
-
- @Test
- fun `isOpenMenuButtonVisible - has actions - true`() = testScope.runTest {
- setUsers(2)
-
- val isVisible = mutableListOf<Boolean>()
- val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
-
- assertThat(isVisible.last()).isTrue()
- job.cancel()
- }
-
- @Test
- fun `isOpenMenuButtonVisible - no actions - false`() = testScope.runTest {
- val userInfos = setUsers(2)
- userRepository.setSelectedUserInfo(userInfos[1])
- keyguardRepository.setKeyguardShowing(true)
- whenever(manager.canAddMoreUsers(any())).thenReturn(false)
-
- val isVisible = mutableListOf<Boolean>()
- val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
-
- assertThat(isVisible.last()).isFalse()
- job.cancel()
- }
-
- @Test
- fun menu() = testScope.runTest {
- val isMenuVisible = mutableListOf<Boolean>()
- val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) }
- assertThat(isMenuVisible.last()).isFalse()
-
- underTest.onOpenMenuButtonClicked()
- assertThat(isMenuVisible.last()).isTrue()
-
- underTest.onMenuClosed()
- assertThat(isMenuVisible.last()).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun `menu actions`() = testScope.runTest {
- setUsers(2)
- val actions = mutableListOf<List<UserActionViewModel>>()
- val job = launch(testDispatcher) { underTest.menu.toList(actions) }
-
- assertThat(actions.last().map { it.viewKey })
- .isEqualTo(
+ fun users() =
+ testScope.runTest {
+ val userInfos =
listOf(
- UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
- UserActionModel.ADD_USER.ordinal.toLong(),
- UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
+ UserInfo(
+ /* id= */ 0,
+ /* name= */ "zero",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_PRIMARY or
+ UserInfo.FLAG_ADMIN or
+ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ UserInfo(
+ /* id= */ 1,
+ /* name= */ "one",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ UserInfo(
+ /* id= */ 2,
+ /* name= */ "two",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
)
- )
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
- job.cancel()
- }
+ val userViewModels = mutableListOf<List<UserViewModel>>()
+ val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
- @Test
- fun `isFinishRequested - finishes when user is switched`() = testScope.runTest {
- val userInfos = setUsers(count = 2)
- val isFinishRequested = mutableListOf<Boolean>()
- val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
- assertThat(isFinishRequested.last()).isFalse()
-
- userRepository.setSelectedUserInfo(userInfos[1])
-
- assertThat(isFinishRequested.last()).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun `isFinishRequested - finishes when the screen turns off`() = testScope.runTest {
- setUsers(count = 2)
- powerRepository.setInteractive(true)
- val isFinishRequested = mutableListOf<Boolean>()
- val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
- assertThat(isFinishRequested.last()).isFalse()
-
- powerRepository.setInteractive(false)
-
- assertThat(isFinishRequested.last()).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun `isFinishRequested - finishes when cancel button is clicked`() = testScope.runTest {
- setUsers(count = 2)
- powerRepository.setInteractive(true)
- val isFinishRequested = mutableListOf<Boolean>()
- val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
- assertThat(isFinishRequested.last()).isFalse()
-
- underTest.onCancelButtonClicked()
-
- assertThat(isFinishRequested.last()).isTrue()
-
- underTest.onFinished()
-
- assertThat(isFinishRequested.last()).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun `guest selected -- name is exit guest`() = testScope.runTest {
- val userInfos =
- listOf(
- UserInfo(
- /* id= */ 0,
- /* name= */ "zero",
- /* iconPath= */ "",
- /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
- UserManager.USER_TYPE_FULL_SYSTEM,
- ),
- UserInfo(
- /* id= */ 1,
- /* name= */ "one",
- /* iconPath= */ "",
- /* flags= */ UserInfo.FLAG_FULL,
- UserManager.USER_TYPE_FULL_GUEST,
- ),
- )
-
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[1])
-
- val userViewModels = mutableListOf<List<UserViewModel>>()
- val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
-
- assertThat(userViewModels.last()).hasSize(2)
- assertUserViewModel(
- viewModel = userViewModels.last()[0],
- viewKey = 0,
- name = Text.Loaded("zero"),
- isSelectionMarkerVisible = false,
- )
- assertUserViewModel(
- viewModel = userViewModels.last()[1],
- viewKey = 1,
- name = Text.Resource(
- com.android.settingslib.R.string.guest_exit_quick_settings_button
- ),
- isSelectionMarkerVisible = true,
- )
- job.cancel()
- }
-
- @Test
- fun `guest not selected -- name is guest`() = testScope.runTest {
- val userInfos =
- listOf(
- UserInfo(
- /* id= */ 0,
- /* name= */ "zero",
- /* iconPath= */ "",
- /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
- UserManager.USER_TYPE_FULL_SYSTEM,
- ),
- UserInfo(
- /* id= */ 1,
- /* name= */ "one",
- /* iconPath= */ "",
- /* flags= */ UserInfo.FLAG_FULL,
- UserManager.USER_TYPE_FULL_GUEST,
- ),
- )
-
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- runCurrent()
-
- val userViewModels = mutableListOf<List<UserViewModel>>()
- val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
-
- assertThat(userViewModels.last()).hasSize(2)
- assertUserViewModel(
+ assertThat(userViewModels.last()).hasSize(3)
+ assertUserViewModel(
viewModel = userViewModels.last()[0],
viewKey = 0,
name = Text.Loaded("zero"),
isSelectionMarkerVisible = true,
- )
- assertUserViewModel(
+ )
+ assertUserViewModel(
viewModel = userViewModels.last()[1],
viewKey = 1,
name = Text.Loaded("one"),
isSelectionMarkerVisible = false,
- )
- job.cancel()
- }
+ )
+ assertUserViewModel(
+ viewModel = userViewModels.last()[2],
+ viewKey = 2,
+ name = Text.Loaded("two"),
+ isSelectionMarkerVisible = false,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `maximumUserColumns - few users`() =
+ testScope.runTest {
+ setUsers(count = 2)
+ val values = mutableListOf<Int>()
+ val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
+
+ assertThat(values.last()).isEqualTo(4)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `maximumUserColumns - many users`() =
+ testScope.runTest {
+ setUsers(count = 5)
+ val values = mutableListOf<Int>()
+ val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
+
+ assertThat(values.last()).isEqualTo(3)
+ job.cancel()
+ }
+
+ @Test
+ fun `isOpenMenuButtonVisible - has actions - true`() =
+ testScope.runTest {
+ setUsers(2)
+
+ val isVisible = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
+
+ assertThat(isVisible.last()).isTrue()
+ job.cancel()
+ }
+
+ @Test
+ fun `isOpenMenuButtonVisible - no actions - false`() =
+ testScope.runTest {
+ val userInfos = setUsers(2)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ keyguardRepository.setKeyguardShowing(true)
+ whenever(manager.canAddMoreUsers(any())).thenReturn(false)
+
+ val isVisible = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
+
+ assertThat(isVisible.last()).isFalse()
+ job.cancel()
+ }
+
+ @Test
+ fun menu() =
+ testScope.runTest {
+ val isMenuVisible = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) }
+ assertThat(isMenuVisible.last()).isFalse()
+
+ underTest.onOpenMenuButtonClicked()
+ assertThat(isMenuVisible.last()).isTrue()
+
+ underTest.onMenuClosed()
+ assertThat(isMenuVisible.last()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `menu actions`() =
+ testScope.runTest {
+ setUsers(2)
+ val actions = mutableListOf<List<UserActionViewModel>>()
+ val job = launch(testDispatcher) { underTest.menu.toList(actions) }
+
+ assertThat(actions.last().map { it.viewKey })
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
+ UserActionModel.ADD_USER.ordinal.toLong(),
+ UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `isFinishRequested - finishes when user is switched`() =
+ testScope.runTest {
+ val userInfos = setUsers(count = 2)
+ val isFinishRequested = mutableListOf<Boolean>()
+ val job =
+ launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+ assertThat(isFinishRequested.last()).isFalse()
+
+ userRepository.setSelectedUserInfo(userInfos[1])
+
+ assertThat(isFinishRequested.last()).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `isFinishRequested - finishes when the screen turns off`() =
+ testScope.runTest {
+ setUsers(count = 2)
+ powerRepository.setInteractive(true)
+ val isFinishRequested = mutableListOf<Boolean>()
+ val job =
+ launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+ assertThat(isFinishRequested.last()).isFalse()
+
+ powerRepository.setInteractive(false)
+
+ assertThat(isFinishRequested.last()).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `isFinishRequested - finishes when cancel button is clicked`() =
+ testScope.runTest {
+ setUsers(count = 2)
+ powerRepository.setInteractive(true)
+ val isFinishRequested = mutableListOf<Boolean>()
+ val job =
+ launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+ assertThat(isFinishRequested.last()).isFalse()
+
+ underTest.onCancelButtonClicked()
+
+ assertThat(isFinishRequested.last()).isTrue()
+
+ underTest.onFinished()
+
+ assertThat(isFinishRequested.last()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `guest selected -- name is exit guest`() =
+ testScope.runTest {
+ val userInfos =
+ listOf(
+ UserInfo(
+ /* id= */ 0,
+ /* name= */ "zero",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_PRIMARY or
+ UserInfo.FLAG_ADMIN or
+ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ UserInfo(
+ /* id= */ 1,
+ /* name= */ "one",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_GUEST,
+ ),
+ )
+
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+
+ val userViewModels = mutableListOf<List<UserViewModel>>()
+ val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
+
+ assertThat(userViewModels.last()).hasSize(2)
+ assertUserViewModel(
+ viewModel = userViewModels.last()[0],
+ viewKey = 0,
+ name = Text.Loaded("zero"),
+ isSelectionMarkerVisible = false,
+ )
+ assertUserViewModel(
+ viewModel = userViewModels.last()[1],
+ viewKey = 1,
+ name =
+ Text.Resource(
+ com.android.settingslib.R.string.guest_exit_quick_settings_button
+ ),
+ isSelectionMarkerVisible = true,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `guest not selected -- name is guest`() =
+ testScope.runTest {
+ val userInfos =
+ listOf(
+ UserInfo(
+ /* id= */ 0,
+ /* name= */ "zero",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_PRIMARY or
+ UserInfo.FLAG_ADMIN or
+ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ UserInfo(
+ /* id= */ 1,
+ /* name= */ "one",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_GUEST,
+ ),
+ )
+
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ runCurrent()
+
+ val userViewModels = mutableListOf<List<UserViewModel>>()
+ val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
+
+ assertThat(userViewModels.last()).hasSize(2)
+ assertUserViewModel(
+ viewModel = userViewModels.last()[0],
+ viewKey = 0,
+ name = Text.Loaded("zero"),
+ isSelectionMarkerVisible = true,
+ )
+ assertUserViewModel(
+ viewModel = userViewModels.last()[1],
+ viewKey = 1,
+ name = Text.Loaded("one"),
+ isSelectionMarkerVisible = false,
+ )
+ job.cancel()
+ }
private suspend fun setUsers(count: Int): List<UserInfo> {
val userInfos =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index c3c6975..d419095 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.volume;
+import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
import static junit.framework.Assert.assertEquals;
@@ -342,6 +343,15 @@
assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, R.drawable.ic_volume_ringer_mute);
}
+ @Test
+ public void testDialogDismissAnimation_notifyVisibleIsNotCalledBeforeAnimation() {
+ mDialog.dismissH(DISMISS_REASON_UNKNOWN);
+ // notifyVisible(false) should not be called immediately but only after the dismiss
+ // animation has ended.
+ verify(mVolumeDialogController, times(0)).notifyVisible(false);
+ mDialog.getDialogView().animate().cancel();
+ }
+
/*
@Test
public void testContentDescriptions() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 388c51f..dec8080 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -24,6 +24,7 @@
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
import static com.google.common.truth.Truth.assertThat;
@@ -228,6 +229,8 @@
private BubbleEntry mBubbleEntryUser11;
private BubbleEntry mBubbleEntry2User11;
+ private Intent mAppBubbleIntent;
+
@Mock
private ShellInit mShellInit;
@Mock
@@ -323,6 +326,9 @@
mBubbleEntry2User11 = BubblesManager.notifToBubbleEntry(
mNotificationTestHelper.createBubble(handle));
+ mAppBubbleIntent = new Intent(mContext, BubblesTestActivity.class);
+ mAppBubbleIntent.setPackage(mContext.getPackageName());
+
mZenModeConfig.suppressedVisualEffects = 0;
when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
@@ -1630,6 +1636,62 @@
any(Bubble.class), anyBoolean(), anyBoolean());
}
+ @Test
+ public void testShowOrHideAppBubble_addsAndExpand() {
+ assertThat(mBubbleController.isStackExpanded()).isFalse();
+ assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull();
+
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+
+ verify(mBubbleController).inflateAndAdd(any(Bubble.class), /* suppressFlyout= */ eq(true),
+ /* showInShade= */ eq(false));
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
+ assertThat(mBubbleController.isStackExpanded()).isTrue();
+ }
+
+ @Test
+ public void testShowOrHideAppBubble_expandIfCollapsed() {
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+ mBubbleController.updateBubble(mBubbleEntry);
+ mBubbleController.collapseStack();
+ assertThat(mBubbleController.isStackExpanded()).isFalse();
+
+ // Calling this while collapsed will expand the app bubble
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
+ assertThat(mBubbleController.isStackExpanded()).isTrue();
+ assertThat(mBubbleData.getBubbles().size()).isEqualTo(2);
+ }
+
+ @Test
+ public void testShowOrHideAppBubble_collapseIfSelected() {
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
+ assertThat(mBubbleController.isStackExpanded()).isTrue();
+
+ // Calling this while the app bubble is expanded should collapse the stack
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
+ assertThat(mBubbleController.isStackExpanded()).isFalse();
+ assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
+ }
+
+ @Test
+ public void testShowOrHideAppBubble_selectIfNotSelected() {
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+ mBubbleController.updateBubble(mBubbleEntry);
+ mBubbleController.expandStackAndSelectBubble(mBubbleEntry);
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(mBubbleEntry.getKey());
+ assertThat(mBubbleController.isStackExpanded()).isTrue();
+
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
+ assertThat(mBubbleController.isStackExpanded()).isTrue();
+ assertThat(mBubbleData.getBubbles().size()).isEqualTo(2);
+ }
+
/** Creates a bubble using the userId and package. */
private Bubble createBubble(int userId, String pkg) {
final UserHandle userHandle = new UserHandle(userId);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
index 3767fbe..3428553 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
@@ -40,24 +40,49 @@
public class MemoryTrackingTestCase extends SysuiTestCase {
private static File sFilesDir = null;
private static String sLatestTestClassName = null;
+ private static int sHeapCount = 0;
+ private static File sLatestBaselineHeapFile = null;
- @Before public void grabFilesDir() {
+ // Ideally, we would do this in @BeforeClass just once, but we need mContext to get the files
+ // dir, and that does not exist until @Before on each test method.
+ @Before
+ public void grabFilesDir() throws IOException {
+ // This should happen only once per suite
if (sFilesDir == null) {
sFilesDir = mContext.getFilesDir();
}
- sLatestTestClassName = getClass().getName();
+
+ // This will happen before the first test method in each class
+ if (sLatestTestClassName == null) {
+ sLatestTestClassName = getClass().getName();
+ sLatestBaselineHeapFile = dump("baseline" + (++sHeapCount), "before-test");
+ }
}
@AfterClass
public static void dumpHeap() throws IOException {
+ File afterTestHeap = dump(sLatestTestClassName, "after-test");
+ if (sLatestBaselineHeapFile != null && afterTestHeap != null) {
+ Log.w("MEMORY", "To compare heap to baseline (use go/ahat):");
+ Log.w("MEMORY", " adb pull " + sLatestBaselineHeapFile);
+ Log.w("MEMORY", " adb pull " + afterTestHeap);
+ Log.w("MEMORY",
+ " java -jar ahat.jar --baseline " + sLatestBaselineHeapFile.getName() + " "
+ + afterTestHeap.getName());
+ }
+ sLatestTestClassName = null;
+ }
+
+ private static File dump(String basename, String heapKind) throws IOException {
if (sFilesDir == null) {
Log.e("MEMORY", "Somehow no test cases??");
- return;
+ return null;
}
mockitoTearDown();
- Log.w("MEMORY", "about to dump heap");
- File path = new File(sFilesDir, sLatestTestClassName + ".ahprof");
+ Log.w("MEMORY", "about to dump " + heapKind + " heap");
+ File path = new File(sFilesDir, basename + ".ahprof");
Debug.dumpHprofData(path.getPath());
- Log.w("MEMORY", "did it! Location: " + path);
+ Log.w("MEMORY", "Success! Location: " + path);
+ return path;
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt
new file mode 100644
index 0000000..f3e52de
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeBiometricRepository : BiometricRepository {
+
+ private val _isFingerprintEnrolled = MutableStateFlow<Boolean>(false)
+ override val isFingerprintEnrolled: StateFlow<Boolean> = _isFingerprintEnrolled.asStateFlow()
+
+ private val _isStrongBiometricAllowed = MutableStateFlow(false)
+ override val isStrongBiometricAllowed = _isStrongBiometricAllowed.asStateFlow()
+
+ private val _isFingerprintEnabledByDevicePolicy = MutableStateFlow(false)
+ override val isFingerprintEnabledByDevicePolicy =
+ _isFingerprintEnabledByDevicePolicy.asStateFlow()
+
+ fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) {
+ _isFingerprintEnrolled.value = isFingerprintEnrolled
+ }
+
+ fun setStrongBiometricAllowed(isStrongBiometricAllowed: Boolean) {
+ _isStrongBiometricAllowed.value = isStrongBiometricAllowed
+ }
+
+ fun setFingerprintEnabledByDevicePolicy(isFingerprintEnabledByDevicePolicy: Boolean) {
+ _isFingerprintEnabledByDevicePolicy.value = isFingerprintEnabledByDevicePolicy
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 39d2eca..15b4736 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -52,6 +52,9 @@
private val _isDozing = MutableStateFlow(false)
override val isDozing: Flow<Boolean> = _isDozing
+ private val _isAodAvailable = MutableStateFlow(false)
+ override val isAodAvailable: Flow<Boolean> = _isAodAvailable
+
private val _isDreaming = MutableStateFlow(false)
override val isDreaming: Flow<Boolean> = _isDreaming
@@ -126,6 +129,10 @@
_isDozing.value = isDozing
}
+ fun setAodAvailable(isAodAvailable: Boolean) {
+ _isAodAvailable.value = isAodAvailable
+ }
+
fun setDreamingWithOverlay(isDreaming: Boolean) {
_isDreamingWithOverlay.value = isDreaming
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
index 045e6f1..7bcad45 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+
import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -57,6 +59,7 @@
private ShortcutInfo mShortcutInfo = null;
private int mRankingAdjustment = 0;
private boolean mIsBubble = false;
+ private int mProposedImportance = IMPORTANCE_UNSPECIFIED;
public RankingBuilder() {
}
@@ -86,6 +89,7 @@
mShortcutInfo = ranking.getConversationShortcutInfo();
mRankingAdjustment = ranking.getRankingAdjustment();
mIsBubble = ranking.isBubble();
+ mProposedImportance = ranking.getProposedImportance();
}
public Ranking build() {
@@ -114,7 +118,8 @@
mIsConversation,
mShortcutInfo,
mRankingAdjustment,
- mIsBubble);
+ mIsBubble,
+ mProposedImportance);
return ranking;
}
@@ -214,6 +219,11 @@
return this;
}
+ public RankingBuilder setProposedImportance(@Importance int importance) {
+ mProposedImportance = importance;
+ return this;
+ }
+
public RankingBuilder setUserSentiment(int userSentiment) {
mUserSentiment = userSentiment;
return this;
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 4430bb4b..9d91b97 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -579,12 +579,6 @@
}
}
- if (onClickIntent != null) {
- views.setOnClickPendingIntent(android.R.id.background,
- PendingIntent.getActivity(mContext, 0, onClickIntent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
- }
-
Icon icon = appInfo.icon != 0
? Icon.createWithResource(appInfo.packageName, appInfo.icon)
: Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
@@ -596,6 +590,12 @@
for (int j = 0; j < widgetCount; j++) {
Widget widget = provider.widgets.get(j);
if (targetWidget != null && targetWidget != widget) continue;
+ if (onClickIntent != null) {
+ views.setOnClickPendingIntent(android.R.id.background,
+ PendingIntent.getActivity(mContext, widget.appWidgetId, onClickIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_IMMUTABLE));
+ }
if (widget.replaceWithMaskedViewsLocked(views)) {
scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked());
}
@@ -817,7 +817,8 @@
if (host != null) {
host.callbacks = null;
pruneHostLocked(host);
- mAppOpsManagerInternal.updateAppWidgetVisibility(host.getWidgetUids(), false);
+ mAppOpsManagerInternal.updateAppWidgetVisibility(host.getWidgetUidsIfBound(),
+ false);
}
}
}
@@ -888,12 +889,8 @@
Host host = lookupHostLocked(id);
if (host != null) {
- try {
- mAppOpsManagerInternal.updateAppWidgetVisibility(host.getWidgetUids(), false);
- } catch (NullPointerException e) {
- Slog.e(TAG, "setAppWidgetHidden(): Getting host uids: " + host.toString(), e);
- throw e;
- }
+ mAppOpsManagerInternal.updateAppWidgetVisibility(host.getWidgetUidsIfBound(),
+ false);
}
}
}
@@ -4345,14 +4342,15 @@
PendingHostUpdate.appWidgetRemoved(appWidgetId));
}
- public SparseArray<String> getWidgetUids() {
+ public SparseArray<String> getWidgetUidsIfBound() {
final SparseArray<String> uids = new SparseArray<>();
for (int i = widgets.size() - 1; i >= 0; i--) {
final Widget widget = widgets.get(i);
if (widget.provider == null) {
if (DEBUG) {
- Slog.e(TAG, "Widget with no provider " + widget.toString());
+ Slog.d(TAG, "Widget with no provider " + widget.toString());
}
+ continue;
}
final ProviderId providerId = widget.provider.id;
uids.put(providerId.uid, providerId.componentName.getPackageName());
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 677871f..8c2c964 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -357,6 +357,7 @@
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.accessibilityTitle = context.getString(R.string.autofill_save_accessibility_title);
params.windowAnimations = R.style.AutofillSaveAnimation;
+ params.setTrustedOverlay();
show();
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 9669c06..c36e070 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3420,6 +3420,11 @@
throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
+ className + " is not an isolatedProcess");
}
+ if (!mAm.getPackageManagerInternal().isSameApp(callingPackage, callingUid,
+ userId)) {
+ throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
+ + "calling package not owned by calling UID ");
+ }
// Run the service under the calling package's application.
ApplicationInfo aInfo = AppGlobals.getPackageManager().getApplicationInfo(
callingPackage, ActivityManagerService.STOCK_PM_FLAGS, userId);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 736914a..278c98f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -82,6 +82,7 @@
private final @NonNull AudioService mAudioService;
private final @NonNull Context mContext;
+ private final @NonNull AudioSystemAdapter mAudioSystem;
/** ID for Communication strategy retrieved form audio policy manager */
private int mCommunicationStrategyId = -1;
@@ -156,12 +157,14 @@
public static final long USE_SET_COMMUNICATION_DEVICE = 243827847L;
//-------------------------------------------------------------------
- /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
+ /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service,
+ @NonNull AudioSystemAdapter audioSystem) {
mContext = context;
mAudioService = service;
mBtHelper = new BtHelper(this);
mDeviceInventory = new AudioDeviceInventory(this);
mSystemServer = SystemServerAdapter.getDefaultAdapter(mContext);
+ mAudioSystem = audioSystem;
init();
}
@@ -170,12 +173,14 @@
* in system_server */
AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service,
@NonNull AudioDeviceInventory mockDeviceInventory,
- @NonNull SystemServerAdapter mockSystemServer) {
+ @NonNull SystemServerAdapter mockSystemServer,
+ @NonNull AudioSystemAdapter audioSystem) {
mContext = context;
mAudioService = service;
mBtHelper = new BtHelper(this);
mDeviceInventory = mockDeviceInventory;
mSystemServer = mockSystemServer;
+ mAudioSystem = audioSystem;
init();
}
@@ -450,7 +455,7 @@
AudioAttributes attr =
AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType(
AudioSystem.STREAM_VOICE_CALL);
- List<AudioDeviceAttributes> devices = AudioSystem.getDevicesForAttributes(
+ List<AudioDeviceAttributes> devices = mAudioSystem.getDevicesForAttributes(
attr, false /* forVolume */);
if (devices.isEmpty()) {
if (mAudioService.isPlatformVoice()) {
@@ -1225,7 +1230,7 @@
Log.v(TAG, "onSetForceUse(useCase<" + useCase + ">, config<" + config + ">, fromA2dp<"
+ fromA2dp + ">, eventSource<" + eventSource + ">)");
}
- AudioSystem.setForceUse(useCase, config);
+ mAudioSystem.setForceUse(useCase, config);
}
private void onSendBecomingNoisyIntent() {
@@ -1863,9 +1868,9 @@
if (preferredCommunicationDevice == null
|| preferredCommunicationDevice.getType() != AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
- AudioSystem.setParameters("BT_SCO=off");
+ mAudioSystem.setParameters("BT_SCO=off");
} else {
- AudioSystem.setParameters("BT_SCO=on");
+ mAudioSystem.setParameters("BT_SCO=on");
}
if (preferredCommunicationDevice == null) {
AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice();
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c804ef2..1bd8f1e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -183,6 +183,7 @@
import com.android.server.SystemService;
import com.android.server.audio.AudioServiceEvents.DeviceVolumeEvent;
import com.android.server.audio.AudioServiceEvents.PhoneStateEvent;
+import com.android.server.audio.AudioServiceEvents.VolChangedBroadcastEvent;
import com.android.server.audio.AudioServiceEvents.VolumeEvent;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
@@ -1205,7 +1206,7 @@
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
- mDeviceBroker = new AudioDeviceBroker(mContext, this);
+ mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
mRecordMonitor = new RecordingActivityMonitor(mContext);
mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true);
@@ -1637,7 +1638,7 @@
synchronized (mSettingsLock) {
final int forDock = mDockAudioMediaEnabled ?
- AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE;
+ AudioSystem.FORCE_DIGITAL_DOCK : AudioSystem.FORCE_NONE;
mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, forDock, "onAudioServerDied");
sendEncodedSurroundMode(mContentResolver, "onAudioServerDied");
sendEnabledSurroundFormats(mContentResolver, true);
@@ -2258,9 +2259,10 @@
SENDMSG_QUEUE,
AudioSystem.FOR_DOCK,
mDockAudioMediaEnabled ?
- AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE,
+ AudioSystem.FORCE_DIGITAL_DOCK : AudioSystem.FORCE_NONE,
new String("readDockAudioSettings"),
0);
+
}
@@ -3601,9 +3603,11 @@
setRingerMode(getNewRingerMode(stream, index, flags),
TAG + ".onSetStreamVolume", false /*external*/);
}
- // setting non-zero volume for a muted stream unmutes the stream and vice versa,
+ // setting non-zero volume for a muted stream unmutes the stream and vice versa
+ // (only when changing volume for the current device),
// except for BT SCO stream where only explicit mute is allowed to comply to BT requirements
- if (streamType != AudioSystem.STREAM_BLUETOOTH_SCO) {
+ if ((streamType != AudioSystem.STREAM_BLUETOOTH_SCO)
+ && (getDeviceForStream(stream) == device)) {
mStreamStates[stream].mute(index == 0);
}
}
@@ -3741,19 +3745,30 @@
Objects.requireNonNull(ada);
Objects.requireNonNull(callingPackage);
- AudioService.sVolumeLogger.loglogi("setDeviceVolume" + " from:" + callingPackage + " "
- + vi + " " + ada, TAG);
-
if (!vi.hasStreamType()) {
Log.e(TAG, "Unsupported non-stream type based VolumeInfo", new Exception());
return;
}
+
int index = vi.getVolumeIndex();
if (index == VolumeInfo.INDEX_NOT_SET && !vi.hasMuteCommand()) {
throw new IllegalArgumentException(
"changing device volume requires a volume index or mute command");
}
+ // force a cache clear to force reevaluating stream type to audio device selection
+ // that can interfere with the sending of the VOLUME_CHANGED_ACTION intent
+ // TODO change cache management to not rely only on invalidation, but on "do not trust"
+ // moments when routing is in flux.
+ mAudioSystem.clearRoutingCache();
+
+ // log the current device that will be used when evaluating the sending of the
+ // VOLUME_CHANGED_ACTION intent to see if the current device is the one being modified
+ final int currDev = getDeviceForStream(vi.getStreamType());
+
+ AudioService.sVolumeLogger.log(new DeviceVolumeEvent(vi.getStreamType(), index, ada,
+ currDev, callingPackage));
+
// TODO handle unmuting of current audio device
// if a stream is not muted but the VolumeInfo is for muting, set the volume index
// for the device to min volume
@@ -3837,11 +3852,11 @@
return;
}
- final AudioEventLogger.Event event = (device == null)
- ? new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
- index/*val1*/, flags/*val2*/, callingPackage)
- : new DeviceVolumeEvent(streamType, index, device, callingPackage);
- sVolumeLogger.log(event);
+ if (device == null) {
+ // call was already logged in setDeviceVolume()
+ sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
+ index/*val1*/, flags/*val2*/, callingPackage));
+ }
setStreamVolume(streamType, index, flags, device,
callingPackage, callingPackage, attributionTag,
Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission());
@@ -4242,7 +4257,11 @@
maybeSendSystemAudioStatusCommand(false);
}
}
- sendVolumeUpdate(streamType, oldIndex, index, flags, device);
+ if (ada == null) {
+ // only non-null when coming here from setDeviceVolume
+ // TODO change test to check early if device is current device or not
+ sendVolumeUpdate(streamType, oldIndex, index, flags, device);
+ }
}
@@ -7982,6 +8001,8 @@
mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
mStreamVolumeAlias[mStreamType]);
+ AudioService.sVolumeLogger.log(new VolChangedBroadcastEvent(
+ mStreamType, mStreamVolumeAlias[mStreamType], index));
sendBroadcastToAll(mVolumeChanged);
}
}
@@ -10155,7 +10176,7 @@
static final int LOG_NB_EVENTS_PHONE_STATE = 20;
static final int LOG_NB_EVENTS_DEVICE_CONNECTION = 50;
static final int LOG_NB_EVENTS_FORCE_USE = 20;
- static final int LOG_NB_EVENTS_VOLUME = 40;
+ static final int LOG_NB_EVENTS_VOLUME = 100;
static final int LOG_NB_EVENTS_DYN_POLICY = 10;
static final int LOG_NB_EVENTS_SPATIAL = 30;
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 30a9e0a7..c2c3f02 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -147,19 +147,42 @@
}
}
+ static final class VolChangedBroadcastEvent extends AudioEventLogger.Event {
+ final int mStreamType;
+ final int mAliasStreamType;
+ final int mIndex;
+
+ VolChangedBroadcastEvent(int stream, int alias, int index) {
+ mStreamType = stream;
+ mAliasStreamType = alias;
+ mIndex = index;
+ }
+
+ @Override
+ public String eventToString() {
+ return new StringBuilder("sending VOLUME_CHANGED stream:")
+ .append(AudioSystem.streamToString(mStreamType))
+ .append(" index:").append(mIndex)
+ .append(" alias:").append(AudioSystem.streamToString(mAliasStreamType))
+ .toString();
+ }
+ }
+
static final class DeviceVolumeEvent extends AudioEventLogger.Event {
final int mStream;
final int mVolIndex;
final String mDeviceNativeType;
final String mDeviceAddress;
final String mCaller;
+ final int mDeviceForStream;
DeviceVolumeEvent(int streamType, int index, @NonNull AudioDeviceAttributes device,
- String callingPackage) {
+ int deviceForStream, String callingPackage) {
mStream = streamType;
mVolIndex = index;
mDeviceNativeType = "0x" + Integer.toHexString(device.getInternalType());
mDeviceAddress = device.getAddress();
+ mDeviceForStream = deviceForStream;
mCaller = callingPackage;
// log metrics
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_VOLUME_EVENT)
@@ -180,7 +203,9 @@
.append(" index:").append(mVolIndex)
.append(" device:").append(mDeviceNativeType)
.append(" addr:").append(mDeviceAddress)
- .append(") from ").append(mCaller).toString();
+ .append(") from ").append(mCaller)
+ .append(" currDevForStream:Ox").append(Integer.toHexString(mDeviceForStream))
+ .toString();
}
}
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index c3754eb..2588371 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -105,6 +105,13 @@
}
}
+ public void clearRoutingCache() {
+ if (DEBUG_CACHE) {
+ Log.d(TAG, "---- routing cache clear (from java) ----------");
+ }
+ invalidateRoutingCache();
+ }
+
/**
* Implementation of AudioSystem.VolumeRangeInitRequestCallback
*/
@@ -337,6 +344,7 @@
* @return
*/
public int setParameters(String keyValuePairs) {
+ invalidateRoutingCache();
return AudioSystem.setParameters(keyValuePairs);
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 54be4bb..1862942 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -58,13 +58,15 @@
public final class PlaybackActivityMonitor
implements AudioPlaybackConfiguration.PlayerDeathMonitor, PlayerFocusEnforcer {
- public static final String TAG = "AudioService.PlaybackActivityMonitor";
+ public static final String TAG = "AS.PlayActivityMonitor";
/*package*/ static final boolean DEBUG = false;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_FADEOUT_ID = 2;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID = 3;
+ /*package*/ static final int VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID = 4;
+ // ducking settings for a "normal duck" at -14dB
private static final VolumeShaper.Configuration DUCK_VSHAPE =
new VolumeShaper.Configuration.Builder()
.setId(VOLUME_SHAPER_SYSTEM_DUCK_ID)
@@ -78,6 +80,22 @@
.build();
private static final VolumeShaper.Configuration DUCK_ID =
new VolumeShaper.Configuration(VOLUME_SHAPER_SYSTEM_DUCK_ID);
+
+ // ducking settings for a "strong duck" at -35dB (attenuation factor of 0.017783)
+ private static final VolumeShaper.Configuration STRONG_DUCK_VSHAPE =
+ new VolumeShaper.Configuration.Builder()
+ .setId(VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID)
+ .setCurve(new float[] { 0.f, 1.f } /* times */,
+ new float[] { 1.f, 0.017783f } /* volumes */)
+ .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+ .setDuration(MediaFocusControl.getFocusRampTimeMs(
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+ new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION)
+ .build()))
+ .build();
+ private static final VolumeShaper.Configuration STRONG_DUCK_ID =
+ new VolumeShaper.Configuration(VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID);
+
private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED =
new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY)
.createIfNeeded()
@@ -659,11 +677,23 @@
// add the players eligible for ducking to the list, and duck them
// (if apcsToDuck is empty, this will at least mark this uid as ducked, so when
// players of the same uid start, they will be ducked by DuckingManager.checkDuck())
- mDuckingManager.duckUid(loser.getClientUid(), apcsToDuck);
+ mDuckingManager.duckUid(loser.getClientUid(), apcsToDuck, reqCausesStrongDuck(winner));
}
return true;
}
+ private boolean reqCausesStrongDuck(FocusRequester requester) {
+ if (requester.getGainRequest() != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
+ return false;
+ }
+ final int reqUsage = requester.getAudioAttributes().getUsage();
+ if ((reqUsage == AudioAttributes.USAGE_ASSISTANT)
+ || (reqUsage == AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)) {
+ return true;
+ }
+ return false;
+ }
+
@Override
public void restoreVShapedPlayers(@NonNull FocusRequester winner) {
if (DEBUG) { Log.v(TAG, "unduckPlayers: uids winner=" + winner.getClientUid()); }
@@ -939,10 +969,11 @@
private static final class DuckingManager {
private final HashMap<Integer, DuckedApp> mDuckers = new HashMap<Integer, DuckedApp>();
- synchronized void duckUid(int uid, ArrayList<AudioPlaybackConfiguration> apcsToDuck) {
+ synchronized void duckUid(int uid, ArrayList<AudioPlaybackConfiguration> apcsToDuck,
+ boolean requestCausesStrongDuck) {
if (DEBUG) { Log.v(TAG, "DuckingManager: duckUid() uid:"+ uid); }
if (!mDuckers.containsKey(uid)) {
- mDuckers.put(uid, new DuckedApp(uid));
+ mDuckers.put(uid, new DuckedApp(uid, requestCausesStrongDuck));
}
final DuckedApp da = mDuckers.get(uid);
for (AudioPlaybackConfiguration apc : apcsToDuck) {
@@ -989,10 +1020,13 @@
private static final class DuckedApp {
private final int mUid;
+ /** determines whether ducking is done with DUCK_VSHAPE or STRONG_DUCK_VSHAPE */
+ private final boolean mUseStrongDuck;
private final ArrayList<Integer> mDuckedPlayers = new ArrayList<Integer>();
- DuckedApp(int uid) {
+ DuckedApp(int uid, boolean useStrongDuck) {
mUid = uid;
+ mUseStrongDuck = useStrongDuck;
}
void dump(PrintWriter pw) {
@@ -1013,9 +1047,9 @@
return;
}
try {
- sEventLogger.log((new DuckEvent(apc, skipRamp)).printLog(TAG));
+ sEventLogger.log((new DuckEvent(apc, skipRamp, mUseStrongDuck)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
- DUCK_VSHAPE,
+ mUseStrongDuck ? STRONG_DUCK_VSHAPE : DUCK_VSHAPE,
skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
mDuckedPlayers.add(piid);
} catch (Exception e) {
@@ -1031,7 +1065,7 @@
sEventLogger.log((new AudioEventLogger.StringEvent("unducking piid:"
+ piid)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
- DUCK_ID,
+ mUseStrongDuck ? STRONG_DUCK_ID : DUCK_ID,
VolumeShaper.Operation.REVERSE);
} catch (Exception e) {
Log.e(TAG, "Error unducking player piid:" + piid + " uid:" + mUid, e);
@@ -1146,13 +1180,17 @@
}
static final class DuckEvent extends VolumeShaperEvent {
+ final boolean mUseStrongDuck;
+
@Override
String getVSAction() {
- return "ducking";
+ return mUseStrongDuck ? "ducking (strong)" : "ducking";
}
- DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
+ DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp, boolean useStrongDuck)
+ {
super(apc, skipRamp);
+ mUseStrongDuck = useStrongDuck;
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 57ea812..1924f3c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -96,7 +96,11 @@
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
Slog.d(TAG, "Remove onClientFinished: " + clientMonitor + ", success: " + success);
- mCallback.onClientFinished(InternalCleanupClient.this, success);
+ if (mUnknownHALTemplates.isEmpty()) {
+ mCallback.onClientFinished(InternalCleanupClient.this, success);
+ } else {
+ startCleanupUnknownHalTemplates();
+ }
}
};
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 05e83da..787bfb0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -16,15 +16,11 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
-import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
-import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.TaskStackListener;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
import android.hardware.biometrics.common.ICancellationSignal;
@@ -92,7 +88,6 @@
private long mSideFpsLastAcquireStartTime;
private Runnable mAuthSuccessRunnable;
private final Clock mClock;
- private boolean mDidFinishSfps;
FingerprintAuthenticationClient(
@NonNull Context context,
@@ -198,9 +193,8 @@
@Override
protected void handleLifecycleAfterAuth(boolean authenticated) {
- if (authenticated && !mDidFinishSfps) {
+ if (authenticated) {
mCallback.onClientFinished(this, true /* success */);
- mDidFinishSfps = true;
}
}
@@ -210,13 +204,11 @@
return false;
}
- public void handleAuthenticate(
+ @Override
+ public void onAuthenticated(
BiometricAuthenticator.Identifier identifier,
boolean authenticated,
ArrayList<Byte> token) {
- if (authenticated && mSensorProps.isAnySidefpsType()) {
- Slog.i(TAG, "(sideFPS): No power press detected, sending auth");
- }
super.onAuthenticated(identifier, authenticated, token);
if (authenticated) {
mState = STATE_STOPPED;
@@ -227,72 +219,11 @@
}
@Override
- public void onAuthenticated(
- BiometricAuthenticator.Identifier identifier,
- boolean authenticated,
- ArrayList<Byte> token) {
-
- mHandler.post(
- () -> {
- long delay = 0;
- if (authenticated && mSensorProps.isAnySidefpsType()) {
- delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
-
- if (mSideFpsLastAcquireStartTime != -1) {
- delay = Math.max(0,
- delay - (mClock.millis() - mSideFpsLastAcquireStartTime));
- }
-
- Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps "
- + "waiting for power until: " + delay + "ms");
- }
-
- if (mHandler.hasMessages(MESSAGE_FINGER_UP)) {
- Slog.i(TAG, "Finger up detected, sending auth");
- delay = 0;
- }
-
- mAuthSuccessRunnable =
- () -> handleAuthenticate(identifier, authenticated, token);
- mHandler.postDelayed(
- mAuthSuccessRunnable,
- MESSAGE_AUTH_SUCCESS,
- delay);
- });
- }
-
- @Override
public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) {
// For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off
// for most ACQUIRED messages. See BiometricFingerprintConstants#FingerprintAcquired
mSensorOverlays.ifUdfps(controller -> controller.onAcquired(getSensorId(), acquiredInfo));
super.onAcquired(acquiredInfo, vendorCode);
- if (mSensorProps.isAnySidefpsType()) {
- if (acquiredInfo == FINGERPRINT_ACQUIRED_START) {
- mSideFpsLastAcquireStartTime = mClock.millis();
- }
- final boolean shouldLookForVendor =
- mSkipWaitForPowerAcquireMessage == FINGERPRINT_ACQUIRED_VENDOR;
- final boolean acquireMessageMatch = acquiredInfo == mSkipWaitForPowerAcquireMessage;
- final boolean vendorMessageMatch = vendorCode == mSkipWaitForPowerVendorAcquireMessage;
- final boolean ignorePowerPress =
- acquireMessageMatch && (!shouldLookForVendor || vendorMessageMatch);
-
- if (ignorePowerPress) {
- Slog.d(TAG, "(sideFPS) onFingerUp");
- mHandler.post(() -> {
- if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) {
- Slog.d(TAG, "(sideFPS) skipping wait for power");
- mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
- mHandler.post(mAuthSuccessRunnable);
- } else {
- mHandler.postDelayed(() -> {
- }, MESSAGE_FINGER_UP, mFingerUpIgnoresPower);
- }
- });
- }
- }
-
}
@Override
@@ -488,22 +419,5 @@
}
@Override
- public void onPowerPressed() {
- if (mSensorProps.isAnySidefpsType()) {
- Slog.i(TAG, "(sideFPS): onPowerPressed");
- mHandler.post(() -> {
- if (mDidFinishSfps) {
- return;
- }
- Slog.i(TAG, "(sideFPS): finishing auth");
- // Ignore auths after a power has been detected
- mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
- // Do not call onError() as that will send an additional callback to coex.
- mDidFinishSfps = true;
- onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
- stopHalOperation();
- mSensorOverlays.hide(getSensorId());
- });
- }
- }
+ public void onPowerPressed() { }
}
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 9dd2f84..b9ca57e 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -76,6 +76,10 @@
return layout;
}
+ int size() {
+ return mLayoutMap.size();
+ }
+
private Layout createLayout(int state) {
if (mLayoutMap.contains(state)) {
Slog.e(TAG, "Attempted to create a second layout for state " + state);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 9278743..2b7fbfb 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -402,6 +402,8 @@
private static final String STABLE_ID_SUFFIX_FORMAT = "id_%d";
private static final String NO_SUFFIX_FORMAT = "%d";
private static final long STABLE_FLAG = 1L << 62;
+ private static final int DEFAULT_PEAK_REFRESH_RATE = 0;
+ private static final int DEFAULT_REFRESH_RATE = 60;
private static final int DEFAULT_LOW_REFRESH_RATE = 60;
private static final int DEFAULT_HIGH_REFRESH_RATE = 0;
private static final int[] DEFAULT_BRIGHTNESS_THRESHOLDS = new int[]{};
@@ -570,17 +572,29 @@
* using higher refresh rates, even if display modes with higher refresh rates are available
* from hardware composer. Only has an effect if the value is non-zero.
*/
- private int mDefaultHighRefreshRate = DEFAULT_HIGH_REFRESH_RATE;
+ private int mDefaultPeakRefreshRate = DEFAULT_PEAK_REFRESH_RATE;
/**
* The default refresh rate for a given device. This value sets the higher default
* refresh rate. If the hardware composer on the device supports display modes with
* a higher refresh rate than the default value specified here, the framework may use those
* higher refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
- * setFrameRate(). We have historically allowed fallback to mDefaultHighRefreshRate if
- * mDefaultLowRefreshRate is set to 0, but this is not supported anymore.
+ * setFrameRate(). We have historically allowed fallback to mDefaultPeakRefreshRate if
+ * mDefaultRefreshRate is set to 0, but this is not supported anymore.
*/
- private int mDefaultLowRefreshRate = DEFAULT_LOW_REFRESH_RATE;
+ private int mDefaultRefreshRate = DEFAULT_REFRESH_RATE;
+
+ /**
+ * Default refresh rate in the high zone defined by brightness and ambient thresholds.
+ * If non-positive, then the refresh rate is unchanged even if thresholds are configured.
+ */
+ private int mDefaultHighBlockingZoneRefreshRate = DEFAULT_HIGH_REFRESH_RATE;
+
+ /**
+ * Default refresh rate in the zone defined by brightness and ambient thresholds.
+ * If non-positive, then the refresh rate is unchanged even if thresholds are configured.
+ */
+ private int mDefaultLowBlockingZoneRefreshRate = DEFAULT_LOW_REFRESH_RATE;
/**
* The display uses different gamma curves for different refresh rates. It's hard for panel
@@ -1296,15 +1310,29 @@
/**
* @return Default peak refresh rate of the associated display
*/
- public int getDefaultHighRefreshRate() {
- return mDefaultHighRefreshRate;
+ public int getDefaultPeakRefreshRate() {
+ return mDefaultPeakRefreshRate;
}
/**
* @return Default refresh rate of the associated display
*/
- public int getDefaultLowRefreshRate() {
- return mDefaultLowRefreshRate;
+ public int getDefaultRefreshRate() {
+ return mDefaultRefreshRate;
+ }
+
+ /**
+ * @return Default refresh rate in the higher blocking zone of the associated display
+ */
+ public int getDefaultHighBlockingZoneRefreshRate() {
+ return mDefaultHighBlockingZoneRefreshRate;
+ }
+
+ /**
+ * @return Default refresh rate in the lower blocking zone of the associated display
+ */
+ public int getDefaultLowBlockingZoneRefreshRate() {
+ return mDefaultLowBlockingZoneRefreshRate;
}
/**
@@ -1442,8 +1470,10 @@
+ ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+ ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+ "\n"
- + ", mDefaultRefreshRate= " + mDefaultLowRefreshRate
- + ", mDefaultPeakRefreshRate= " + mDefaultHighRefreshRate
+ + ", mDefaultLowBlockingZoneRefreshRate= " + mDefaultLowBlockingZoneRefreshRate
+ + ", mDefaultHighBlockingZoneRefreshRate= " + mDefaultHighBlockingZoneRefreshRate
+ + ", mDefaultPeakRefreshRate= " + mDefaultPeakRefreshRate
+ + ", mDefaultRefreshRate= " + mDefaultRefreshRate
+ ", mLowDisplayBrightnessThresholds= "
+ Arrays.toString(mLowDisplayBrightnessThresholds)
+ ", mLowAmbientBrightnessThresholds= "
@@ -1757,10 +1787,31 @@
BlockingZoneConfig higherBlockingZoneConfig =
(refreshRateConfigs == null) ? null
: refreshRateConfigs.getHigherBlockingZoneConfigs();
+ loadPeakDefaultRefreshRate(refreshRateConfigs);
+ loadDefaultRefreshRate(refreshRateConfigs);
loadLowerRefreshRateBlockingZones(lowerBlockingZoneConfig);
loadHigherRefreshRateBlockingZones(higherBlockingZoneConfig);
}
+ private void loadPeakDefaultRefreshRate(RefreshRateConfigs refreshRateConfigs) {
+ if (refreshRateConfigs == null || refreshRateConfigs.getDefaultPeakRefreshRate() == null) {
+ mDefaultPeakRefreshRate = mContext.getResources().getInteger(
+ R.integer.config_defaultPeakRefreshRate);
+ } else {
+ mDefaultPeakRefreshRate =
+ refreshRateConfigs.getDefaultPeakRefreshRate().intValue();
+ }
+ }
+
+ private void loadDefaultRefreshRate(RefreshRateConfigs refreshRateConfigs) {
+ if (refreshRateConfigs == null || refreshRateConfigs.getDefaultRefreshRate() == null) {
+ mDefaultRefreshRate = mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRate);
+ } else {
+ mDefaultRefreshRate =
+ refreshRateConfigs.getDefaultRefreshRate().intValue();
+ }
+ }
/**
* Loads the refresh rate configurations pertaining to the upper blocking zones.
@@ -1785,10 +1836,10 @@
private void loadHigherBlockingZoneDefaultRefreshRate(
BlockingZoneConfig upperBlockingZoneConfig) {
if (upperBlockingZoneConfig == null) {
- mDefaultHighRefreshRate = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_defaultPeakRefreshRate);
+ mDefaultHighBlockingZoneRefreshRate = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_fixedRefreshRateInHighZone);
} else {
- mDefaultHighRefreshRate =
+ mDefaultHighBlockingZoneRefreshRate =
upperBlockingZoneConfig.getDefaultRefreshRate().intValue();
}
}
@@ -1800,10 +1851,10 @@
private void loadLowerBlockingZoneDefaultRefreshRate(
BlockingZoneConfig lowerBlockingZoneConfig) {
if (lowerBlockingZoneConfig == null) {
- mDefaultLowRefreshRate = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_defaultRefreshRate);
+ mDefaultLowBlockingZoneRefreshRate = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultRefreshRateInZone);
} else {
- mDefaultLowRefreshRate =
+ mDefaultLowBlockingZoneRefreshRate =
lowerBlockingZoneConfig.getDefaultRefreshRate().intValue();
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 8f35924..5cfe65b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -104,6 +104,7 @@
import android.provider.Settings;
import android.sysprop.DisplayProperties;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.IntArray;
@@ -256,6 +257,13 @@
final SparseArray<Pair<IVirtualDevice, DisplayWindowPolicyController>>
mDisplayWindowPolicyControllers = new SparseArray<>();
+ /**
+ * Map of every internal primary display device {@link HighBrightnessModeMetadata}s indexed by
+ * {@link DisplayDevice#mUniqueId}.
+ */
+ public final ArrayMap<String, HighBrightnessModeMetadata> mHighBrightnessModeMetadataMap =
+ new ArrayMap<>();
+
// List of all currently registered display adapters.
private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>();
@@ -1570,7 +1578,16 @@
DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
- dpc.onDisplayChanged();
+ final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+ if (device == null) {
+ Slog.wtf(TAG, "Display Device is null in DisplayManagerService for display: "
+ + display.getDisplayIdLocked());
+ return;
+ }
+
+ final String uniqueId = device.getUniqueId();
+ HighBrightnessModeMetadata hbmMetadata = mHighBrightnessModeMetadataMap.get(uniqueId);
+ dpc.onDisplayChanged(hbmMetadata);
}
}
@@ -1627,7 +1644,15 @@
final int displayId = display.getDisplayIdLocked();
final DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
- dpc.onDisplayChanged();
+ final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+ if (device == null) {
+ Slog.wtf(TAG, "Display Device is null in DisplayManagerService for display: "
+ + display.getDisplayIdLocked());
+ return;
+ }
+ final String uniqueId = device.getUniqueId();
+ HighBrightnessModeMetadata hbmMetadata = mHighBrightnessModeMetadataMap.get(uniqueId);
+ dpc.onDisplayChanged(hbmMetadata);
}
}
@@ -2611,6 +2636,31 @@
mLogicalDisplayMapper.forEachLocked(this::addDisplayPowerControllerLocked);
}
+ private HighBrightnessModeMetadata getHighBrightnessModeMetadata(LogicalDisplay display) {
+ final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+ if (device == null) {
+ Slog.wtf(TAG, "Display Device is null in DisplayPowerController for display: "
+ + display.getDisplayIdLocked());
+ return null;
+ }
+
+ // HBM brightness mode is only applicable to internal physical displays.
+ if (display.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) {
+ return null;
+ }
+
+ final String uniqueId = device.getUniqueId();
+
+ if (mHighBrightnessModeMetadataMap.containsKey(uniqueId)) {
+ return mHighBrightnessModeMetadataMap.get(uniqueId);
+ }
+
+ // HBM Time info not present. Create a new one for this physical display.
+ HighBrightnessModeMetadata hbmInfo = new HighBrightnessModeMetadata();
+ mHighBrightnessModeMetadataMap.put(uniqueId, hbmInfo);
+ return hbmInfo;
+ }
+
private void addDisplayPowerControllerLocked(LogicalDisplay display) {
if (mPowerHandler == null) {
// initPowerManagement has not yet been called.
@@ -2622,10 +2672,18 @@
final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore,
display, mSyncRoot);
+
+ // If display is internal and has a HighBrightnessModeMetadata mapping, use that.
+ // Or create a new one and use that.
+ // We also need to pass a mapping of the HighBrightnessModeTimeInfoMap to
+ // displayPowerController, so the hbm info can be correctly associated
+ // with the corresponding displaydevice.
+ HighBrightnessModeMetadata hbmMetadata = getHighBrightnessModeMetadata(display);
+
final DisplayPowerController displayPowerController = new DisplayPowerController(
mContext, mDisplayPowerCallbacks, mPowerHandler, mSensorManager,
mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
- () -> handleBrightnessChange(display));
+ () -> handleBrightnessChange(display), hbmMetadata);
mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController);
}
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index aafba5a..fdfc20a 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -1169,7 +1169,7 @@
mDefaultRefreshRate =
(displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
R.integer.config_defaultRefreshRate)
- : (float) displayDeviceConfig.getDefaultLowRefreshRate();
+ : (float) displayDeviceConfig.getDefaultRefreshRate();
}
public void observe() {
@@ -1256,7 +1256,7 @@
defaultPeakRefreshRate =
(displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
R.integer.config_defaultPeakRefreshRate)
- : (float) displayDeviceConfig.getDefaultHighRefreshRate();
+ : (float) displayDeviceConfig.getDefaultPeakRefreshRate();
}
mDefaultPeakRefreshRate = defaultPeakRefreshRate;
}
@@ -1612,8 +1612,26 @@
return mHighAmbientBrightnessThresholds;
}
+ /**
+ * @return the refresh rate to lock to when in a high brightness zone
+ */
+ @VisibleForTesting
+ int getRefreshRateInHighZone() {
+ return mRefreshRateInHighZone;
+ }
+
+ /**
+ * @return the refresh rate to lock to when in a low brightness zone
+ */
+ @VisibleForTesting
+ int getRefreshRateInLowZone() {
+ return mRefreshRateInLowZone;
+ }
+
private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
boolean attemptLoadingFromDeviceConfig) {
+ loadRefreshRateInHighZone(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+ loadRefreshRateInLowZone(displayDeviceConfig, attemptLoadingFromDeviceConfig);
mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
() -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
() -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
@@ -1634,6 +1652,44 @@
}
}
+ private void loadRefreshRateInLowZone(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
+ int refreshRateInLowZone =
+ (displayDeviceConfig == null) ? mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRateInZone)
+ : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate();
+ if (attemptLoadingFromDeviceConfig) {
+ try {
+ refreshRateInLowZone = mDeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE,
+ refreshRateInLowZone);
+ } catch (Exception exception) {
+ // Do nothing
+ }
+ }
+ mRefreshRateInLowZone = refreshRateInLowZone;
+ }
+
+ private void loadRefreshRateInHighZone(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
+ int refreshRateInHighZone =
+ (displayDeviceConfig == null) ? mContext.getResources().getInteger(
+ R.integer.config_fixedRefreshRateInHighZone) : displayDeviceConfig
+ .getDefaultHighBlockingZoneRefreshRate();
+ if (attemptLoadingFromDeviceConfig) {
+ try {
+ refreshRateInHighZone = mDeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE,
+ refreshRateInHighZone);
+ } catch (Exception exception) {
+ // Do nothing
+ }
+ }
+ mRefreshRateInHighZone = refreshRateInHighZone;
+ }
+
private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
boolean attemptLoadingFromDeviceConfig) {
mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
@@ -1687,14 +1743,6 @@
}
/**
- * @return the refresh to lock to when in a low brightness zone
- */
- @VisibleForTesting
- int getRefreshRateInLowZone() {
- return mRefreshRateInLowZone;
- }
-
- /**
* @return the display brightness thresholds for the low brightness zones
*/
@VisibleForTesting
@@ -1739,8 +1787,17 @@
mHighAmbientBrightnessThresholds = highAmbientBrightnessThresholds;
}
- mRefreshRateInLowZone = mDeviceConfigDisplaySettings.getRefreshRateInLowZone();
- mRefreshRateInHighZone = mDeviceConfigDisplaySettings.getRefreshRateInHighZone();
+ final int refreshRateInLowZone = mDeviceConfigDisplaySettings
+ .getRefreshRateInLowZone();
+ if (refreshRateInLowZone != -1) {
+ mRefreshRateInLowZone = refreshRateInLowZone;
+ }
+
+ final int refreshRateInHighZone = mDeviceConfigDisplaySettings
+ .getRefreshRateInHighZone();
+ if (refreshRateInHighZone != -1) {
+ mRefreshRateInHighZone = refreshRateInHighZone;
+ }
restartObserver();
mDeviceConfigDisplaySettings.startListening();
@@ -1794,6 +1851,10 @@
restartObserver();
}
+ /**
+ * Used to reload the lower blocking zone refresh rate in case of changes in the
+ * DeviceConfig properties.
+ */
public void onDeviceConfigRefreshRateInLowZoneChanged(int refreshRate) {
if (refreshRate != mRefreshRateInLowZone) {
mRefreshRateInLowZone = refreshRate;
@@ -1817,6 +1878,10 @@
restartObserver();
}
+ /**
+ * Used to reload the higher blocking zone refresh rate in case of changes in the
+ * DeviceConfig properties.
+ */
public void onDeviceConfigRefreshRateInHighZoneChanged(int refreshRate) {
if (refreshRate != mRefreshRateInHighZone) {
mRefreshRateInHighZone = refreshRate;
@@ -2664,15 +2729,10 @@
}
public int getRefreshRateInLowZone() {
- int defaultRefreshRateInZone = mContext.getResources().getInteger(
- R.integer.config_defaultRefreshRateInZone);
-
- int refreshRate = mDeviceConfig.getInt(
+ return mDeviceConfig.getInt(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE,
- defaultRefreshRateInZone);
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1);
- return refreshRate;
}
/*
@@ -2694,15 +2754,10 @@
}
public int getRefreshRateInHighZone() {
- int defaultRefreshRateInZone = mContext.getResources().getInteger(
- R.integer.config_fixedRefreshRateInHighZone);
-
- int refreshRate = mDeviceConfig.getInt(
+ return mDeviceConfig.getInt(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE,
- defaultRefreshRateInZone);
-
- return refreshRate;
+ -1);
}
public int getRefreshRateInHbmSunlight() {
@@ -2750,23 +2805,29 @@
int[] lowDisplayBrightnessThresholds = getLowDisplayBrightnessThresholds();
int[] lowAmbientBrightnessThresholds = getLowAmbientBrightnessThresholds();
- int refreshRateInLowZone = getRefreshRateInLowZone();
+ final int refreshRateInLowZone = getRefreshRateInLowZone();
mHandler.obtainMessage(MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED,
new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds))
.sendToTarget();
- mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone, 0)
+
+ if (refreshRateInLowZone != -1) {
+ mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone)
.sendToTarget();
+ }
int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds();
int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds();
- int refreshRateInHighZone = getRefreshRateInHighZone();
+ final int refreshRateInHighZone = getRefreshRateInHighZone();
mHandler.obtainMessage(MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED,
new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds))
.sendToTarget();
- mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone, 0)
+
+ if (refreshRateInHighZone != -1) {
+ mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone)
.sendToTarget();
+ }
final int refreshRateInHbmSunlight = getRefreshRateInHbmSunlight();
mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d6e78a1..b431306 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -391,6 +391,7 @@
private float[] mNitsRange;
private final HighBrightnessModeController mHbmController;
+ private final HighBrightnessModeMetadata mHighBrightnessModeMetadata;
private final BrightnessThrottler mBrightnessThrottler;
@@ -511,7 +512,7 @@
DisplayPowerCallbacks callbacks, Handler handler,
SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
- Runnable onBrightnessChangeRunnable) {
+ Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata) {
mLogicalDisplay = logicalDisplay;
mDisplayId = mLogicalDisplay.getDisplayIdLocked();
final String displayIdStr = "[" + mDisplayId + "]";
@@ -521,6 +522,7 @@
mSuspendBlockerIdProxPositive = displayIdStr + "prox positive";
mSuspendBlockerIdProxNegative = displayIdStr + "prox negative";
mSuspendBlockerIdProxDebounce = displayIdStr + "prox debounce";
+ mHighBrightnessModeMetadata = hbmMetadata;
mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
@@ -793,7 +795,7 @@
* of each display need to be properly reflected in AutomaticBrightnessController.
*/
@GuardedBy("DisplayManagerService.mSyncRoot")
- public void onDisplayChanged() {
+ public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata) {
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
if (device == null) {
Slog.wtf(TAG, "Display Device is null in DisplayPowerController for display: "
@@ -815,11 +817,11 @@
mUniqueDisplayId = uniqueId;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
- loadFromDisplayDeviceConfig(token, info);
+ loadFromDisplayDeviceConfig(token, info, hbmMetadata);
- // Since the underlying display-device changed, we really don't know the
- // last command that was sent to change it's state. Lets assume it is off and we
- // trigger a change immediately.
+ /// Since the underlying display-device changed, we really don't know the
+ // last command that was sent to change it's state. Lets assume it is unknown so
+ // that we trigger a change immediately.
mPowerState.resetScreenState();
}
if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
@@ -872,7 +874,8 @@
}
}
- private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info) {
+ private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info,
+ HighBrightnessModeMetadata hbmMetadata) {
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
loadBrightnessRampRates();
@@ -885,6 +888,7 @@
mBrightnessRampIncreaseMaxTimeMillis,
mBrightnessRampDecreaseMaxTimeMillis);
}
+ mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
mDisplayDeviceConfig.getHighBrightnessModeData(),
new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
@@ -1965,7 +1969,7 @@
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.update();
}
- }, mContext);
+ }, mHighBrightnessModeMetadata, mContext);
}
private BrightnessThrottler createBrightnessThrottlerLocked() {
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 7d1396d..2c257a1 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -340,20 +340,12 @@
}
/**
- * Resets the screen state to {@link Display#STATE_OFF}. Even though we do not know the last
- * state that was sent to the underlying display-device, we assume it is off.
- *
- * We do not set the screen state to {@link Display#STATE_UNKNOWN} to avoid getting in the state
- * where PhotonicModulator holds onto the lock. This happens because we currently try to keep
- * the mScreenState and mPendingState in sync, however if the screenState is set to
- * {@link Display#STATE_UNKNOWN} here, mPendingState will get progressed to this, which will
- * force the PhotonicModulator thread to wait onto the lock to take it out of that state.
- * b/262294651 for more info.
+ * Resets the screen state to unknown. Useful when the underlying display-device changes for the
+ * LogicalDisplay and we do not know the last state that was sent to it.
*/
void resetScreenState() {
- mScreenState = Display.STATE_OFF;
+ mScreenState = Display.STATE_UNKNOWN;
mScreenReady = false;
- scheduleScreenUpdate();
}
private void scheduleScreenUpdate() {
@@ -514,6 +506,8 @@
boolean valid = state != Display.STATE_UNKNOWN && !Float.isNaN(brightnessState);
boolean changed = stateChanged || backlightChanged;
if (!valid || !changed) {
+ mStateChangeInProgress = false;
+ mBacklightChangeInProgress = false;
try {
mLock.wait();
} catch (InterruptedException ex) {
diff --git a/services/core/java/com/android/server/display/HbmEvent.java b/services/core/java/com/android/server/display/HbmEvent.java
new file mode 100644
index 0000000..5675e2f
--- /dev/null
+++ b/services/core/java/com/android/server/display/HbmEvent.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+
+/**
+ * Represents an event in which High Brightness Mode was enabled.
+ */
+class HbmEvent {
+ private long mStartTimeMillis;
+ private long mEndTimeMillis;
+
+ HbmEvent(long startTimeMillis, long endTimeMillis) {
+ this.mStartTimeMillis = startTimeMillis;
+ this.mEndTimeMillis = endTimeMillis;
+ }
+
+ public long getStartTimeMillis() {
+ return mStartTimeMillis;
+ }
+
+ public long getEndTimeMillis() {
+ return mEndTimeMillis;
+ }
+
+ @Override
+ public String toString() {
+ return "HbmEvent: {startTimeMillis:" + mStartTimeMillis + ", endTimeMillis: "
+ + mEndTimeMillis + "}, total: "
+ + ((mEndTimeMillis - mStartTimeMillis) / 1000) + "]";
+ }
+}
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 0b9d4de..ac32d53 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -42,8 +42,8 @@
import com.android.server.display.DisplayManagerService.Clock;
import java.io.PrintWriter;
+import java.util.ArrayDeque;
import java.util.Iterator;
-import java.util.LinkedList;
/**
* Controls the status of high-brightness mode for devices that support it. This class assumes that
@@ -105,30 +105,24 @@
private int mHbmStatsState = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
/**
- * If HBM is currently running, this is the start time for the current HBM session.
+ * If HBM is currently running, this is the start time and set of all events,
+ * for the current HBM session.
*/
- private long mRunningStartTimeMillis = -1;
-
- /**
- * List of previous HBM-events ordered from most recent to least recent.
- * Meant to store only the events that fall into the most recent
- * {@link mHbmData.timeWindowMillis}.
- */
- private LinkedList<HbmEvent> mEvents = new LinkedList<>();
+ private HighBrightnessModeMetadata mHighBrightnessModeMetadata = null;
HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken,
String displayUniqueId, float brightnessMin, float brightnessMax,
HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg,
- Runnable hbmChangeCallback, Context context) {
+ Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) {
this(new Injector(), handler, width, height, displayToken, displayUniqueId, brightnessMin,
- brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, context);
+ brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, hbmMetadata, context);
}
@VisibleForTesting
HighBrightnessModeController(Injector injector, Handler handler, int width, int height,
IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax,
HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg,
- Runnable hbmChangeCallback, Context context) {
+ Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) {
mInjector = injector;
mContext = context;
mClock = injector.getClock();
@@ -137,6 +131,7 @@
mBrightnessMin = brightnessMin;
mBrightnessMax = brightnessMax;
mHbmChangeCallback = hbmChangeCallback;
+ mHighBrightnessModeMetadata = hbmMetadata;
mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
mSettingsObserver = new SettingsObserver(mHandler);
mRecalcRunnable = this::recalculateTimeAllowance;
@@ -222,19 +217,22 @@
// If we are starting or ending a high brightness mode session, store the current
// session in mRunningStartTimeMillis, or the old one in mEvents.
- final boolean wasHbmDrainingAvailableTime = mRunningStartTimeMillis != -1;
+ final long runningStartTime = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
+ final boolean wasHbmDrainingAvailableTime = runningStartTime != -1;
final boolean shouldHbmDrainAvailableTime = mBrightness > mHbmData.transitionPoint
&& !mIsHdrLayerPresent;
if (wasHbmDrainingAvailableTime != shouldHbmDrainAvailableTime) {
final long currentTime = mClock.uptimeMillis();
if (shouldHbmDrainAvailableTime) {
- mRunningStartTimeMillis = currentTime;
+ mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime);
} else {
- mEvents.addFirst(new HbmEvent(mRunningStartTimeMillis, currentTime));
- mRunningStartTimeMillis = -1;
+ final HbmEvent hbmEvent = new HbmEvent(runningStartTime, currentTime);
+ mHighBrightnessModeMetadata.addHbmEvent(hbmEvent);
+ mHighBrightnessModeMetadata.setRunningStartTimeMillis(-1);
if (DEBUG) {
- Slog.d(TAG, "New HBM event: " + mEvents.getFirst());
+ Slog.d(TAG, "New HBM event: "
+ + mHighBrightnessModeMetadata.getHbmEventQueue().peekFirst());
}
}
}
@@ -260,6 +258,10 @@
mSettingsObserver.stopObserving();
}
+ void setHighBrightnessModeMetadata(HighBrightnessModeMetadata hbmInfo) {
+ mHighBrightnessModeMetadata = hbmInfo;
+ }
+
void resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId,
HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg) {
mWidth = width;
@@ -316,20 +318,22 @@
pw.println(" mBrightnessMax=" + mBrightnessMax);
pw.println(" remainingTime=" + calculateRemainingTime(mClock.uptimeMillis()));
pw.println(" mIsTimeAvailable= " + mIsTimeAvailable);
- pw.println(" mRunningStartTimeMillis=" + TimeUtils.formatUptime(mRunningStartTimeMillis));
+ pw.println(" mRunningStartTimeMillis="
+ + TimeUtils.formatUptime(mHighBrightnessModeMetadata.getRunningStartTimeMillis()));
pw.println(" mIsThermalStatusWithinLimit=" + mIsThermalStatusWithinLimit);
pw.println(" mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode);
pw.println(" width*height=" + mWidth + "*" + mHeight);
pw.println(" mEvents=");
final long currentTime = mClock.uptimeMillis();
long lastStartTime = currentTime;
- if (mRunningStartTimeMillis != -1) {
- lastStartTime = dumpHbmEvent(pw, new HbmEvent(mRunningStartTimeMillis, currentTime));
+ long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
+ if (runningStartTimeMillis != -1) {
+ lastStartTime = dumpHbmEvent(pw, new HbmEvent(runningStartTimeMillis, currentTime));
}
- for (HbmEvent event : mEvents) {
- if (lastStartTime > event.endTimeMillis) {
+ for (HbmEvent event : mHighBrightnessModeMetadata.getHbmEventQueue()) {
+ if (lastStartTime > event.getEndTimeMillis()) {
pw.println(" event: [normal brightness]: "
- + TimeUtils.formatDuration(lastStartTime - event.endTimeMillis));
+ + TimeUtils.formatDuration(lastStartTime - event.getEndTimeMillis()));
}
lastStartTime = dumpHbmEvent(pw, event);
}
@@ -338,12 +342,12 @@
}
private long dumpHbmEvent(PrintWriter pw, HbmEvent event) {
- final long duration = event.endTimeMillis - event.startTimeMillis;
+ final long duration = event.getEndTimeMillis() - event.getStartTimeMillis();
pw.println(" event: ["
- + TimeUtils.formatUptime(event.startTimeMillis) + ", "
- + TimeUtils.formatUptime(event.endTimeMillis) + "] ("
+ + TimeUtils.formatUptime(event.getStartTimeMillis()) + ", "
+ + TimeUtils.formatUptime(event.getEndTimeMillis()) + "] ("
+ TimeUtils.formatDuration(duration) + ")");
- return event.startTimeMillis;
+ return event.getStartTimeMillis();
}
private boolean isCurrentlyAllowed() {
@@ -372,13 +376,15 @@
// First, lets see how much time we've taken for any currently running
// session of HBM.
- if (mRunningStartTimeMillis > 0) {
- if (mRunningStartTimeMillis > currentTime) {
+ long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
+ if (runningStartTimeMillis > 0) {
+ if (runningStartTimeMillis > currentTime) {
Slog.e(TAG, "Start time set to the future. curr: " + currentTime
- + ", start: " + mRunningStartTimeMillis);
- mRunningStartTimeMillis = currentTime;
+ + ", start: " + runningStartTimeMillis);
+ mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime);
+ runningStartTimeMillis = currentTime;
}
- timeAlreadyUsed = currentTime - mRunningStartTimeMillis;
+ timeAlreadyUsed = currentTime - runningStartTimeMillis;
}
if (DEBUG) {
@@ -387,18 +393,19 @@
// Next, lets iterate through the history of previous sessions and add those times.
final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis;
- Iterator<HbmEvent> it = mEvents.iterator();
+ Iterator<HbmEvent> it = mHighBrightnessModeMetadata.getHbmEventQueue().iterator();
while (it.hasNext()) {
final HbmEvent event = it.next();
// If this event ended before the current Timing window, discard forever and ever.
- if (event.endTimeMillis < windowstartTimeMillis) {
+ if (event.getEndTimeMillis() < windowstartTimeMillis) {
it.remove();
continue;
}
- final long startTimeMillis = Math.max(event.startTimeMillis, windowstartTimeMillis);
- timeAlreadyUsed += event.endTimeMillis - startTimeMillis;
+ final long startTimeMillis = Math.max(event.getStartTimeMillis(),
+ windowstartTimeMillis);
+ timeAlreadyUsed += event.getEndTimeMillis() - startTimeMillis;
}
if (DEBUG) {
@@ -425,17 +432,18 @@
// Calculate the time at which we want to recalculate mIsTimeAvailable in case a lux or
// brightness change doesn't happen before then.
long nextTimeout = -1;
+ final ArrayDeque<HbmEvent> hbmEvents = mHighBrightnessModeMetadata.getHbmEventQueue();
if (mBrightness > mHbmData.transitionPoint) {
// if we're in high-lux now, timeout when we run out of allowed time.
nextTimeout = currentTime + remainingTime;
- } else if (!mIsTimeAvailable && mEvents.size() > 0) {
+ } else if (!mIsTimeAvailable && hbmEvents.size() > 0) {
// If we are not allowed...timeout when the oldest event moved outside of the timing
// window by at least minTime. Basically, we're calculating the soonest time we can
// get {@code timeMinMillis} back to us.
final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis;
- final HbmEvent lastEvent = mEvents.getLast();
+ final HbmEvent lastEvent = hbmEvents.peekLast();
final long startTimePlusMinMillis =
- Math.max(windowstartTimeMillis, lastEvent.startTimeMillis)
+ Math.max(windowstartTimeMillis, lastEvent.getStartTimeMillis())
+ mHbmData.timeMinMillis;
final long timeWhenMinIsGainedBack =
currentTime + (startTimePlusMinMillis - windowstartTimeMillis) - remainingTime;
@@ -459,9 +467,10 @@
+ ", mUnthrottledBrightness: " + mUnthrottledBrightness
+ ", mThrottlingReason: "
+ BrightnessInfo.briMaxReasonToString(mThrottlingReason)
- + ", RunningStartTimeMillis: " + mRunningStartTimeMillis
+ + ", RunningStartTimeMillis: "
+ + mHighBrightnessModeMetadata.getRunningStartTimeMillis()
+ ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1)
- + ", events: " + mEvents);
+ + ", events: " + hbmEvents);
}
if (nextTimeout != -1) {
@@ -588,25 +597,6 @@
}
}
- /**
- * Represents an event in which High Brightness Mode was enabled.
- */
- private static class HbmEvent {
- public long startTimeMillis;
- public long endTimeMillis;
-
- HbmEvent(long startTimeMillis, long endTimeMillis) {
- this.startTimeMillis = startTimeMillis;
- this.endTimeMillis = endTimeMillis;
- }
-
- @Override
- public String toString() {
- return "[Event: {" + startTimeMillis + ", " + endTimeMillis + "}, total: "
- + ((endTimeMillis - startTimeMillis) / 1000) + "]";
- }
- }
-
@VisibleForTesting
class HdrListener extends SurfaceControlHdrLayerInfoListener {
@Override
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java b/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java
new file mode 100644
index 0000000..37234ff
--- /dev/null
+++ b/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import java.util.ArrayDeque;
+
+
+/**
+ * Represents High Brightness Mode metadata associated
+ * with a specific internal physical display.
+ * Required for separately storing data like time information,
+ * and related events when display was in HBM mode per
+ * physical internal display.
+ */
+class HighBrightnessModeMetadata {
+ /**
+ * Queue of previous HBM-events ordered from most recent to least recent.
+ * Meant to store only the events that fall into the most recent
+ * {@link HighBrightnessModeData#timeWindowMillis mHbmData.timeWindowMillis}.
+ */
+ private final ArrayDeque<HbmEvent> mEvents = new ArrayDeque<>();
+
+ /**
+ * If HBM is currently running, this is the start time for the current HBM session.
+ */
+ private long mRunningStartTimeMillis = -1;
+
+ public long getRunningStartTimeMillis() {
+ return mRunningStartTimeMillis;
+ }
+
+ public void setRunningStartTimeMillis(long setTime) {
+ mRunningStartTimeMillis = setTime;
+ }
+
+ public ArrayDeque<HbmEvent> getHbmEventQueue() {
+ return mEvents;
+ }
+
+ public void addHbmEvent(HbmEvent hbmEvent) {
+ mEvents.addFirst(hbmEvent);
+ }
+}
+
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index bf576b8..375e51c 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -372,12 +372,23 @@
void setDeviceStateLocked(int state, boolean isOverrideActive) {
Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState
+ ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted);
+ mPendingDeviceState = state;
+
+ if (!mBootCompleted) {
+ // The boot animation might still be in progress, we do not want to switch states now
+ // as the boot animation would end up with an incorrect size.
+ if (DEBUG) {
+ Slog.d(TAG, "Postponing transition to state: " + mPendingDeviceState
+ + " until boot is completed");
+ }
+ return;
+ }
+
// As part of a state transition, we may need to turn off some displays temporarily so that
// the transition is smooth. Plus, on some devices, only one internal displays can be
// on at a time. We use LogicalDisplay.setIsInTransition to mark a display that needs to be
// temporarily turned off.
resetLayoutLocked(mDeviceState, state, /* isStateChangeStarting= */ true);
- mPendingDeviceState = state;
final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState,
mInteractive, mBootCompleted);
final boolean sleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState, mDeviceState,
@@ -424,6 +435,9 @@
void onBootCompleted() {
synchronized (mSyncRoot) {
mBootCompleted = true;
+ if (mPendingDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) {
+ setDeviceStateLocked(mPendingDeviceState, /* isOverrideActive= */ false);
+ }
}
}
@@ -926,6 +940,15 @@
final int layerStack = assignLayerStackLocked(displayId);
final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
display.updateLocked(mDisplayDeviceRepo);
+
+ final DisplayInfo info = display.getDisplayInfoLocked();
+ if (info.type == Display.TYPE_INTERNAL && mDeviceStateToLayoutMap.size() > 1) {
+ // If this is an internal display and the device uses a display layout configuration,
+ // the display should be disabled as later we will receive a device state update, which
+ // will tell us which internal displays should be enabled and which should be disabled.
+ display.setEnabledLocked(false);
+ }
+
mLogicalDisplays.put(displayId, display);
return display;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4d44c886..6bc8582 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -239,7 +239,6 @@
import android.service.notification.NotificationRecordProto;
import android.service.notification.NotificationServiceDumpProto;
import android.service.notification.NotificationStats;
-import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeProto;
@@ -8526,95 +8525,6 @@
}
}
- static class NotificationRecordExtractorData {
- // Class that stores any field in a NotificationRecord that can change via an extractor.
- // Used to cache previous data used in a sort.
- int mPosition;
- int mVisibility;
- boolean mShowBadge;
- boolean mAllowBubble;
- boolean mIsBubble;
- NotificationChannel mChannel;
- String mGroupKey;
- ArrayList<String> mOverridePeople;
- ArrayList<SnoozeCriterion> mSnoozeCriteria;
- Integer mUserSentiment;
- Integer mSuppressVisually;
- ArrayList<Notification.Action> mSystemSmartActions;
- ArrayList<CharSequence> mSmartReplies;
- int mImportance;
-
- // These fields may not trigger a reranking but diffs here may be logged.
- float mRankingScore;
- boolean mIsConversation;
-
- NotificationRecordExtractorData(int position, int visibility, boolean showBadge,
- boolean allowBubble, boolean isBubble, NotificationChannel channel, String groupKey,
- ArrayList<String> overridePeople, ArrayList<SnoozeCriterion> snoozeCriteria,
- Integer userSentiment, Integer suppressVisually,
- ArrayList<Notification.Action> systemSmartActions,
- ArrayList<CharSequence> smartReplies, int importance, float rankingScore,
- boolean isConversation) {
- mPosition = position;
- mVisibility = visibility;
- mShowBadge = showBadge;
- mAllowBubble = allowBubble;
- mIsBubble = isBubble;
- mChannel = channel;
- mGroupKey = groupKey;
- mOverridePeople = overridePeople;
- mSnoozeCriteria = snoozeCriteria;
- mUserSentiment = userSentiment;
- mSuppressVisually = suppressVisually;
- mSystemSmartActions = systemSmartActions;
- mSmartReplies = smartReplies;
- mImportance = importance;
- mRankingScore = rankingScore;
- mIsConversation = isConversation;
- }
-
- // Returns whether the provided NotificationRecord differs from the cached data in any way.
- // Should be guarded by mNotificationLock; not annotated here as this class is static.
- boolean hasDiffForRankingLocked(NotificationRecord r, int newPosition) {
- return mPosition != newPosition
- || mVisibility != r.getPackageVisibilityOverride()
- || mShowBadge != r.canShowBadge()
- || mAllowBubble != r.canBubble()
- || mIsBubble != r.getNotification().isBubbleNotification()
- || !Objects.equals(mChannel, r.getChannel())
- || !Objects.equals(mGroupKey, r.getGroupKey())
- || !Objects.equals(mOverridePeople, r.getPeopleOverride())
- || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
- || !Objects.equals(mUserSentiment, r.getUserSentiment())
- || !Objects.equals(mSuppressVisually, r.getSuppressedVisualEffects())
- || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
- || !Objects.equals(mSmartReplies, r.getSmartReplies())
- || mImportance != r.getImportance();
- }
-
- // Returns whether the NotificationRecord has a change from this data for which we should
- // log an update. This method specifically targets fields that may be changed via
- // adjustments from the assistant.
- //
- // Fields here are the union of things in NotificationRecordLogger.shouldLogReported
- // and NotificationRecord.applyAdjustments.
- //
- // Should be guarded by mNotificationLock; not annotated here as this class is static.
- boolean hasDiffForLoggingLocked(NotificationRecord r, int newPosition) {
- return mPosition != newPosition
- || !Objects.equals(mChannel, r.getChannel())
- || !Objects.equals(mGroupKey, r.getGroupKey())
- || !Objects.equals(mOverridePeople, r.getPeopleOverride())
- || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
- || !Objects.equals(mUserSentiment, r.getUserSentiment())
- || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
- || !Objects.equals(mSmartReplies, r.getSmartReplies())
- || mImportance != r.getImportance()
- || !r.rankingScoreMatches(mRankingScore)
- || mIsConversation != r.isConversation();
- }
- }
-
void handleRankingSort() {
if (mRankingHelper == null) return;
synchronized (mNotificationLock) {
@@ -8640,7 +8550,8 @@
r.getSmartReplies(),
r.getImportance(),
r.getRankingScore(),
- r.isConversation());
+ r.isConversation(),
+ r.getProposedImportance());
extractorDataBefore.put(r.getKey(), extractorData);
mRankingHelper.extractSignals(r);
}
@@ -9935,7 +9846,8 @@
record.getRankingScore() == 0
? RANKING_UNCHANGED
: (record.getRankingScore() > 0 ? RANKING_PROMOTED : RANKING_DEMOTED),
- record.getNotification().isBubbleNotification()
+ record.getNotification().isBubbleNotification(),
+ record.getProposedImportance()
);
rankings.add(ranking);
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index cbaf485..d344306 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -210,6 +210,7 @@
// Whether this notification record should have an update logged the next time notifications
// are sorted.
private boolean mPendingLogUpdate = false;
+ private int mProposedImportance = IMPORTANCE_UNSPECIFIED;
public NotificationRecord(Context context, StatusBarNotification sbn,
NotificationChannel channel) {
@@ -499,6 +500,8 @@
pw.println(prefix + "mImportance="
+ NotificationListenerService.Ranking.importanceToString(mImportance));
pw.println(prefix + "mImportanceExplanation=" + getImportanceExplanation());
+ pw.println(prefix + "mProposedImportance="
+ + NotificationListenerService.Ranking.importanceToString(mProposedImportance));
pw.println(prefix + "mIsAppImportanceLocked=" + mIsAppImportanceLocked);
pw.println(prefix + "mIntercept=" + mIntercept);
pw.println(prefix + "mHidden==" + mHidden);
@@ -738,6 +741,12 @@
Adjustment.KEY_NOT_CONVERSATION,
Boolean.toString(mIsNotConversationOverride));
}
+ if (signals.containsKey(Adjustment.KEY_IMPORTANCE_PROPOSAL)) {
+ mProposedImportance = signals.getInt(Adjustment.KEY_IMPORTANCE_PROPOSAL);
+ EventLogTags.writeNotificationAdjusted(getKey(),
+ Adjustment.KEY_IMPORTANCE_PROPOSAL,
+ Integer.toString(mProposedImportance));
+ }
if (!signals.isEmpty() && adjustment.getIssuer() != null) {
mAdjustmentIssuer = adjustment.getIssuer();
}
@@ -870,6 +879,10 @@
return stats.naturalImportance;
}
+ public int getProposedImportance() {
+ return mProposedImportance;
+ }
+
public float getRankingScore() {
return mRankingScore;
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java b/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java
new file mode 100644
index 0000000..6dc9029
--- /dev/null
+++ b/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.notification;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.service.notification.SnoozeCriterion;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * Class that stores any field in a NotificationRecord that can change via an extractor.
+ * Used to cache previous data used in a sort.
+ */
+public final class NotificationRecordExtractorData {
+ private final int mPosition;
+ private final int mVisibility;
+ private final boolean mShowBadge;
+ private final boolean mAllowBubble;
+ private final boolean mIsBubble;
+ private final NotificationChannel mChannel;
+ private final String mGroupKey;
+ private final ArrayList<String> mOverridePeople;
+ private final ArrayList<SnoozeCriterion> mSnoozeCriteria;
+ private final Integer mUserSentiment;
+ private final Integer mSuppressVisually;
+ private final ArrayList<Notification.Action> mSystemSmartActions;
+ private final ArrayList<CharSequence> mSmartReplies;
+ private final int mImportance;
+
+ // These fields may not trigger a reranking but diffs here may be logged.
+ private final float mRankingScore;
+ private final boolean mIsConversation;
+ private final int mProposedImportance;
+
+ NotificationRecordExtractorData(int position, int visibility, boolean showBadge,
+ boolean allowBubble, boolean isBubble, NotificationChannel channel, String groupKey,
+ ArrayList<String> overridePeople, ArrayList<SnoozeCriterion> snoozeCriteria,
+ Integer userSentiment, Integer suppressVisually,
+ ArrayList<Notification.Action> systemSmartActions,
+ ArrayList<CharSequence> smartReplies, int importance, float rankingScore,
+ boolean isConversation, int proposedImportance) {
+ mPosition = position;
+ mVisibility = visibility;
+ mShowBadge = showBadge;
+ mAllowBubble = allowBubble;
+ mIsBubble = isBubble;
+ mChannel = channel;
+ mGroupKey = groupKey;
+ mOverridePeople = overridePeople;
+ mSnoozeCriteria = snoozeCriteria;
+ mUserSentiment = userSentiment;
+ mSuppressVisually = suppressVisually;
+ mSystemSmartActions = systemSmartActions;
+ mSmartReplies = smartReplies;
+ mImportance = importance;
+ mRankingScore = rankingScore;
+ mIsConversation = isConversation;
+ mProposedImportance = proposedImportance;
+ }
+
+ // Returns whether the provided NotificationRecord differs from the cached data in any way.
+ // Should be guarded by mNotificationLock; not annotated here as this class is static.
+ boolean hasDiffForRankingLocked(NotificationRecord r, int newPosition) {
+ return mPosition != newPosition
+ || mVisibility != r.getPackageVisibilityOverride()
+ || mShowBadge != r.canShowBadge()
+ || mAllowBubble != r.canBubble()
+ || mIsBubble != r.getNotification().isBubbleNotification()
+ || !Objects.equals(mChannel, r.getChannel())
+ || !Objects.equals(mGroupKey, r.getGroupKey())
+ || !Objects.equals(mOverridePeople, r.getPeopleOverride())
+ || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
+ || !Objects.equals(mUserSentiment, r.getUserSentiment())
+ || !Objects.equals(mSuppressVisually, r.getSuppressedVisualEffects())
+ || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
+ || !Objects.equals(mSmartReplies, r.getSmartReplies())
+ || mImportance != r.getImportance()
+ || mProposedImportance != r.getProposedImportance();
+ }
+
+ // Returns whether the NotificationRecord has a change from this data for which we should
+ // log an update. This method specifically targets fields that may be changed via
+ // adjustments from the assistant.
+ //
+ // Fields here are the union of things in NotificationRecordLogger.shouldLogReported
+ // and NotificationRecord.applyAdjustments.
+ //
+ // Should be guarded by mNotificationLock; not annotated here as this class is static.
+ boolean hasDiffForLoggingLocked(NotificationRecord r, int newPosition) {
+ return mPosition != newPosition
+ || !Objects.equals(mChannel, r.getChannel())
+ || !Objects.equals(mGroupKey, r.getGroupKey())
+ || !Objects.equals(mOverridePeople, r.getPeopleOverride())
+ || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
+ || !Objects.equals(mUserSentiment, r.getUserSentiment())
+ || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
+ || !Objects.equals(mSmartReplies, r.getSmartReplies())
+ || mImportance != r.getImportance()
+ || !r.rankingScoreMatches(mRankingScore)
+ || mIsConversation != r.isConversation()
+ || mProposedImportance != r.getProposedImportance();
+ }
+}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index d8aa469..cdcf6c4 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -1005,6 +1005,7 @@
channel.setAllowBubbles(existing != null
? existing.getAllowBubbles()
: NotificationChannel.DEFAULT_ALLOW_BUBBLE);
+ channel.setImportantConversation(false);
}
clearLockedFieldsLocked(channel);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 00fb065..866a995 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -91,6 +91,7 @@
import android.service.gatekeeper.IGateKeeperService;
import android.service.voice.VoiceInteractionManagerInternal;
import android.stats.devicepolicy.DevicePolicyEnums;
+import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -1760,6 +1761,63 @@
}
}
+ /**
+ * Returns whether switching users is currently allowed for the provided user.
+ * <p>
+ * Switching users is not allowed in the following cases:
+ * <li>the user is in a phone call</li>
+ * <li>{@link UserManager#DISALLOW_USER_SWITCH} is set</li>
+ * <li>system user hasn't been unlocked yet</li>
+ *
+ * @return A {@link UserManager.UserSwitchabilityResult} flag indicating if the user is
+ * switchable.
+ */
+ public @UserManager.UserSwitchabilityResult int getUserSwitchability(int userId) {
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserSwitchability");
+
+ final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
+ t.traceBegin("getUserSwitchability-" + userId);
+
+ int flags = UserManager.SWITCHABILITY_STATUS_OK;
+
+ t.traceBegin("TM.isInCall");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
+ if (telecomManager != null && telecomManager.isInCall()) {
+ flags |= UserManager.SWITCHABILITY_STATUS_USER_IN_CALL;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ t.traceEnd();
+
+ t.traceBegin("hasUserRestriction-DISALLOW_USER_SWITCH");
+ if (mLocalService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)) {
+ flags |= UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED;
+ }
+ t.traceEnd();
+
+ // System User is always unlocked in Headless System User Mode, so ignore this flag
+ if (!UserManager.isHeadlessSystemUserMode()) {
+ t.traceBegin("getInt-ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED");
+ final boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
+ t.traceEnd();
+ t.traceBegin("isUserUnlocked-USER_SYSTEM");
+ final boolean systemUserUnlocked = mLocalService.isUserUnlocked(UserHandle.USER_SYSTEM);
+ t.traceEnd();
+
+ if (!allowUserSwitchingWhenSystemUserLocked && !systemUserUnlocked) {
+ flags |= UserManager.SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED;
+ }
+ }
+ t.traceEnd();
+
+ return flags;
+ }
+
@Override
public boolean isUserSwitcherEnabled(@UserIdInt int mUserId) {
boolean multiUserSettingOn = Settings.Global.getInt(mContext.getContentResolver(),
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 554e269..20c9a21 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -4215,7 +4215,6 @@
}
boolean changed = false;
- Set<Permission> needsUpdate = null;
synchronized (mLock) {
final Iterator<Permission> it = mRegistry.getPermissionTrees().iterator();
while (it.hasNext()) {
@@ -4234,26 +4233,6 @@
+ " that used to be declared by " + bp.getPackageName());
it.remove();
}
- if (needsUpdate == null) {
- needsUpdate = new ArraySet<>();
- }
- needsUpdate.add(bp);
- }
- }
- if (needsUpdate != null) {
- for (final Permission bp : needsUpdate) {
- final AndroidPackage sourcePkg =
- mPackageManagerInt.getPackage(bp.getPackageName());
- final PackageStateInternal sourcePs =
- mPackageManagerInt.getPackageStateInternal(bp.getPackageName());
- synchronized (mLock) {
- if (sourcePkg != null && sourcePs != null) {
- continue;
- }
- Slog.w(TAG, "Removing dangling permission tree: " + bp.getName()
- + " from package " + bp.getPackageName());
- mRegistry.removePermission(bp.getName());
- }
}
}
return changed;
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index e80c260..0bfc48b 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -98,7 +98,7 @@
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
return mService.getRecentTasks().createRecentTaskInfo(task,
- false /* stripExtras */);
+ false /* stripExtras */, true /* getTasksAllowed */);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index abaa363..0ea6157 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -892,7 +892,7 @@
*
* TODO(b/213312721): Remove this predicate and its callers once ShellTransition is enabled.
*/
- private static boolean isTaskViewTask(WindowContainer wc) {
+ static boolean isTaskViewTask(WindowContainer wc) {
// We use Task#mRemoveWithTaskOrganizer to identify an embedded Task, but this is a hack and
// it is not guaranteed to work this logic in the future version.
return wc instanceof Task && ((Task) wc).mRemoveWithTaskOrganizer;
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index ba0413d..c6037da 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -203,8 +203,11 @@
|| !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
return;
}
- boolean cycleThroughStop = mWmService.mLetterboxConfiguration
- .isCameraCompatRefreshCycleThroughStopEnabled();
+ boolean cycleThroughStop =
+ mWmService.mLetterboxConfiguration
+ .isCameraCompatRefreshCycleThroughStopEnabled()
+ && !activity.mLetterboxUiController
+ .shouldRefreshActivityViaPauseForCameraCompat();
try {
activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true);
ProtoLog.v(WM_DEBUG_STATES,
@@ -255,7 +258,8 @@
Configuration lastReportedConfig) {
return newConfig.windowConfiguration.getDisplayRotation()
!= lastReportedConfig.windowConfiguration.getDisplayRotation()
- && isTreatmentEnabledForActivity(activity);
+ && isTreatmentEnabledForActivity(activity)
+ && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat();
}
/**
@@ -294,7 +298,8 @@
// handle dynamic changes so we shouldn't force rotate them.
&& activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR
&& activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED
- && mCameraIdPackageBiMap.containsPackageName(activity.packageName);
+ && mCameraIdPackageBiMap.containsPackageName(activity.packageName)
+ && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
}
private synchronized void notifyCameraOpened(
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 0c8a645..75ba214 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -17,12 +17,18 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.screenOrientationToString;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
@@ -132,6 +138,15 @@
@Nullable
private Letterbox mLetterbox;
+ @Nullable
+ private final Boolean mBooleanPropertyCameraCompatAllowForceRotation;
+
+ @Nullable
+ private final Boolean mBooleanPropertyCameraCompatAllowRefresh;
+
+ @Nullable
+ private final Boolean mBooleanPropertyCameraCompatEnableRefreshViaPause;
+
// Whether activity "refresh" was requested but not finished in
// ActivityRecord#activityResumedLocked following the camera compat force rotation in
// DisplayRotationCompatPolicy.
@@ -154,8 +169,33 @@
readComponentProperty(packageManager, mActivityRecord.packageName,
mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled,
PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION);
+ mBooleanPropertyCameraCompatAllowForceRotation =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+ /* checkDeviceConfig */ true),
+ PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION);
+ mBooleanPropertyCameraCompatAllowRefresh =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+ /* checkDeviceConfig */ true),
+ PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH);
+ mBooleanPropertyCameraCompatEnableRefreshViaPause =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+ /* checkDeviceConfig */ true),
+ PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
}
+ /**
+ * Reads a {@link Boolean} component property fot a given {@code packageName} and a {@code
+ * propertyName}. Returns {@code null} if {@code gatingCondition} is {@code false} or if the
+ * property isn't specified for the package.
+ *
+ * <p>Return value is {@link Boolean} rather than {@code boolean} so we can know when the
+ * property is unset. Particularly, when this returns {@code null}, {@link
+ * #shouldEnableWithOverrideAndProperty} will check the value of override for the final
+ * decision.
+ */
@Nullable
private static Boolean readComponentProperty(PackageManager packageManager, String packageName,
BooleanSupplier gatingCondition, String propertyName) {
@@ -210,15 +250,11 @@
* </ul>
*/
boolean shouldIgnoreRequestedOrientation(@ScreenOrientation int requestedOrientation) {
- if (!mLetterboxConfiguration.isPolicyForIgnoringRequestedOrientationEnabled()) {
- return false;
- }
- if (Boolean.FALSE.equals(mBooleanPropertyIgnoreRequestedOrientation)) {
- return false;
- }
- if (!Boolean.TRUE.equals(mBooleanPropertyIgnoreRequestedOrientation)
- && !mActivityRecord.info.isChangeEnabled(
- OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION)) {
+ if (!shouldEnableWithOverrideAndProperty(
+ /* gatingCondition */ mLetterboxConfiguration
+ ::isPolicyForIgnoringRequestedOrientationEnabled,
+ OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION,
+ mBooleanPropertyIgnoreRequestedOrientation)) {
return false;
}
if (mIsRelauchingAfterRequestedOrientationChanged) {
@@ -262,6 +298,109 @@
mIsRefreshAfterRotationRequested = isRequested;
}
+ /**
+ * Whether activity is eligible for activity "refresh" after camera compat force rotation
+ * treatment. See {@link DisplayRotationCompatPolicy} for context.
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Flag gating the camera compat treatment is enabled.
+ * <li>Activity isn't opted out by the device manufacturer with override or by the app
+ * developers with the component property.
+ * </ul>
+ */
+ boolean shouldRefreshActivityForCameraCompat() {
+ return shouldEnableWithOptOutOverrideAndProperty(
+ /* gatingCondition */ () -> mLetterboxConfiguration
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
+ OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH,
+ mBooleanPropertyCameraCompatAllowRefresh);
+ }
+
+ /**
+ * Whether activity should be "refreshed" after the camera compat force rotation treatment
+ * using the "resumed -> paused -> resumed" cycle rather than the "resumed -> ... -> stopped
+ * -> ... -> resumed" cycle. See {@link DisplayRotationCompatPolicy} for context.
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Flag gating the camera compat treatment is enabled.
+ * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle isn't disabled with the
+ * component property by the app developers.
+ * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle is enabled by the device
+ * manufacturer with override / by the app developers with the component property.
+ * </ul>
+ */
+ boolean shouldRefreshActivityViaPauseForCameraCompat() {
+ return shouldEnableWithOverrideAndProperty(
+ /* gatingCondition */ () -> mLetterboxConfiguration
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
+ OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE,
+ mBooleanPropertyCameraCompatEnableRefreshViaPause);
+ }
+
+ /**
+ * Whether activity is eligible for camera compat force rotation treatment. See {@link
+ * DisplayRotationCompatPolicy} for context.
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Flag gating the camera compat treatment is enabled.
+ * <li>Activity isn't opted out by the device manufacturer with override or by the app
+ * developers with the component property.
+ * </ul>
+ */
+ boolean shouldForceRotateForCameraCompat() {
+ return shouldEnableWithOptOutOverrideAndProperty(
+ /* gatingCondition */ () -> mLetterboxConfiguration
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
+ OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION,
+ mBooleanPropertyCameraCompatAllowForceRotation);
+ }
+
+ /**
+ * Returns {@code true} when the following conditions are met:
+ * <ul>
+ * <li>{@code gatingCondition} isn't {@code false}
+ * <li>OEM didn't opt out with a {@code overrideChangeId} override
+ * <li>App developers didn't opt out with a component {@code property}
+ * </ul>
+ *
+ * <p>This is used for the treatments that are enabled based with the heuristic but can be
+ * disabled on per-app basis by OEMs or app developers.
+ */
+ private boolean shouldEnableWithOptOutOverrideAndProperty(BooleanSupplier gatingCondition,
+ long overrideChangeId, Boolean property) {
+ if (!gatingCondition.getAsBoolean()) {
+ return false;
+ }
+ return !Boolean.FALSE.equals(property)
+ && !mActivityRecord.info.isChangeEnabled(overrideChangeId);
+ }
+
+ /**
+ * Returns {@code true} when the following conditions are met:
+ * <ul>
+ * <li>{@code gatingCondition} isn't {@code false}
+ * <li>App developers didn't opt out with a component {@code property}
+ * <li>App developers opted in with a component {@code property} or an OEM opted in with a
+ * component {@code property}
+ * </ul>
+ *
+ * <p>This is used for the treatments that are enabled only on per-app basis.
+ */
+ private boolean shouldEnableWithOverrideAndProperty(BooleanSupplier gatingCondition,
+ long overrideChangeId, Boolean property) {
+ if (!gatingCondition.getAsBoolean()) {
+ return false;
+ }
+ if (Boolean.FALSE.equals(property)) {
+ return false;
+ }
+ return Boolean.TRUE.equals(property)
+ || mActivityRecord.info.isChangeEnabled(overrideChangeId);
+ }
+
boolean hasWallpaperBackgroundForLetterbox() {
return mShowWallpaperForLetterboxBackground;
}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 4860762..1fc061b 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -976,7 +976,7 @@
continue;
}
- res.add(createRecentTaskInfo(task, true /* stripExtras */));
+ res.add(createRecentTaskInfo(task, true /* stripExtras */, getTasksAllowed));
}
return res;
}
@@ -1895,7 +1895,8 @@
/**
* Creates a new RecentTaskInfo from a Task.
*/
- ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras) {
+ ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras,
+ boolean getTasksAllowed) {
final ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
// If the recent Task is detached, we consider it will be re-attached to the default
// TaskDisplayArea because we currently only support recent overview in the default TDA.
@@ -1907,6 +1908,9 @@
rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID;
rti.persistentId = rti.taskId;
rti.lastSnapshotData.set(tr.mLastTaskSnapshotData);
+ if (!getTasksAllowed) {
+ Task.trimIneffectiveInfo(tr, rti);
+ }
// Fill in organized child task info for the task created by organizer.
if (tr.mCreatedByOrganizer) {
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 120fec0..0e60274 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -142,6 +142,10 @@
task.fillTaskInfo(rti, !mKeepIntentExtra);
// Fill in some deprecated values
rti.id = rti.taskId;
+
+ if (!mAllowed) {
+ Task.trimIneffectiveInfo(task, rti);
+ }
return rti;
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5806f79..a464112 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3467,6 +3467,54 @@
info.isSleeping = shouldSleepActivities();
}
+ /**
+ * Removes the activity info if the activity belongs to a different uid, which is
+ * different from the app that hosts the task.
+ */
+ static void trimIneffectiveInfo(Task task, TaskInfo info) {
+ final ActivityRecord baseActivity = task.getActivity(r -> !r.finishing,
+ false /* traverseTopToBottom */);
+ final int baseActivityUid =
+ baseActivity != null ? baseActivity.getUid() : task.effectiveUid;
+
+ if (info.topActivityInfo != null
+ && task.effectiveUid != info.topActivityInfo.applicationInfo.uid) {
+ // Making a copy to prevent eliminating the info in the original ActivityRecord.
+ info.topActivityInfo = new ActivityInfo(info.topActivityInfo);
+ info.topActivityInfo.applicationInfo =
+ new ApplicationInfo(info.topActivityInfo.applicationInfo);
+
+ // Strip the sensitive info.
+ info.topActivity = new ComponentName("", "");
+ info.topActivityInfo.packageName = "";
+ info.topActivityInfo.taskAffinity = "";
+ info.topActivityInfo.processName = "";
+ info.topActivityInfo.name = "";
+ info.topActivityInfo.parentActivityName = "";
+ info.topActivityInfo.targetActivity = "";
+ info.topActivityInfo.splitName = "";
+ info.topActivityInfo.applicationInfo.className = "";
+ info.topActivityInfo.applicationInfo.credentialProtectedDataDir = "";
+ info.topActivityInfo.applicationInfo.dataDir = "";
+ info.topActivityInfo.applicationInfo.deviceProtectedDataDir = "";
+ info.topActivityInfo.applicationInfo.manageSpaceActivityName = "";
+ info.topActivityInfo.applicationInfo.nativeLibraryDir = "";
+ info.topActivityInfo.applicationInfo.nativeLibraryRootDir = "";
+ info.topActivityInfo.applicationInfo.processName = "";
+ info.topActivityInfo.applicationInfo.publicSourceDir = "";
+ info.topActivityInfo.applicationInfo.scanPublicSourceDir = "";
+ info.topActivityInfo.applicationInfo.scanSourceDir = "";
+ info.topActivityInfo.applicationInfo.sourceDir = "";
+ info.topActivityInfo.applicationInfo.taskAffinity = "";
+ info.topActivityInfo.applicationInfo.name = "";
+ info.topActivityInfo.applicationInfo.packageName = "";
+ }
+
+ if (task.effectiveUid != baseActivityUid) {
+ info.baseActivity = new ComponentName("", "");
+ }
+ }
+
@Nullable PictureInPictureParams getPictureInPictureParams() {
final Task topTask = getTopMostTask();
if (topTask == null) return null;
@@ -6363,6 +6411,11 @@
return this;
}
+ Builder setRemoveWithTaskOrganizer(boolean removeWithTaskOrganizer) {
+ mRemoveWithTaskOrganizer = removeWithTaskOrganizer;
+ return this;
+ }
+
private Builder setUserId(int userId) {
mUserId = userId;
return this;
@@ -6560,7 +6613,7 @@
mCallingPackage = mActivityInfo.packageName;
mResizeMode = mActivityInfo.resizeMode;
mSupportsPictureInPicture = mActivityInfo.supportsPictureInPicture();
- if (mActivityOptions != null) {
+ if (!mRemoveWithTaskOrganizer && mActivityOptions != null) {
mRemoveWithTaskOrganizer = mActivityOptions.getRemoveWithTaskOranizer();
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 8ad76a3..79be946 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1074,10 +1074,14 @@
// Use launch-adjacent-flag-root if launching with launch-adjacent flag.
if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
&& mLaunchAdjacentFlagRootTask != null) {
- // If the adjacent launch is coming from the same root, launch to adjacent root instead.
- if (sourceTask != null && mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment() != null
+ if (sourceTask != null && sourceTask == candidateTask) {
+ // Do nothing when task that is getting opened is same as the source.
+ } else if (sourceTask != null
+ && mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment() != null
&& (sourceTask == mLaunchAdjacentFlagRootTask
|| sourceTask.isDescendantOf(mLaunchAdjacentFlagRootTask))) {
+ // If the adjacent launch is coming from the same root, launch to
+ // adjacent root instead.
return mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment().asTask();
} else {
return mLaunchAdjacentFlagRootTask;
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index d619547..d780cae 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -783,7 +783,8 @@
}
@Override
- public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie) {
+ public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie,
+ boolean removeWithTaskOrganizer) {
enforceTaskPermission("createRootTask()");
final long origId = Binder.clearCallingIdentity();
try {
@@ -795,7 +796,7 @@
return;
}
- createRootTask(display, windowingMode, launchCookie);
+ createRootTask(display, windowingMode, launchCookie, removeWithTaskOrganizer);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -804,6 +805,12 @@
@VisibleForTesting
Task createRootTask(DisplayContent display, int windowingMode, @Nullable IBinder launchCookie) {
+ return createRootTask(display, windowingMode, launchCookie,
+ false /* removeWithTaskOrganizer */);
+ }
+
+ Task createRootTask(DisplayContent display, int windowingMode, @Nullable IBinder launchCookie,
+ boolean removeWithTaskOrganizer) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Create root task displayId=%d winMode=%d",
display.mDisplayId, windowingMode);
// We want to defer the task appear signal until the task is fully created and attached to
@@ -816,6 +823,7 @@
.setDeferTaskAppear(true)
.setLaunchCookie(launchCookie)
.setParent(display.getDefaultTaskDisplayArea())
+ .setRemoveWithTaskOrganizer(removeWithTaskOrganizer)
.build();
task.setDeferTaskAppear(false /* deferTaskAppear */);
return task;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 1d25dbc..b2dab78b 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -371,7 +371,7 @@
boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) {
// Size of the display the wallpaper is rendered on.
- final Rect lastWallpaperBounds = wallpaperWin.getLastReportedBounds();
+ final Rect lastWallpaperBounds = wallpaperWin.getParentFrame();
// Full size of the wallpaper (usually larger than bounds above to parallax scroll when
// swiping through Launcher pages).
final Rect wallpaperFrame = wallpaperWin.getFrame();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index fb584fe..8bdab9c 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3197,11 +3197,11 @@
private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
boolean isVoiceInteraction) {
- if (isOrganized()
+ if (AppTransitionController.isTaskViewTask(this) || (isOrganized()
// TODO(b/161711458): Clean-up when moved to shell.
&& getWindowingMode() != WINDOWING_MODE_FULLSCREEN
&& getWindowingMode() != WINDOWING_MODE_FREEFORM
- && getWindowingMode() != WINDOWING_MODE_MULTI_WINDOW) {
+ && getWindowingMode() != WINDOWING_MODE_MULTI_WINDOW)) {
return null;
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3187337..38613a6 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1949,6 +1949,13 @@
creationParams.getPairedPrimaryFragmentToken());
final int pairedPosition = ownerTask.mChildren.indexOf(pairedPrimaryTaskFragment);
position = pairedPosition != -1 ? pairedPosition + 1 : POSITION_TOP;
+ } else if (creationParams.getPairedActivityToken() != null) {
+ // When there is a paired Activity, we want to place the new TaskFragment right above
+ // the paired Activity to make sure the Activity position is not changed after reparent.
+ final ActivityRecord pairedActivity = ActivityRecord.forTokenLocked(
+ creationParams.getPairedActivityToken());
+ final int pairedPosition = ownerTask.mChildren.indexOf(pairedActivity);
+ position = pairedPosition != -1 ? pairedPosition + 1 : POSITION_TOP;
} else {
position = POSITION_TOP;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0469961..63607ad 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3081,12 +3081,6 @@
return mLastReportedConfiguration.getMergedConfiguration();
}
- /** Returns the last window configuration bounds reported to the client. */
- Rect getLastReportedBounds() {
- final Rect bounds = getLastReportedConfiguration().windowConfiguration.getBounds();
- return !bounds.isEmpty() ? bounds : getBounds();
- }
-
void adjustStartingWindowFlags() {
if (mAttrs.type == TYPE_BASE_APPLICATION && mActivityRecord != null
&& mActivityRecord.mStartingWindow != null) {
@@ -4421,6 +4415,9 @@
pw.print("null");
}
+ if (mXOffset != 0 || mYOffset != 0) {
+ pw.println(prefix + "mXOffset=" + mXOffset + " mYOffset=" + mYOffset);
+ }
if (mHScale != 1 || mVScale != 1) {
pw.println(prefix + "mHScale=" + mHScale
+ " mVScale=" + mVScale);
@@ -5573,7 +5570,7 @@
mSurfacePosition);
if (mWallpaperScale != 1f) {
- final Rect bounds = getLastReportedBounds();
+ final Rect bounds = getParentFrame();
Matrix matrix = mTmpMatrix;
matrix.setTranslate(mXOffset, mYOffset);
matrix.postScale(mWallpaperScale, mWallpaperScale, bounds.exactCenterX(),
@@ -5686,6 +5683,14 @@
&& imeTarget.compareTo(this) <= 0;
return inTokenWithAndAboveImeTarget;
}
+
+ // The condition is for the system dialog not belonging to any Activity.
+ // (^FLAG_NOT_FOCUSABLE & FLAG_ALT_FOCUSABLE_IM) means the dialog is still focusable but
+ // should be placed above the IME window.
+ if ((mAttrs.flags & (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM))
+ == FLAG_ALT_FOCUSABLE_IM && isTrustedOverlay() && canAddInternalSystemWindow()) {
+ return true;
+ }
return false;
}
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index f628fba..abe48f8 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -464,6 +464,14 @@
</xs:complexType>
<xs:complexType name="refreshRateConfigs">
+ <xs:element name="defaultRefreshRate" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element name="defaultPeakRefreshRate" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
<xs:element name="lowerBlockingZoneConfigs" type="blockingZoneConfig"
minOccurs="0" maxOccurs="1">
<xs:annotation name="final"/>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index cb08179..2c97af5 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -186,8 +186,12 @@
public class RefreshRateConfigs {
ctor public RefreshRateConfigs();
+ method public final java.math.BigInteger getDefaultPeakRefreshRate();
+ method public final java.math.BigInteger getDefaultRefreshRate();
method public final com.android.server.display.config.BlockingZoneConfig getHigherBlockingZoneConfigs();
method public final com.android.server.display.config.BlockingZoneConfig getLowerBlockingZoneConfigs();
+ method public final void setDefaultPeakRefreshRate(java.math.BigInteger);
+ method public final void setDefaultRefreshRate(java.math.BigInteger);
method public final void setHigherBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
method public final void setLowerBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index dad9fe8..31599ee 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -74,7 +74,7 @@
mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem));
mSpySystemServer = spy(new NoOpSystemServerAdapter());
mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory,
- mSpySystemServer);
+ mSpySystemServer, mSpyAudioSystem);
mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker);
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 666d401..3c735e3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -41,7 +41,6 @@
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -55,7 +54,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.internal.R;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
@@ -369,274 +367,6 @@
verify(mCancellationSignal).cancel();
}
- @Test
- public void fingerprintPowerIgnoresAuthInWindow() throws Exception {
- when(mSensorProps.isAnySidefpsType()).thenReturn(true);
- when(mHal.authenticate(anyLong())).thenReturn(mCancellationSignal);
-
- final FingerprintAuthenticationClient client = createClient(1);
- client.start(mCallback);
- client.onPowerPressed();
- client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, 2 /* deviceId */),
- true /* authenticated */, new ArrayList<>());
- mLooper.moveTimeForward(1000);
- mLooper.dispatchAll();
-
- verify(mCallback).onClientFinished(any(), eq(false));
- verify(mCancellationSignal).cancel();
- }
-
- @Test
- public void fingerprintAuthIgnoredWaitingForPower() throws Exception {
- when(mSensorProps.isAnySidefpsType()).thenReturn(true);
- when(mHal.authenticate(anyLong())).thenReturn(mCancellationSignal);
-
- final FingerprintAuthenticationClient client = createClient(1);
- client.start(mCallback);
- client.onAuthenticated(new Fingerprint("friendly", 3 /* fingerId */, 4 /* deviceId */),
- true /* authenticated */, new ArrayList<>());
- client.onPowerPressed();
- mLooper.moveTimeForward(1000);
- mLooper.dispatchAll();
-
- verify(mCallback).onClientFinished(any(), eq(false));
- verify(mCancellationSignal).cancel();
- }
-
- @Test
- public void fingerprintAuthFailsWhenAuthAfterPower() throws Exception {
- when(mSensorProps.isAnySidefpsType()).thenReturn(true);
- when(mHal.authenticate(anyLong())).thenReturn(mCancellationSignal);
-
- final FingerprintAuthenticationClient client = createClient(1);
- client.start(mCallback);
- client.onPowerPressed();
- mLooper.dispatchAll();
- mLooper.moveTimeForward(1000);
- mLooper.dispatchAll();
- client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
- true /* authenticated */, new ArrayList<>());
- mLooper.dispatchAll();
- mLooper.moveTimeForward(1000);
- mLooper.dispatchAll();
-
- verify(mCallback, never()).onClientFinished(any(), eq(true));
- verify(mCallback).onClientFinished(any(), eq(false));
- when(mHal.authenticateWithContext(anyLong(), any())).thenReturn(mCancellationSignal);
- }
-
- @Test
- public void sideFingerprintDoesntSendAuthImmediately() throws Exception {
- when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-
- final FingerprintAuthenticationClient client = createClient(1);
- client.start(mCallback);
- mLooper.dispatchAll();
- client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
- true /* authenticated */, new ArrayList<>());
- mLooper.dispatchAll();
-
- verify(mCallback, never()).onClientFinished(any(), anyBoolean());
- }
-
- @Test
- public void sideFingerprintSkipsWindowIfFingerUp() throws Exception {
- when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-
- mContext.getOrCreateTestableResources().addOverride(
- R.integer.config_sidefpsSkipWaitForPowerAcquireMessage, FINGER_UP);
-
- final FingerprintAuthenticationClient client = createClient(1);
- client.start(mCallback);
- mLooper.dispatchAll();
- client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
- true /* authenticated */, new ArrayList<>());
- client.onAcquired(FINGER_UP, 0);
- mLooper.dispatchAll();
-
- verify(mCallback).onClientFinished(any(), eq(true));
- }
-
- @Test
- public void sideFingerprintSkipsWindowIfVendorMessageMatch() throws Exception {
- when(mSensorProps.isAnySidefpsType()).thenReturn(true);
- final int vendorAcquireMessage = 1234;
-
- mContext.getOrCreateTestableResources().addOverride(
- R.integer.config_sidefpsSkipWaitForPowerAcquireMessage,
- FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR);
- mContext.getOrCreateTestableResources().addOverride(
- R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage,
- vendorAcquireMessage);
-
- final FingerprintAuthenticationClient client = createClient(1);
- client.start(mCallback);
- mLooper.dispatchAll();
- client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
- true /* authenticated */, new ArrayList<>());
- client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, vendorAcquireMessage);
- mLooper.dispatchAll();
-
- verify(mCallback).onClientFinished(any(), eq(true));
- }
-
- @Test
- public void sideFingerprintDoesNotSkipWindowOnVendorErrorMismatch() throws Exception {
- when(mSensorProps.isAnySidefpsType()).thenReturn(true);
- final int vendorAcquireMessage = 1234;
-
- mContext.getOrCreateTestableResources().addOverride(
- R.integer.config_sidefpsSkipWaitForPowerAcquireMessage,
- FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR);
- mContext.getOrCreateTestableResources().addOverride(
- R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage,
- vendorAcquireMessage);
-
- final FingerprintAuthenticationClient client = createClient(1);
- client.start(mCallback);
- mLooper.dispatchAll();
- client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
- true /* authenticated */, new ArrayList<>());
- client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 1);
- mLooper.dispatchAll();
-
- verify(mCallback, never()).onClientFinished(any(), anyBoolean());
- }
-
- @Test
- public void sideFingerprintSendsAuthIfFingerUp() throws Exception {
- when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-
- mContext.getOrCreateTestableResources().addOverride(
- R.integer.config_sidefpsSkipWaitForPowerAcquireMessage, FINGER_UP);
-
- final FingerprintAuthenticationClient client = createClient(1);
- client.start(mCallback);
- mLooper.dispatchAll();
- client.onAcquired(FINGER_UP, 0);
- client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
- true /* authenticated */, new ArrayList<>());
- mLooper.dispatchAll();
-
- verify(mCallback).onClientFinished(any(), eq(true));
- }
-
- @Test
- public void sideFingerprintShortCircuitExpires() throws Exception {
- when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-
- final int timeBeforeAuthSent = 500;
-
- mContext.getOrCreateTestableResources().addOverride(
- R.integer.config_sidefpsKeyguardPowerPressWindow, timeBeforeAuthSent);
- mContext.getOrCreateTestableResources().addOverride(
- R.integer.config_sidefpsSkipWaitForPowerAcquireMessage, FINGER_UP);
-
- final FingerprintAuthenticationClient client = createClient(1);
- client.start(mCallback);
- mLooper.dispatchAll();
- client.onAcquired(FINGER_UP, 0);
- mLooper.dispatchAll();
-
- mLooper.moveTimeForward(500);
- mLooper.dispatchAll();
- client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
- true /* authenticated */, new ArrayList<>());
- mLooper.dispatchAll();
- verify(mCallback, never()).onClientFinished(any(), anyBoolean());
-
- mLooper.moveTimeForward(500);
- mLooper.dispatchAll();
- verify(mCallback).onClientFinished(any(), eq(true));
- }
-
- @Test
- public void sideFingerprintPowerWindowStartsOnAcquireStart() throws Exception {
- final int powerWindow = 500;
- final long authStart = 300;
-
- when(mSensorProps.isAnySidefpsType()).thenReturn(true);
- mContext.getOrCreateTestableResources().addOverride(
- R.integer.config_sidefpsBpPowerPressWindow, powerWindow);
-
- final FingerprintAuthenticationClient client = createClient(1);
- client.start(mCallback);
-
- // Acquire start occurs at time = 0ms
- when(mClock.millis()).thenReturn(0L);
- client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */);
-
- // Auth occurs at time = 300
- when(mClock.millis()).thenReturn(authStart);
- // At this point the delay should be 500 - (300 - 0) == 200 milliseconds.
- client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
- true /* authenticated */, new ArrayList<>());
- mLooper.dispatchAll();
- verify(mCallback, never()).onClientFinished(any(), anyBoolean());
-
- // After waiting 200 milliseconds, auth should succeed.
- mLooper.moveTimeForward(powerWindow - authStart);
- mLooper.dispatchAll();
- verify(mCallback).onClientFinished(any(), eq(true));
- }
-
- @Test
- public void sideFingerprintPowerWindowStartsOnLastAcquireStart() throws Exception {
- final int powerWindow = 500;
-
- when(mSensorProps.isAnySidefpsType()).thenReturn(true);
- mContext.getOrCreateTestableResources().addOverride(
- R.integer.config_sidefpsBpPowerPressWindow, powerWindow);
-
- final FingerprintAuthenticationClient client = createClient(1);
- client.start(mCallback);
- // Acquire start occurs at time = 0ms
- when(mClock.millis()).thenReturn(0L);
- client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */);
-
- // Auth reject occurs at time = 300ms
- when(mClock.millis()).thenReturn(300L);
- client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
- false /* authenticated */, new ArrayList<>());
- mLooper.dispatchAll();
-
- mLooper.moveTimeForward(300);
- mLooper.dispatchAll();
- verify(mCallback, never()).onClientFinished(any(), anyBoolean());
-
- when(mClock.millis()).thenReturn(1300L);
- client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */);
-
- // If code is correct, the new acquired start timestamp should be used
- // and the code should only have to wait 500 - (1500-1300)ms.
- when(mClock.millis()).thenReturn(1500L);
- client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
- true /* authenticated */, new ArrayList<>());
- mLooper.dispatchAll();
-
- mLooper.moveTimeForward(299);
- mLooper.dispatchAll();
- verify(mCallback, never()).onClientFinished(any(), anyBoolean());
-
- mLooper.moveTimeForward(1);
- mLooper.dispatchAll();
- verify(mCallback).onClientFinished(any(), eq(true));
- }
-
- @Test
- public void sideFpsPowerPressCancelsIsntantly() throws Exception {
- when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-
- final FingerprintAuthenticationClient client = createClient(1);
- client.start(mCallback);
-
- client.onPowerPressed();
- mLooper.dispatchAll();
-
- verify(mCallback, never()).onClientFinished(any(), eq(true));
- verify(mCallback).onClientFinished(any(), eq(false));
- }
-
private FingerprintAuthenticationClient createClient() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 86c5937..77e5d1d 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -51,6 +51,8 @@
public final class DisplayDeviceConfigTest {
private static final int DEFAULT_PEAK_REFRESH_RATE = 75;
private static final int DEFAULT_REFRESH_RATE = 120;
+ private static final int DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE = 55;
+ private static final int DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE = 95;
private static final int[] LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{10, 30};
private static final int[] LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{1, 21};
private static final int[] HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{160};
@@ -150,8 +152,10 @@
assertEquals("ProximitySensor123", mDisplayDeviceConfig.getProximitySensor().name);
assertEquals("prox_type_1", mDisplayDeviceConfig.getProximitySensor().type);
- assertEquals(75, mDisplayDeviceConfig.getDefaultLowRefreshRate());
- assertEquals(90, mDisplayDeviceConfig.getDefaultHighRefreshRate());
+ assertEquals(75, mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate());
+ assertEquals(90, mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate());
+ assertEquals(85, mDisplayDeviceConfig.getDefaultPeakRefreshRate());
+ assertEquals(45, mDisplayDeviceConfig.getDefaultRefreshRate());
assertArrayEquals(new int[]{45, 55},
mDisplayDeviceConfig.getLowDisplayBrightnessThresholds());
assertArrayEquals(new int[]{50, 60},
@@ -230,8 +234,12 @@
mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
assertArrayEquals(new float[]{29, 30, 31},
mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
- assertEquals(mDisplayDeviceConfig.getDefaultLowRefreshRate(), DEFAULT_REFRESH_RATE);
- assertEquals(mDisplayDeviceConfig.getDefaultHighRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(),
+ DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(),
+ DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE);
assertArrayEquals(mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(),
LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
assertArrayEquals(mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(),
@@ -449,6 +457,8 @@
+ "<type>prox_type_1</type>\n"
+ "</proxSensor>\n"
+ "<refreshRate>\n"
+ + "<defaultRefreshRate>45</defaultRefreshRate>\n"
+ + "<defaultPeakRefreshRate>85</defaultPeakRefreshRate>\n"
+ "<lowerBlockingZoneConfigs>\n"
+ "<defaultRefreshRate>75</defaultRefreshRate>\n"
+ "<blockingZoneThreshold>\n"
@@ -550,10 +560,14 @@
.thenReturn(new int[]{370, 380, 390});
// Configs related to refresh rates and blocking zones
- when(mResources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
+ when(mResources.getInteger(R.integer.config_defaultPeakRefreshRate))
.thenReturn(DEFAULT_PEAK_REFRESH_RATE);
- when(mResources.getInteger(com.android.internal.R.integer.config_defaultRefreshRate))
+ when(mResources.getInteger(R.integer.config_defaultRefreshRate))
.thenReturn(DEFAULT_REFRESH_RATE);
+ when(mResources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+ .thenReturn(DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
+ when(mResources.getInteger(R.integer.config_defaultRefreshRateInZone))
+ .thenReturn(DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
.thenReturn(LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index b133a2a..af39dd4 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -1869,6 +1869,10 @@
.thenReturn(75);
when(resources.getInteger(R.integer.config_defaultRefreshRate))
.thenReturn(45);
+ when(resources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+ .thenReturn(65);
+ when(resources.getInteger(R.integer.config_defaultRefreshRateInZone))
+ .thenReturn(85);
when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
.thenReturn(new int[]{5});
when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
@@ -1888,6 +1892,8 @@
assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 45, 0.0);
assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 75,
0.0);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 65);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 85);
assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
new int[]{250});
assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
@@ -1899,17 +1905,21 @@
// Notify that the default display is updated, such that DisplayDeviceConfig has new values
DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
- when(displayDeviceConfig.getDefaultLowRefreshRate()).thenReturn(50);
- when(displayDeviceConfig.getDefaultHighRefreshRate()).thenReturn(55);
+ when(displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50);
+ when(displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55);
+ when(displayDeviceConfig.getDefaultRefreshRate()).thenReturn(60);
+ when(displayDeviceConfig.getDefaultPeakRefreshRate()).thenReturn(65);
when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
director.defaultDisplayDeviceUpdated(displayDeviceConfig);
- assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0);
- assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 55,
+ assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
+ assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65,
0.0);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 55);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 50);
assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
new int[]{210});
assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
@@ -1922,6 +1932,8 @@
// Notify that the default display is updated, such that DeviceConfig has new values
FakeDeviceConfig config = mInjector.getDeviceConfig();
config.setDefaultPeakRefreshRate(60);
+ config.setRefreshRateInHighZone(65);
+ config.setRefreshRateInLowZone(70);
config.setLowAmbientBrightnessThresholds(new int[]{20});
config.setLowDisplayBrightnessThresholds(new int[]{10});
config.setHighDisplayBrightnessThresholds(new int[]{255});
@@ -1929,9 +1941,11 @@
director.defaultDisplayDeviceUpdated(displayDeviceConfig);
- assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0);
+ assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 60,
0.0);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 65);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 70);
assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
new int[]{255});
assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
@@ -1971,8 +1985,8 @@
any(Handler.class));
DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class);
- when(ddcMock.getDefaultLowRefreshRate()).thenReturn(50);
- when(ddcMock.getDefaultHighRefreshRate()).thenReturn(55);
+ when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50);
+ when(ddcMock.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55);
when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
diff --git a/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java b/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java
new file mode 100644
index 0000000..24fc348
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertEquals;
+
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class HbmEventTest {
+ private long mStartTimeMillis;
+ private long mEndTimeMillis;
+ private HbmEvent mHbmEvent;
+
+ @Before
+ public void setUp() {
+ mStartTimeMillis = 10;
+ mEndTimeMillis = 20;
+ mHbmEvent = new HbmEvent(mStartTimeMillis, mEndTimeMillis);
+ }
+
+ @Test
+ public void getCorrectValues() {
+ assertEquals(mHbmEvent.getStartTimeMillis(), mStartTimeMillis);
+ assertEquals(mHbmEvent.getEndTimeMillis(), mEndTimeMillis);
+ }
+
+ @Test
+ public void toStringGeneratesExpectedString() {
+ String actualString = mHbmEvent.toString();
+ String expectedString = "HbmEvent: {startTimeMillis:" + mStartTimeMillis
+ + ", endTimeMillis: " + mEndTimeMillis + "}, total: "
+ + ((mEndTimeMillis - mStartTimeMillis) / 1000) + "]";
+ assertEquals(actualString, expectedString);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index 53fa3e2..da2e1be 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -27,9 +27,7 @@
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
import static com.android.server.display.AutomaticBrightnessController
.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
-
import static com.android.server.display.DisplayDeviceConfig.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
-
import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
import static org.junit.Assert.assertEquals;
@@ -102,6 +100,7 @@
private Binder mDisplayToken;
private String mDisplayUniqueId;
private Context mContextSpy;
+ private HighBrightnessModeMetadata mHighBrightnessModeMetadata;
@Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@@ -124,6 +123,7 @@
mTestLooper = new TestLooper(mClock::now);
mDisplayToken = null;
mDisplayUniqueId = "unique_id";
+
mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
when(mContextSpy.getContentResolver()).thenReturn(resolver);
@@ -140,7 +140,8 @@
initHandler(null);
final HighBrightnessModeController hbmc = new HighBrightnessModeController(
mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
- mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, mContextSpy);
+ mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {},
+ null, mContextSpy);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
}
@@ -150,7 +151,8 @@
initHandler(null);
final HighBrightnessModeController hbmc = new HighBrightnessModeController(
mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
- mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, mContextSpy);
+ mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {},
+ null, mContextSpy);
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
@@ -705,9 +707,12 @@
// Creates instance with standard initialization values.
private HighBrightnessModeController createDefaultHbm(OffsettableClock clock) {
initHandler(clock);
+ if (mHighBrightnessModeMetadata == null) {
+ mHighBrightnessModeMetadata = new HighBrightnessModeMetadata();
+ }
return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH,
DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX,
- DEFAULT_HBM_DATA, null, () -> {}, mContextSpy);
+ DEFAULT_HBM_DATA, null, () -> {}, mHighBrightnessModeMetadata, mContextSpy);
}
private void initHandler(OffsettableClock clock) {
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java
new file mode 100644
index 0000000..ede54e0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class HighBrightnessModeMetadataTest {
+ private HighBrightnessModeMetadata mHighBrightnessModeMetadata;
+
+ private long mRunningStartTimeMillis = -1;
+
+ @Before
+ public void setUp() {
+ mHighBrightnessModeMetadata = new HighBrightnessModeMetadata();
+ }
+
+ @Test
+ public void checkDefaultValues() {
+ assertEquals(mHighBrightnessModeMetadata.getRunningStartTimeMillis(),
+ mRunningStartTimeMillis);
+ assertEquals(mHighBrightnessModeMetadata.getHbmEventQueue().size(), 0);
+ }
+
+ @Test
+ public void checkSetValues() {
+ mRunningStartTimeMillis = 10;
+ mHighBrightnessModeMetadata.setRunningStartTimeMillis(mRunningStartTimeMillis);
+ assertEquals(mHighBrightnessModeMetadata.getRunningStartTimeMillis(),
+ mRunningStartTimeMillis);
+ HbmEvent expectedHbmEvent = new HbmEvent(10, 20);
+ mHighBrightnessModeMetadata.addHbmEvent(expectedHbmEvent);
+ HbmEvent actualHbmEvent = mHighBrightnessModeMetadata.getHbmEventQueue().peekFirst();
+ assertEquals(expectedHbmEvent.toString(), actualHbmEvent.toString());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 638637d..6790ad9 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -18,6 +18,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.DEFAULT_DISPLAY_GROUP;
+import static android.view.Display.TYPE_EXTERNAL;
import static android.view.Display.TYPE_INTERNAL;
import static android.view.Display.TYPE_VIRTUAL;
@@ -173,7 +174,7 @@
@Test
public void testDisplayDeviceAddAndRemove_NonInternalTypes() {
- testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_EXTERNAL);
+ testDisplayDeviceAddAndRemove_NonInternal(TYPE_EXTERNAL);
testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_WIFI);
testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_OVERLAY);
testDisplayDeviceAddAndRemove_NonInternal(TYPE_VIRTUAL);
@@ -218,7 +219,7 @@
@Test
public void testDisplayDeviceAddAndRemove_OneExternalDefault() {
- DisplayDevice device = createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800,
+ DisplayDevice device = createDisplayDevice(TYPE_EXTERNAL, 600, 800,
FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
// add
@@ -268,7 +269,7 @@
public void testGetDisplayIdsLocked() {
add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0));
+ add(createDisplayDevice(TYPE_EXTERNAL, 600, 800, 0));
add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID,
@@ -460,7 +461,7 @@
Layout layout = new Layout();
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, true, true);
- layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, false, false);
+ layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, false, true);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
layout = new Layout();
@@ -469,6 +470,8 @@
when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout);
when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout);
+ when(mDeviceStateToLayoutMapSpy.size()).thenReturn(4);
+
LogicalDisplay display1 = add(device1);
assertEquals(info(display1).address, info(device1).address);
assertEquals(DEFAULT_DISPLAY, id(display1));
@@ -481,8 +484,15 @@
mLogicalDisplayMapper.setDeviceStateLocked(0, false);
mLooper.moveTimeForward(1000);
mLooper.dispatchAll();
+ // The new state is not applied until the boot is completed
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+
+ mLogicalDisplayMapper.onBootCompleted();
+ mLooper.moveTimeForward(1000);
+ mLooper.dispatchAll();
+ assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+ assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
@@ -623,6 +633,23 @@
assertEquals(3, threeDisplaysEnabled.length);
}
+ @Test
+ public void testCreateNewLogicalDisplay() {
+ DisplayDevice device1 = createDisplayDevice(TYPE_EXTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
+ LogicalDisplay display1 = add(device1);
+
+ assertTrue(display1.isEnabledLocked());
+
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ when(mDeviceStateToLayoutMapSpy.size()).thenReturn(2);
+ LogicalDisplay display2 = add(device2);
+
+ assertFalse(display2.isEnabledLocked());
+ }
+
/////////////////
// Helper Methods
/////////////////
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 66c3f07..b12e6ad 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -1678,7 +1678,7 @@
mParentNotificationChannel.getImportance(),
null, null,
mParentNotificationChannel, null, null, true, 0, false, -1, false, null, null,
- false, false, false, null, 0, false);
+ false, false, false, null, 0, false, 0);
return true;
}).when(mRankingMap).getRanking(eq(GENERIC_KEY),
any(NotificationListenerService.Ranking.class));
@@ -1704,7 +1704,7 @@
mNotificationChannel.getImportance(),
null, null,
mNotificationChannel, null, null, true, 0, false, -1, false, null, null, false,
- false, false, null, 0, false);
+ false, false, null, 0, false, 0);
return true;
}).when(mRankingMap).getRanking(eq(CUSTOM_KEY),
any(NotificationListenerService.Ranking.class));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 12cd834..8a99c2c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -193,7 +193,8 @@
tweak.isConversation(),
tweak.getConversationShortcutInfo(),
tweak.getRankingAdjustment(),
- tweak.isBubble()
+ tweak.isBubble(),
+ tweak.getProposedImportance()
);
assertNotEquals(nru, nru2);
}
@@ -274,7 +275,8 @@
isConversation(i),
getShortcutInfo(i),
getRankingAdjustment(i),
- isBubble(i)
+ isBubble(i),
+ getProposedImportance(i)
);
rankings[i] = ranking;
}
@@ -402,6 +404,10 @@
return index % 3 - 1;
}
+ private int getProposedImportance(int index) {
+ return index % 5 - 1;
+ }
+
private boolean isBubble(int index) {
return index % 4 == 0;
}
@@ -443,6 +449,7 @@
assertEquals(comment, a.getConversationShortcutInfo().getId(),
b.getConversationShortcutInfo().getId());
assertActionsEqual(a.getSmartActions(), b.getSmartActions());
+ assertEquals(a.getProposedImportance(), b.getProposedImportance());
}
private void detailedAssertEquals(RankingMap a, RankingMap b) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
new file mode 100644
index 0000000..87e86cb
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.service.notification.Adjustment;
+import android.service.notification.SnoozeCriterion;
+import android.service.notification.StatusBarNotification;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+public class NotificationRecordExtractorDataTest extends UiServiceTestCase {
+
+ @Test
+ public void testHasDiffs_noDiffs() {
+ NotificationRecord r = generateRecord();
+
+ NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData(
+ 1,
+ r.getPackageVisibilityOverride(),
+ r.canShowBadge(),
+ r.canBubble(),
+ r.getNotification().isBubbleNotification(),
+ r.getChannel(),
+ r.getGroupKey(),
+ r.getPeopleOverride(),
+ r.getSnoozeCriteria(),
+ r.getUserSentiment(),
+ r.getSuppressedVisualEffects(),
+ r.getSystemGeneratedSmartActions(),
+ r.getSmartReplies(),
+ r.getImportance(),
+ r.getRankingScore(),
+ r.isConversation(),
+ r.getProposedImportance());
+
+ assertFalse(extractorData.hasDiffForRankingLocked(r, 1));
+ assertFalse(extractorData.hasDiffForLoggingLocked(r, 1));
+ }
+
+ @Test
+ public void testHasDiffs_proposedImportanceChange() {
+ NotificationRecord r = generateRecord();
+
+ NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData(
+ 1,
+ r.getPackageVisibilityOverride(),
+ r.canShowBadge(),
+ r.canBubble(),
+ r.getNotification().isBubbleNotification(),
+ r.getChannel(),
+ r.getGroupKey(),
+ r.getPeopleOverride(),
+ r.getSnoozeCriteria(),
+ r.getUserSentiment(),
+ r.getSuppressedVisualEffects(),
+ r.getSystemGeneratedSmartActions(),
+ r.getSmartReplies(),
+ r.getImportance(),
+ r.getRankingScore(),
+ r.isConversation(),
+ r.getProposedImportance());
+
+ Bundle signals = new Bundle();
+ signals.putInt(Adjustment.KEY_IMPORTANCE_PROPOSAL, IMPORTANCE_HIGH);
+ Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0);
+ r.addAdjustment(adjustment);
+ r.applyAdjustments();
+
+ assertTrue(extractorData.hasDiffForRankingLocked(r, 1));
+ assertTrue(extractorData.hasDiffForLoggingLocked(r, 1));
+ }
+
+ private NotificationRecord generateRecord() {
+ NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
+ final Notification.Builder builder = new Notification.Builder(getContext())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ Notification n = builder.build();
+ StatusBarNotification sbn = new StatusBarNotification("", "", 0, "", 0,
+ 0, n, UserHandle.ALL, null, System.currentTimeMillis());
+ return new NotificationRecord(getContext(), sbn, channel);
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 5468220..14b0048 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -19,6 +19,7 @@
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_NOT_CONVERSATION;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
@@ -755,6 +756,24 @@
}
@Test
+ public void testProposedImportance() {
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertEquals(IMPORTANCE_UNSPECIFIED, record.getProposedImportance());
+
+ Bundle signals = new Bundle();
+ signals.putInt(Adjustment.KEY_IMPORTANCE_PROPOSAL, IMPORTANCE_DEFAULT);
+ record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId()));
+
+ record.applyAdjustments();
+
+ assertEquals(IMPORTANCE_DEFAULT, record.getProposedImportance());
+ }
+
+ @Test
public void testAppImportance_returnsCorrectly() {
StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 598a22b..770feab 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -1703,6 +1703,7 @@
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
channel.setShowBadge(true);
channel.setAllowBubbles(false);
+ channel.setImportantConversation(true);
int lockMask = 0;
for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
@@ -1718,6 +1719,7 @@
assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
assertFalse(savedChannel.canBypassDnd());
assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
+ assertFalse(channel.isImportantConversation());
assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
assertEquals(channel.canBubble(), savedChannel.canBubble());
@@ -4396,7 +4398,7 @@
new NotificationChannel("A person calls", "calls from A", IMPORTANCE_DEFAULT);
channel2.setConversationId(calls.getId(), convoId);
channel2.setImportantConversation(true);
- mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel2, false, false);
List<ConversationChannelWrapper> convos =
mHelper.getConversations(IntArray.wrap(new int[] {0}), false);
@@ -4473,7 +4475,7 @@
new NotificationChannel("A person calls", "calls from A", IMPORTANCE_DEFAULT);
channel2.setConversationId(calls.getId(), convoId);
channel2.setImportantConversation(true);
- mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel2, false, false);
List<ConversationChannelWrapper> convos =
mHelper.getConversations(IntArray.wrap(new int[] {0}), false);
@@ -4501,13 +4503,13 @@
new NotificationChannel("A person msgs", "messages from A", IMPORTANCE_DEFAULT);
channel.setConversationId(messages.getId(), convoId);
channel.setImportantConversation(true);
- mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel, false, false);
NotificationChannel diffConvo =
new NotificationChannel("B person msgs", "messages from B", IMPORTANCE_DEFAULT);
diffConvo.setConversationId(p.getId(), "different convo");
diffConvo.setImportantConversation(true);
- mHelper.createNotificationChannel(PKG_P, UID_P, diffConvo, true, false);
+ mHelper.createNotificationChannel(PKG_P, UID_P, diffConvo, false, false);
NotificationChannel channel2 =
new NotificationChannel("A person calls", "calls from A", IMPORTANCE_DEFAULT);
@@ -4534,7 +4536,7 @@
new NotificationChannel("A person msgs", "messages from A", IMPORTANCE_DEFAULT);
channel.setConversationId(messages.getId(), convoId);
channel.setImportantConversation(true);
- mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel, false, false);
mHelper.permanentlyDeleteNotificationChannel(PKG_O, UID_O, "messages");
@@ -4935,7 +4937,7 @@
"conversation", IMPORTANCE_DEFAULT);
friend.setConversationId(parent.getId(), "friend");
friend.setImportantConversation(true);
- mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O, friend, false, false);
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index e30e5db..74ea7d7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -40,7 +40,7 @@
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.WindowManager;
-import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IOnBackInvokedCallback;
import android.window.OnBackInvokedCallback;
@@ -242,11 +242,11 @@
private IOnBackInvokedCallback createOnBackInvokedCallback() {
return new IOnBackInvokedCallback.Stub() {
@Override
- public void onBackStarted(BackEvent backEvent) {
+ public void onBackStarted(BackMotionEvent backEvent) {
}
@Override
- public void onBackProgressed(BackEvent backEvent) {
+ public void onBackProgressed(BackMotionEvent backEvent) {
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 8bb79e3..45b30b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -155,6 +155,18 @@
}
@Test
+ public void testTreatmentDisabledPerApp_noForceRotationOrRefresh()
+ throws Exception {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ when(mActivity.mLetterboxUiController.shouldForceRotateForCameraCompat())
+ .thenReturn(false);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertNoForceRotationOrRefresh();
+ }
+
+ @Test
public void testMultiWindowMode_returnUnspecified_noForceRotationOrRefresh() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, mDisplayContent);
@@ -327,7 +339,21 @@
}
@Test
- public void testOnActivityConfigurationChanging_refreshDisabled_noRefresh() throws Exception {
+ public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh()
+ throws Exception {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ when(mActivity.mLetterboxUiController.shouldRefreshActivityForCameraCompat())
+ .thenReturn(false);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+ assertActivityRefreshRequested(/* refreshRequested */ false);
+ }
+
+ @Test
+ public void testOnActivityConfigurationChanging_refreshDisabledPerApp_noRefresh()
+ throws Exception {
when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()).thenReturn(false);
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -362,6 +388,19 @@
assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
}
+ @Test
+ public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
+ throws Exception {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ when(mActivity.mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat())
+ .thenReturn(true);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+ assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+ }
+
private void configureActivity(@ScreenOrientation int activityOrientation) {
configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 6d778afe..5e087f0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -16,8 +16,14 @@
package com.android.server.wm;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -74,6 +80,8 @@
mController = new LetterboxUiController(mWm, mActivity);
}
+ // shouldIgnoreRequestedOrientation
+
@Test
@EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
public void testShouldIgnoreRequestedOrientation_activityRelaunching_returnsTrue() {
@@ -134,7 +142,7 @@
}
@Test
- @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
public void testShouldIgnoreRequestedOrientation_flagIsDisabled_returnsFalse() {
prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch();
doReturn(false).when(mLetterboxConfiguration)
@@ -143,6 +151,163 @@
assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
}
+ // shouldRefreshActivityForCameraCompat
+
+ @Test
+ public void testShouldRefreshActivityForCameraCompat_flagIsDisabled_returnsFalse() {
+ doReturn(false).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+
+ assertFalse(mController.shouldRefreshActivityForCameraCompat());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
+ public void testShouldRefreshActivityForCameraCompat_overrideEnabled_returnsFalse() {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+
+ assertFalse(mController.shouldRefreshActivityForCameraCompat());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
+ public void testShouldRefreshActivityForCameraCompat_propertyIsTrueAndOverride_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldRefreshActivityForCameraCompat());
+ }
+
+ @Test
+ public void testShouldRefreshActivityForCameraCompat_propertyIsFalse_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldRefreshActivityForCameraCompat());
+ }
+
+ @Test
+ public void testShouldRefreshActivityForCameraCompat_propertyIsTrue_returnsTrue()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldRefreshActivityForCameraCompat());
+ }
+
+ // shouldRefreshActivityViaPauseForCameraCompat
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
+ public void testShouldRefreshActivityViaPauseForCameraCompat_flagIsDisabled_returnsFalse() {
+ doReturn(false).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+
+ assertFalse(mController.shouldRefreshActivityViaPauseForCameraCompat());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
+ public void testShouldRefreshActivityViaPauseForCameraCompat_overrideEnabled_returnsTrue() {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+
+ assertTrue(mController.shouldRefreshActivityViaPauseForCameraCompat());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
+ public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsFalseAndOverride_returnFalse()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldRefreshActivityViaPauseForCameraCompat());
+ }
+
+ @Test
+ public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsTrue_returnsTrue()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldRefreshActivityViaPauseForCameraCompat());
+ }
+
+ // shouldForceRotateForCameraCompat
+
+ @Test
+ public void testShouldForceRotateForCameraCompat_flagIsDisabled_returnsFalse() {
+ doReturn(false).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+
+ assertFalse(mController.shouldForceRotateForCameraCompat());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
+ public void testShouldForceRotateForCameraCompat_overrideEnabled_returnsFalse() {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+
+ assertFalse(mController.shouldForceRotateForCameraCompat());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
+ public void testShouldForceRotateForCameraCompat_propertyIsTrueAndOverride_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldForceRotateForCameraCompat());
+ }
+
+ @Test
+ public void testShouldForceRotateForCameraCompat_propertyIsFalse_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldForceRotateForCameraCompat());
+ }
+
+ @Test
+ public void testShouldForceRotateForCameraCompat_propertyIsTrue_returnsTrue()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldForceRotateForCameraCompat());
+ }
+
private void mockThatProperty(String propertyName, boolean value) throws Exception {
Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
/* className */ "");
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index adf694c..db6ac0b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -30,6 +30,7 @@
import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.os.Process.NOBODY_UID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -1220,20 +1221,35 @@
@Test
public void testCreateRecentTaskInfo_detachedTask() {
- final Task task = createTaskBuilder(".Task").setCreateActivity(true).build();
+ final Task task = createTaskBuilder(".Task").build();
+ final ComponentName componentName = getUniqueComponentName();
+ new ActivityBuilder(mSupervisor.mService)
+ .setTask(task)
+ .setUid(NOBODY_UID)
+ .setComponent(componentName)
+ .build();
final TaskDisplayArea tda = task.getDisplayArea();
assertTrue(task.isAttached());
assertTrue(task.supportsMultiWindow());
- RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true);
+ RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+ true /* getTasksAllowed */);
assertTrue(info.supportsMultiWindow);
+ info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+ false /* getTasksAllowed */);
+
+ assertFalse(info.topActivity.equals(componentName));
+ assertFalse(info.topActivityInfo.packageName.equals(componentName.getPackageName()));
+ assertFalse(info.baseActivity.equals(componentName));
+
// The task can be put in split screen even if it is not attached now.
task.removeImmediately();
- info = mRecentTasks.createRecentTaskInfo(task, true);
+ info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+ true /* getTasksAllowed */);
assertTrue(info.supportsMultiWindow);
@@ -1242,7 +1258,8 @@
doReturn(false).when(tda).supportsNonResizableMultiWindow();
doReturn(false).when(task).isResizeable();
- info = mRecentTasks.createRecentTaskInfo(task, true);
+ info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+ true /* getTasksAllowed */);
assertFalse(info.supportsMultiWindow);
@@ -1250,7 +1267,8 @@
// the device supports it.
doReturn(true).when(tda).supportsNonResizableMultiWindow();
- info = mRecentTasks.createRecentTaskInfo(task, true);
+ info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+ true /* getTasksAllowed */);
assertTrue(info.supportsMultiWindow);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 2420efc..6b3425c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -796,6 +796,40 @@
}
@Test
+ public void testApplyTransaction_createTaskFragment_withPairedActivityToken() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activityAtBottom = createActivityRecord(task);
+ final int uid = Binder.getCallingUid();
+ activityAtBottom.info.applicationInfo.uid = uid;
+ activityAtBottom.getTask().effectiveUid = uid;
+ mTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setFragmentToken(mFragmentToken)
+ .createActivityCount(1)
+ .build();
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ final IBinder fragmentToken1 = new Binder();
+ final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+ mOrganizerToken, fragmentToken1, activityAtBottom.token)
+ .setPairedActivityToken(activityAtBottom.token)
+ .build();
+ mTransaction.setTaskFragmentOrganizer(mIOrganizer);
+ mTransaction.createTaskFragment(params);
+ assertApplyTransactionAllowed(mTransaction);
+
+ // Successfully created a TaskFragment.
+ final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(
+ fragmentToken1);
+ assertNotNull(taskFragment);
+ // The new TaskFragment should be positioned right above the paired activity.
+ assertEquals(task.mChildren.indexOf(activityAtBottom) + 1,
+ task.mChildren.indexOf(taskFragment));
+ // The top TaskFragment should remain on top.
+ assertEquals(task.mChildren.indexOf(taskFragment) + 1,
+ task.mChildren.indexOf(mTaskFragment));
+ }
+
+ @Test
public void testApplyTransaction_enforceHierarchyChange_reparentChildren() {
doReturn(true).when(mTaskFragment).isAttached();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 06a79f4..1407cdd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -391,7 +391,7 @@
dc.updateOrientation();
dc.sendNewConfiguration();
spyOn(wallpaperWindow);
- doReturn(new Rect(0, 0, width, height)).when(wallpaperWindow).getLastReportedBounds();
+ doReturn(new Rect(0, 0, width, height)).when(wallpaperWindow).getParentFrame();
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 0568f2a..3556ded 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -42,6 +42,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -969,6 +970,19 @@
assertFalse(sameTokenWindow.needsRelativeLayeringToIme());
}
+ @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+ @Test
+ public void testNeedsRelativeLayeringToIme_systemDialog() {
+ WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY,
+ mDisplayContent,
+ "SystemDialog", true);
+ mDisplayContent.setImeLayeringTarget(mAppWindow);
+ mAppWindow.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ makeWindowVisible(mImeWindow);
+ systemDialogWindow.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
+ assertTrue(systemDialogWindow.needsRelativeLayeringToIme());
+ }
+
@Test
public void testSetFreezeInsetsState() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 77fca45..7959d82 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -31,6 +32,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
@@ -543,4 +545,28 @@
assertZOrderGreaterThan(mTransaction, popupWindow.getSurfaceControl(),
mDisplayContent.getImeContainer().getSurfaceControl());
}
+
+ @Test
+ public void testSystemDialogWindow_expectHigherThanIme_inMultiWindow() {
+ // Simulate the app window is in multi windowing mode and being IME target
+ mAppWindow.getConfiguration().windowConfiguration.setWindowingMode(
+ WINDOWING_MODE_MULTI_WINDOW);
+ mDisplayContent.setImeLayeringTarget(mAppWindow);
+ mDisplayContent.setImeInputTarget(mAppWindow);
+ makeWindowVisible(mImeWindow);
+
+ // Create a popupWindow
+ final WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY,
+ mDisplayContent, "SystemDialog", true);
+ systemDialogWindow.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
+ spyOn(systemDialogWindow);
+
+ mDisplayContent.assignChildLayers(mTransaction);
+
+ // Verify the surface layer of the popupWindow should higher than IME
+ verify(systemDialogWindow).needsRelativeLayeringToIme();
+ assertThat(systemDialogWindow.needsRelativeLayeringToIme()).isTrue();
+ assertZOrderGreaterThan(mTransaction, systemDialogWindow.getSurfaceControl(),
+ mDisplayContent.getImeContainer().getSurfaceControl());
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 55bf2ab..1c57ba5 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -55,6 +55,7 @@
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1152,6 +1153,11 @@
Slog.w(TAG, "Failed to report onError status: " + e);
}
}
+ // Can improve to log exit reason if needed
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH,
+ mVoiceInteractionServiceUid);
}
@Override