Merge "[flexiglass] Always round up the remaining throttle milliseconds." into main
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index 73a80b1..ad3e422 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -98,7 +98,10 @@
switch (action) {
case Intent.ACTION_PACKAGE_RESTARTED: {
synchronized (mLock) {
- mPackageStoppedState.add(pkgUid, pkgName, Boolean.TRUE);
+ // ACTION_PACKAGE_RESTARTED doesn't always mean the app is placed and kept
+ // in the stopped state, so don't put TRUE in the cache. Remove any existing
+ // entry and rely on an explicit call to PackageManager's isStopped() API.
+ mPackageStoppedState.delete(pkgUid, pkgName);
updateJobRestrictionsForUidLocked(pkgUid, false);
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 7731fac..c2c4c9e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -39298,7 +39298,7 @@
method @Nullable public java.util.Date getKeyValidityStart();
method @NonNull public String getKeystoreAlias();
method public int getMaxUsageCount();
- method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
+ method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
method public int getPurposes();
method @NonNull public String[] getSignaturePaddings();
method public int getUserAuthenticationType();
@@ -39306,7 +39306,7 @@
method public boolean isDevicePropertiesAttestationIncluded();
method @NonNull public boolean isDigestsSpecified();
method public boolean isInvalidatedByBiometricEnrollment();
- method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified();
+ method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified();
method public boolean isRandomizedEncryptionRequired();
method public boolean isStrongBoxBacked();
method public boolean isUnlockedDeviceRequired();
@@ -39338,7 +39338,7 @@
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForOriginationEnd(java.util.Date);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMaxUsageCount(int);
- method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...);
+ method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean);
@@ -39443,14 +39443,14 @@
method @Nullable public java.util.Date getKeyValidityForOriginationEnd();
method @Nullable public java.util.Date getKeyValidityStart();
method public int getMaxUsageCount();
- method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
+ method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
method public int getPurposes();
method @NonNull public String[] getSignaturePaddings();
method public int getUserAuthenticationType();
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isDigestsSpecified();
method public boolean isInvalidatedByBiometricEnrollment();
- method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified();
+ method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified();
method public boolean isRandomizedEncryptionRequired();
method public boolean isUnlockedDeviceRequired();
method public boolean isUserAuthenticationRequired();
@@ -39472,7 +39472,7 @@
method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date);
method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityStart(java.util.Date);
method @NonNull public android.security.keystore.KeyProtection.Builder setMaxUsageCount(int);
- method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...);
+ method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...);
method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean);
method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...);
method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 777bca2..a1465df 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3248,6 +3248,7 @@
method @Deprecated public int getDefaultNavigationPolicy();
method public int getDevicePolicy(int);
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @Nullable public android.content.ComponentName getHomeComponent();
+ method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @Nullable public android.content.ComponentName getInputMethodComponent();
method public int getLockState();
method @Nullable public String getName();
method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
@@ -3281,6 +3282,7 @@
method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
+ method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName);
method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 0d73e44..0253ddd 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -258,6 +258,7 @@
// Mapping of @PolicyType to @DevicePolicy
@NonNull private final SparseIntArray mDevicePolicies;
@Nullable private final ComponentName mHomeComponent;
+ @Nullable private final ComponentName mInputMethodComponent;
@NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;
@Nullable private final IVirtualSensorCallback mVirtualSensorCallback;
private final int mAudioPlaybackSessionId;
@@ -273,6 +274,7 @@
@Nullable String name,
@NonNull SparseIntArray devicePolicies,
@Nullable ComponentName homeComponent,
+ @Nullable ComponentName inputMethodComponent,
@NonNull List<VirtualSensorConfig> virtualSensorConfigs,
@Nullable IVirtualSensorCallback virtualSensorCallback,
int audioPlaybackSessionId,
@@ -289,6 +291,7 @@
mName = name;
mDevicePolicies = Objects.requireNonNull(devicePolicies);
mHomeComponent = homeComponent;
+ mInputMethodComponent = inputMethodComponent;
mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
mVirtualSensorCallback = virtualSensorCallback;
mAudioPlaybackSessionId = audioPlaybackSessionId;
@@ -312,6 +315,7 @@
mAudioPlaybackSessionId = parcel.readInt();
mAudioRecordingSessionId = parcel.readInt();
mHomeComponent = parcel.readTypedObject(ComponentName.CREATOR);
+ mInputMethodComponent = parcel.readTypedObject(ComponentName.CREATOR);
}
/**
@@ -336,6 +340,18 @@
}
/**
+ * Returns the custom component used as input method on all displays owned by this virtual
+ * device.
+ *
+ * @see Builder#setInputMethodComponent
+ */
+ @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME)
+ @Nullable
+ public ComponentName getInputMethodComponent() {
+ return mInputMethodComponent;
+ }
+
+ /**
* Returns the user handles with matching managed accounts on the remote device to which
* this virtual device is streaming.
*
@@ -532,6 +548,7 @@
dest.writeInt(mAudioPlaybackSessionId);
dest.writeInt(mAudioRecordingSessionId);
dest.writeTypedObject(mHomeComponent, flags);
+ dest.writeTypedObject(mInputMethodComponent, flags);
}
@Override
@@ -563,6 +580,8 @@
&& Objects.equals(mActivityPolicyExemptions, that.mActivityPolicyExemptions)
&& mDefaultActivityPolicy == that.mDefaultActivityPolicy
&& Objects.equals(mName, that.mName)
+ && Objects.equals(mHomeComponent, that.mHomeComponent)
+ && Objects.equals(mInputMethodComponent, that.mInputMethodComponent)
&& mAudioPlaybackSessionId == that.mAudioPlaybackSessionId
&& mAudioRecordingSessionId == that.mAudioRecordingSessionId;
}
@@ -572,7 +591,8 @@
int hashCode = Objects.hash(
mLockState, mUsersWithMatchingAccounts, mCrossTaskNavigationExemptions,
mDefaultNavigationPolicy, mActivityPolicyExemptions, mDefaultActivityPolicy, mName,
- mDevicePolicies, mHomeComponent, mAudioPlaybackSessionId, mAudioRecordingSessionId);
+ mDevicePolicies, mHomeComponent, mInputMethodComponent, mAudioPlaybackSessionId,
+ mAudioRecordingSessionId);
for (int i = 0; i < mDevicePolicies.size(); i++) {
hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
@@ -593,6 +613,7 @@
+ " mName=" + mName
+ " mDevicePolicies=" + mDevicePolicies
+ " mHomeComponent=" + mHomeComponent
+ + " mInputMethodComponent=" + mInputMethodComponent
+ " mAudioPlaybackSessionId=" + mAudioPlaybackSessionId
+ " mAudioRecordingSessionId=" + mAudioRecordingSessionId
+ ")";
@@ -612,6 +633,8 @@
pw.println(prefix + "mActivityPolicyExemptions=" + mActivityPolicyExemptions);
pw.println(prefix + "mDevicePolicies=" + mDevicePolicies);
pw.println(prefix + "mVirtualSensorConfigs=" + mVirtualSensorConfigs);
+ pw.println(prefix + "mHomeComponent=" + mHomeComponent);
+ pw.println(prefix + "mInputMethodComponent=" + mInputMethodComponent);
pw.println(prefix + "mAudioPlaybackSessionId=" + mAudioPlaybackSessionId);
pw.println(prefix + "mAudioRecordingSessionId=" + mAudioRecordingSessionId);
}
@@ -644,16 +667,17 @@
private int mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
private boolean mDefaultActivityPolicyConfigured = false;
@Nullable private String mName;
- @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
+ @NonNull private final SparseIntArray mDevicePolicies = new SparseIntArray();
private int mAudioPlaybackSessionId = AUDIO_SESSION_ID_GENERATE;
private int mAudioRecordingSessionId = AUDIO_SESSION_ID_GENERATE;
- @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
+ @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
@Nullable private Executor mVirtualSensorCallbackExecutor;
@Nullable private VirtualSensorCallback mVirtualSensorCallback;
@Nullable private Executor mVirtualSensorDirectChannelCallbackExecutor;
@Nullable private VirtualSensorDirectChannelCallback mVirtualSensorDirectChannelCallback;
@Nullable private ComponentName mHomeComponent;
+ @Nullable private ComponentName mInputMethodComponent;
private static class VirtualSensorCallbackDelegate extends IVirtualSensorCallback.Stub {
@NonNull
@@ -749,6 +773,28 @@
}
/**
+ * Specifies a component to be used as input method on all displays owned by this virtual
+ * device.
+ *
+ * @param inputMethodComponent The component name to be used as input method. Must comply to
+ * all general input method requirements described in the guide to
+ * <a href="{@docRoot}guide/topics/text/creating-input-method.html">
+ * Creating an Input Method</a>. If the given component is not available for any user that
+ * may interact with the virtual device, then there will effectively be no IME on this
+ * device's displays for that user.
+ *
+ * @see android.inputmethodservice.InputMethodService
+ * @attr ref android.R.styleable#InputMethod_isVirtualDeviceOnly
+ * @attr ref android.R.styleable#InputMethod_showInInputMethodPicker
+ */
+ @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME)
+ @NonNull
+ public Builder setInputMethodComponent(@Nullable ComponentName inputMethodComponent) {
+ mInputMethodComponent = inputMethodComponent;
+ return this;
+ }
+
+ /**
* Sets the user handles with matching managed accounts on the remote device to which
* this virtual device is streaming. The caller is responsible for verifying the presence
* and legitimacy of a matching managed account on the remote device.
@@ -1136,6 +1182,7 @@
mName,
mDevicePolicies,
mHomeComponent,
+ mInputMethodComponent,
mVirtualSensorConfigs,
virtualSensorCallbackDelegate,
mAudioPlaybackSessionId,
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 457fd63..51f3d8e 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -379,7 +379,8 @@
/**
* If true, the requestor of the unarchival has specified that the app should be unarchived
- * for all users.
+ * for all users. Sent as part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE}
+ * intent.
*/
@FlaggedApi(Flags.FLAG_ARCHIVING)
public static final String EXTRA_UNARCHIVE_ALL_USERS =
@@ -396,6 +397,9 @@
* with an intent for a corresponding follow-up action (e.g. storage clearing dialog) or a
* failure dialog.
*
+ * <p> Used as part of {@link #requestUnarchive} to return the status of the unarchival through
+ * the {@link IntentSender}.
+ *
* @see #requestUnarchive
*/
@FlaggedApi(Flags.FLAG_ARCHIVING)
@@ -704,7 +708,8 @@
/**
* The installer responsible for the unarchival is disabled.
*
- * <p> Should only be used by the system.
+ * <p> The system will return this status if appropriate. Installers do not need to verify for
+ * this error.
*/
@FlaggedApi(Flags.FLAG_ARCHIVING)
public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4;
@@ -712,7 +717,8 @@
/**
* The installer responsible for the unarchival has been uninstalled
*
- * <p> Should only be used by the system.
+ * <p> The system will return this status if appropriate. Installers do not need to verify for
+ * this error.
*/
@FlaggedApi(Flags.FLAG_ARCHIVING)
public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5;
diff --git a/core/java/android/database/ContentObserver.java b/core/java/android/database/ContentObserver.java
index 39c9400..4322bed 100644
--- a/core/java/android/database/ContentObserver.java
+++ b/core/java/android/database/ContentObserver.java
@@ -31,6 +31,7 @@
import java.util.Arrays;
import java.util.Collection;
+import java.util.concurrent.Executor;
/**
* Receives call backs for changes to content.
@@ -54,6 +55,7 @@
private Transport mTransport; // guarded by mLock
Handler mHandler;
+ private final Executor mExecutor;
/**
* Creates a content observer.
@@ -62,6 +64,18 @@
*/
public ContentObserver(Handler handler) {
mHandler = handler;
+ mExecutor = null;
+ }
+
+ /**
+ * @hide
+ * Creates a content observer with an executor.
+ *
+ * @param executor The executor to run {@link #onChange} on, or null if none.
+ * @param unused a second argument to avoid source incompatibility.
+ */
+ public ContentObserver(@Nullable Executor executor, int unused) {
+ mExecutor = executor;
}
/**
@@ -306,12 +320,19 @@
/** @hide */
public final void dispatchChange(boolean selfChange, @NonNull Collection<Uri> uris,
@NotifyFlags int flags, @UserIdInt int userId) {
- if (mHandler == null) {
- onChange(selfChange, uris, flags, userId);
- } else {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> {
+ onChange(selfChange, uris, flags, userId);
+ });
+ } else if (mHandler != null) {
+ // Supporting Handler directly rather than wrapping in a HandlerExecutor
+ // avoids introducing a RejectedExecutionException for legacy code when
+ // the post fails.
mHandler.post(() -> {
onChange(selfChange, uris, flags, userId);
});
+ } else {
+ onChange(selfChange, uris, flags, userId);
}
}
diff --git a/core/java/android/database/ExecutorContentObserver.java b/core/java/android/database/ExecutorContentObserver.java
new file mode 100644
index 0000000..3ea807d
--- /dev/null
+++ b/core/java/android/database/ExecutorContentObserver.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+import android.annotation.Nullable;
+
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ *
+ * Receives callbacks for changes to content.
+ * Must be implemented by objects which are added to a {@link ContentObservable}.
+ */
+public abstract class ExecutorContentObserver extends ContentObserver {
+ /**
+ * Creates a content observer that uses an executor for change handling.
+ *
+ * @param executor The executor to run {@link #onChange} on, or null if none.
+ */
+ public ExecutorContentObserver(@Nullable Executor executor) {
+ super(executor, 0);
+ }
+}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 0dc0413..28ef70b 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -8,6 +8,13 @@
}
flag {
+ name: "mgf1_digest_setter"
+ namespace: "hardware_backed_security"
+ description: "Feature flag for mgf1 digest setter in key generation and import parameters."
+ bug: "308378912"
+}
+
+flag {
name: "fix_unlocked_device_required_keys_v2"
namespace: "hardware_backed_security"
description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys"
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index f828cff..ad0e9a4 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -41,3 +41,10 @@
description: "Prevent a task to restart based on a visible window during task switch."
bug: "171459802"
}
+
+flag {
+ name: "bal_respect_app_switch_state_when_check_bound_by_foreground_uid"
+ namespace: "responsible_apis"
+ description: "Prevent BAL based on it is bound by foreground Uid but the app switch is stopped."
+ bug: "171459802"
+}
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index 1e0b2a0..efc1455 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -80,7 +80,8 @@
// Suspension conditions were modified, dismiss any related visible dialogs.
final String[] modified = intent.getStringArrayExtra(
Intent.EXTRA_CHANGED_PACKAGE_LIST);
- if (ArrayUtils.contains(modified, mSuspendedPackage)) {
+ if (ArrayUtils.contains(modified, mSuspendedPackage)
+ && !isPackageSuspended(mSuspendedPackage)) {
if (!isFinishing()) {
Slog.w(TAG, "Package " + mSuspendedPackage + " has modified"
+ " suspension conditions while dialog was visible. Finishing.");
@@ -92,6 +93,15 @@
}
};
+ private boolean isPackageSuspended(String packageName) {
+ try {
+ return mPm.isPackageSuspended(packageName);
+ } catch (PackageManager.NameNotFoundException ne) {
+ Slog.e(TAG, "Package " + packageName + " not found", ne);
+ }
+ return false;
+ }
+
private CharSequence getAppLabel(String packageName) {
try {
return mPm.getApplicationInfoAsUser(packageName, 0, mUserId).loadLabel(mPm);
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 034a55a..e1c1a42 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3855,8 +3855,9 @@
device -->
<attr name="isVrOnly" format="boolean"/>
<!-- Specifies if an IME can only be used on a display created by a virtual device.
- @hide @SystemApi
- @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") -->
+ @see android.companion.virtual.VirtualDeviceParams.Builder#setInputMethodComponent
+ @hide @SystemApi -->
+ <!-- @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") -->
<attr name="isVirtualDeviceOnly" format="boolean"/>
<attr name="__removed2" format="boolean" />
<!-- Specifies whether the IME supports showing inline suggestions. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 73a7e42..1f6ac80 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6312,11 +6312,6 @@
<!-- Content of connected display unavailable due to thermals notification. [CHAR LIMIT=NONE] -->
<string name="connected_display_thermally_unavailable_notification_content">Your device is too warm and can\'t mirror to the display until it cools down</string>
- <!-- Title of cable don't support displays notifications. [CHAR LIMIT=NONE] -->
- <string name="connected_display_cable_dont_support_displays_notification_title">Cable may not support displays</string>
- <!-- Content of cable don't support displays notification. [CHAR LIMIT=NONE] -->
- <string name="connected_display_cable_dont_support_displays_notification_content">Your USB-C cable may not connect to displays properly</string>
-
<!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] -->
<string name="concurrent_display_notification_name">Dual screen</string>
<!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0ddb05e..a5b1028 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5107,8 +5107,6 @@
<java-symbol type="string" name="connected_display_unavailable_notification_title"/>
<java-symbol type="string" name="connected_display_unavailable_notification_content"/>
<java-symbol type="string" name="connected_display_thermally_unavailable_notification_content"/>
- <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_title"/>
- <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_content"/>
<java-symbol type="string" name="concurrent_display_notification_name"/>
<java-symbol type="string" name="concurrent_display_notification_active_title"/>
<java-symbol type="string" name="concurrent_display_notification_active_content"/>
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 231fa48..4982f37 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -618,7 +618,7 @@
* @see #isMgf1DigestsSpecified()
*/
@NonNull
- @FlaggedApi("MGF1_DIGEST_SETTER")
+ @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
public @KeyProperties.DigestEnum Set<String> getMgf1Digests() {
if (mMgf1Digests.isEmpty()) {
throw new IllegalStateException("Mask generation function (MGF) not specified");
@@ -633,7 +633,7 @@
* @see #getMgf1Digests()
*/
@NonNull
- @FlaggedApi("MGF1_DIGEST_SETTER")
+ @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
public boolean isMgf1DigestsSpecified() {
return !mMgf1Digests.isEmpty();
}
@@ -1292,7 +1292,7 @@
* <p>See {@link KeyProperties}.{@code DIGEST} constants.
*/
@NonNull
- @FlaggedApi("MGF1_DIGEST_SETTER")
+ @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
public Builder setMgf1Digests(@NonNull @KeyProperties.DigestEnum String... mgf1Digests) {
mMgf1Digests = Set.of(mgf1Digests);
return this;
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index c1e3bab..7b6b2d1 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -401,7 +401,7 @@
* @see #isMgf1DigestsSpecified()
*/
@NonNull
- @FlaggedApi("MGF1_DIGEST_SETTER")
+ @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
public @KeyProperties.DigestEnum Set<String> getMgf1Digests() {
if (mMgf1Digests.isEmpty()) {
throw new IllegalStateException("Mask generation function (MGF) not specified");
@@ -416,7 +416,7 @@
* @see #getMgf1Digests()
*/
@NonNull
- @FlaggedApi("MGF1_DIGEST_SETTER")
+ @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
public boolean isMgf1DigestsSpecified() {
return !mMgf1Digests.isEmpty();
}
@@ -799,7 +799,7 @@
* <p>See {@link KeyProperties}.{@code DIGEST} constants.
*/
@NonNull
- @FlaggedApi("MGF1_DIGEST_SETTER")
+ @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
public Builder setMgf1Digests(@Nullable @KeyProperties.DigestEnum String... mgf1Digests) {
mMgf1Digests = Set.of(mgf1Digests);
return this;
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index ed4b485..9c05a3a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -28,6 +28,7 @@
import android.hardware.security.keymint.Tag;
import android.os.Build;
import android.os.StrictMode;
+import android.security.Flags;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore2;
import android.security.KeyStoreException;
@@ -853,6 +854,22 @@
KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, mgf1Digest
));
});
+
+ /* If the MGF1 Digest setter is not set, fall back to the previous behaviour:
+ * Add, as MGF1 Digest function, all the primary digests.
+ * Avoid adding the default MGF1 digest as it will have been included in the
+ * mKeymasterMgf1Digests field.
+ */
+ if (!getMgf1DigestSetterFlag()) {
+ final int defaultMgf1Digest = KeyProperties.Digest.toKeymaster(
+ DEFAULT_MGF1_DIGEST);
+ ArrayUtils.forEach(mKeymasterDigests, (digest) -> {
+ if (digest != defaultMgf1Digest) {
+ params.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, digest));
+ }
+ });
+ }
}
});
ArrayUtils.forEach(mKeymasterSignaturePaddings, (padding) -> {
@@ -928,6 +945,16 @@
return params;
}
+ private static boolean getMgf1DigestSetterFlag() {
+ try {
+ return Flags.mgf1DigestSetter();
+ } catch (SecurityException e) {
+ Log.w(TAG, "Cannot read MGF1 Digest setter flag value", e);
+ return false;
+ }
+ }
+
+
private void addAlgorithmSpecificParameters(List<KeyParameter> params) {
switch (mKeymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_RSA:
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index ddbd93e..2d8c5a3 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -25,6 +25,7 @@
import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.SecurityLevel;
import android.os.StrictMode;
+import android.security.Flags;
import android.security.GateKeeper;
import android.security.KeyStore2;
import android.security.KeyStoreParameter;
@@ -256,6 +257,15 @@
}
}
+ private static boolean getMgf1DigestSetterFlag() {
+ try {
+ return Flags.mgf1DigestSetter();
+ } catch (SecurityException e) {
+ Log.w(NAME, "Cannot read MGF1 Digest setter flag value", e);
+ return false;
+ }
+ }
+
@Override
public Date engineGetCreationDate(String alias) {
KeyEntryResponse response = getKeyMetadata(alias);
@@ -537,11 +547,31 @@
/* Because of default MGF1 digest is SHA-1. It has to be added in Key
* characteristics. Otherwise, crypto operations will fail with Incompatible
* MGF1 digest.
+ * If the MGF1 Digest setter flag isn't set, then the condition in the
+ * if clause above must be false (cannot have MGF1 digests specified if the
+ * flag was off). In that case, in addition to adding the default MGF1
+ * digest, we have to add all the other digests as MGF1 Digests.
+ *
*/
importArgs.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST)
));
+ if (!getMgf1DigestSetterFlag()) {
+ final int defaultMgf1Digest = KeyProperties.Digest.toKeymaster(
+ DEFAULT_MGF1_DIGEST);
+ for (String digest : spec.getDigests()) {
+ int digestToAddAsMgf1Digest = KeyProperties.Digest.toKeymaster(
+ digest);
+ // Do not add the default MGF1 digest as it has been added above.
+ if (digestToAddAsMgf1Digest != defaultMgf1Digest) {
+ importArgs.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
+ digestToAddAsMgf1Digest
+ ));
+ }
+ }
+ }
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 85ea809..7a3210e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -637,7 +637,7 @@
* @return the last time this bubble was updated or accessed, whichever is most recent.
*/
long getLastActivity() {
- return isAppBubble() ? Long.MAX_VALUE : Math.max(mLastUpdated, mLastAccessed);
+ return Math.max(mLastUpdated, mLastAccessed);
}
/**
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 595a4af..bbb4b74 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
@@ -784,8 +784,7 @@
if (bubble.getPendingIntentCanceled()
|| !(reason == Bubbles.DISMISS_AGED
|| reason == Bubbles.DISMISS_USER_GESTURE
- || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)
- || bubble.isAppBubble()) {
+ || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
return;
}
if (DEBUG_BUBBLE_DATA) {
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 4bca96b..dab762f 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
@@ -221,6 +221,22 @@
}
@Test
+ public void testAddAppBubble_setsTime() {
+ // Setup
+ mBubbleData.setListener(mListener);
+
+ // Test
+ assertThat(mAppBubble.getLastActivity()).isEqualTo(0);
+ setCurrentTime(1000);
+ mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/,
+ false /* showInShade */);
+
+ // Verify
+ assertThat(mBubbleData.getBubbleInStackWithKey(mAppBubble.getKey())).isEqualTo(mAppBubble);
+ assertThat(mAppBubble.getLastActivity()).isEqualTo(1000);
+ }
+
+ @Test
public void testRemoveBubble() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
@@ -1162,7 +1178,7 @@
}
@Test
- public void test_removeAppBubble_skipsOverflow() {
+ public void test_removeAppBubble_overflows() {
String appBubbleKey = mAppBubble.getKey();
mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/,
false /* showInShade */);
@@ -1170,7 +1186,7 @@
mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_USER_GESTURE);
- assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNull();
+ assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isEqualTo(mAppBubble);
assertThat(mBubbleData.getBubbleInStackWithKey(appBubbleKey)).isNull();
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c52a89c..2970aaa 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -138,6 +138,13 @@
}
flag {
+ name: "qs_new_tiles"
+ namespace: "systemui"
+ description: "Use the new tiles in the Quick Settings. Should have no behavior changes."
+ bug: "241772429"
+}
+
+flag {
name: "coroutine_tracing"
namespace: "systemui"
description: "Adds thread-local data to System UI's global coroutine scopes to "
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
index 5d8eaf7..58052cd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
@@ -2,10 +2,9 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import androidx.compose.ui.unit.IntSize
interface DraggableHandler {
- fun onDragStarted(layoutSize: IntSize, startedPosition: Offset, pointersDown: Int = 1)
+ fun onDragStarted(startedPosition: Offset, overSlop: Float, pointersDown: Int = 1)
fun onDelta(pixels: Float)
fun onDragStopped(velocity: Float)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index a0fba80..3873878 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -66,8 +66,8 @@
orientation: Orientation,
enabled: Boolean,
startDragImmediately: Boolean,
- onDragStarted: (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit,
- onDragDelta: (Float) -> Unit,
+ onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
+ onDragDelta: (delta: Float) -> Unit,
onDragStopped: (velocity: Float) -> Unit,
): Modifier =
this.then(
@@ -86,7 +86,7 @@
private val enabled: Boolean,
private val startDragImmediately: Boolean,
private val onDragStarted:
- (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit,
+ (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
private val onDragDelta: (Float) -> Unit,
private val onDragStopped: (velocity: Float) -> Unit,
) : ModifierNodeElement<MultiPointerDraggableNode>() {
@@ -114,7 +114,7 @@
orientation: Orientation,
enabled: Boolean,
var startDragImmediately: Boolean,
- var onDragStarted: (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit,
+ var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
var onDragDelta: (Float) -> Unit,
var onDragStopped: (velocity: Float) -> Unit,
) : PointerInputModifierNode, DelegatingNode(), CompositionLocalConsumerModifierNode {
@@ -153,9 +153,9 @@
return
}
- val onDragStart: (Offset, Int) -> Unit = { startedPosition, pointersDown ->
+ val onDragStart: (Offset, Float, Int) -> Unit = { startedPosition, overSlop, pointersDown ->
velocityTracker.resetTracking()
- onDragStarted(size, startedPosition, pointersDown)
+ onDragStarted(startedPosition, overSlop, pointersDown)
}
val onDragCancel: () -> Unit = { onDragStopped(/* velocity= */ 0f) }
@@ -203,7 +203,7 @@
private suspend fun PointerInputScope.detectDragGestures(
orientation: Orientation,
startDragImmediately: () -> Boolean,
- onDragStart: (startedPosition: Offset, pointersDown: Int) -> Unit,
+ onDragStart: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
onDragEnd: () -> Unit,
onDragCancel: () -> Unit,
onDrag: (change: PointerInputChange, dragAmount: Float) -> Unit,
@@ -241,7 +241,7 @@
}
}
- onDragStart(drag.position, pressed.size)
+ onDragStart(drag.position, overSlop, pressed.size)
onDrag(drag, overSlop)
val successful =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index 2986504..ded6cc1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -44,22 +44,22 @@
* Overscroll will only be used by the [SceneTransitionLayout] to move to the next scene if the
* gesture begins at the edge of the scrollable component (so that a scroll in that direction
* can no longer be consumed). If the gesture is partially consumed by the scrollable component,
- * there will be NO overscroll effect between scenes.
+ * there will be NO preview of the next scene.
*
* In addition, during scene transitions, scroll events are consumed by the
* [SceneTransitionLayout] instead of the scrollable component.
*/
- EdgeNoOverscroll(canStartOnPostFling = false),
+ EdgeNoPreview(canStartOnPostFling = false),
/**
* Overscroll will only be used by the [SceneTransitionLayout] to move to the next scene if the
* gesture begins at the edge of the scrollable component. If the gesture is partially consumed
- * by the scrollable component, there will be an overscroll effect between scenes.
+ * by the scrollable component, there will be a preview of the next scene.
*
* In addition, during scene transitions, scroll events are consumed by the
* [SceneTransitionLayout] instead of the scrollable component.
*/
- EdgeWithOverscroll(canStartOnPostFling = true),
+ EdgeWithPreview(canStartOnPostFling = true),
/**
* Any overscroll will be used by the [SceneTransitionLayout] to move to the next scene.
@@ -67,7 +67,7 @@
* In addition, during scene transitions, scroll events are consumed by the
* [SceneTransitionLayout] instead of the scrollable component.
*/
- Always(canStartOnPostFling = true),
+ EdgeAlways(canStartOnPostFling = true),
}
internal fun Modifier.nestedScrollToScene(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index b00c886..212c9eb6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:Suppress("NOTHING_TO_INLINE")
+
package com.android.compose.animation.scene
import android.util.Log
@@ -26,7 +28,6 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
@@ -48,14 +49,13 @@
layoutImpl.state.transitionState = value
}
- /**
- * The transition controlled by this gesture handler. It will be set as the [transitionState] in
- * the [SceneTransitionLayoutImpl] whenever this handler is driving the current transition.
- *
- * Note: the initialScene here does not matter, it's only used for initializing the transition
- * and will be replaced when a drag event starts.
- */
- internal val swipeTransition = SwipeTransition(initialScene = currentScene)
+ internal var swipeTransition: SwipeTransition = SwipeTransition(currentScene, currentScene, 1f)
+ private set
+
+ private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
+ if (isDrivingTransition || force) transitionState = newTransition
+ swipeTransition = newTransition
+ }
internal val currentScene: Scene
get() = layoutImpl.scene(transitionState.currentScene)
@@ -63,15 +63,6 @@
internal val isDrivingTransition
get() = transitionState == swipeTransition
- internal var isAnimatingOffset
- get() = swipeTransition.isAnimatingOffset
- private set(value) {
- swipeTransition.isAnimatingOffset = value
- }
-
- internal val swipeTransitionToScene
- get() = swipeTransition._toScene
-
/**
* The velocity threshold at which the intent of the user is to swipe up or down. It is the same
* as SwipeableV2Defaults.VelocityThreshold.
@@ -86,11 +77,17 @@
internal var gestureWithPriority: Any? = null
- internal fun onDragStarted(pointersDown: Int, layoutSize: IntSize, startedPosition: Offset?) {
+ /** The [UserAction]s associated to the current swipe. */
+ private var actionUpOrLeft: UserAction? = null
+ private var actionDownOrRight: UserAction? = null
+ private var actionUpOrLeftNoEdge: UserAction? = null
+ private var actionDownOrRightNoEdge: UserAction? = null
+
+ internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?, overSlop: Float) {
if (isDrivingTransition) {
// This [transition] was already driving the animation: simply take over it.
// Stop animating and start from where the current offset.
- swipeTransition.stopOffsetAnimation()
+ swipeTransition.cancelOffsetAnimation()
return
}
@@ -106,37 +103,29 @@
}
val fromScene = currentScene
+ setCurrentActions(fromScene, startedPosition, pointersDown)
- swipeTransition._currentScene = fromScene
- swipeTransition._fromScene = fromScene
+ if (fromScene.upOrLeft() == null && fromScene.downOrRight() == null) {
+ return
+ }
- // We don't know where we are transitioning to yet given that the drag just started, so set
- // it to fromScene, which will effectively be treated the same as Idle(fromScene).
- swipeTransition._toScene = fromScene
+ val (targetScene, distance) = fromScene.findTargetSceneAndDistance(overSlop)
- swipeTransition.stopOffsetAnimation()
- swipeTransition.dragOffset = 0f
+ updateTransition(SwipeTransition(fromScene, targetScene, distance), force = true)
+ }
- // Use the layout size in the swipe orientation for swipe distance.
- // TODO(b/290184746): Also handle custom distances for transitions. With smaller distances,
- // we will also have to make sure that we correctly handle overscroll.
- swipeTransition.absoluteDistance =
- when (orientation) {
- Orientation.Horizontal -> layoutSize.width
- Orientation.Vertical -> layoutSize.height
- }.toFloat()
-
+ private fun setCurrentActions(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
val fromEdge =
startedPosition?.let { position ->
layoutImpl.edgeDetector.edge(
- layoutSize,
+ fromScene.targetSize,
position.round(),
layoutImpl.density,
orientation,
)
}
- swipeTransition.actionUpOrLeft =
+ val upOrLeft =
Swipe(
direction =
when (orientation) {
@@ -147,7 +136,7 @@
fromEdge = fromEdge,
)
- swipeTransition.actionDownOrRight =
+ val downOrRight =
Swipe(
direction =
when (orientation) {
@@ -159,108 +148,114 @@
)
if (fromEdge == null) {
- swipeTransition.actionUpOrLeftNoEdge = null
- swipeTransition.actionDownOrRightNoEdge = null
+ actionUpOrLeft = null
+ actionDownOrRight = null
+ actionUpOrLeftNoEdge = upOrLeft
+ actionDownOrRightNoEdge = downOrRight
} else {
- swipeTransition.actionUpOrLeftNoEdge =
- (swipeTransition.actionUpOrLeft as Swipe).copy(fromEdge = null)
- swipeTransition.actionDownOrRightNoEdge =
- (swipeTransition.actionDownOrRight as Swipe).copy(fromEdge = null)
- }
-
- if (swipeTransition.absoluteDistance > 0f) {
- transitionState = swipeTransition
+ actionUpOrLeft = upOrLeft
+ actionDownOrRight = downOrRight
+ actionUpOrLeftNoEdge = upOrLeft.copy(fromEdge = null)
+ actionDownOrRightNoEdge = downOrRight.copy(fromEdge = null)
}
}
- internal fun onDrag(delta: Float) {
- if (delta == 0f) return
+ /**
+ * Use the layout size in the swipe orientation for swipe distance.
+ *
+ * TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, we
+ * will also have to make sure that we correctly handle overscroll.
+ */
+ private fun Scene.getAbsoluteDistance(): Float {
+ return when (orientation) {
+ Orientation.Horizontal -> targetSize.width
+ Orientation.Vertical -> targetSize.height
+ }.toFloat()
+ }
+ internal fun onDrag(delta: Float) {
+ if (delta == 0f || !isDrivingTransition) return
swipeTransition.dragOffset += delta
- // First check transition.fromScene should be changed for the case where the user quickly
- // swiped twice in a row to accelerate the transition and go from A => B then B => C really
- // fast.
- maybeHandleAcceleratedSwipe()
-
- val offset = swipeTransition.dragOffset
- val fromScene = swipeTransition._fromScene
+ val (fromScene, acceleratedOffset) =
+ computeFromSceneConsideringAcceleratedSwipe(swipeTransition)
+ swipeTransition.dragOffset += acceleratedOffset
// Compute the target scene depending on the current offset.
- val target = fromScene.findTargetSceneAndDistance(offset)
+ val (targetScene, distance) =
+ fromScene.findTargetSceneAndDistance(swipeTransition.dragOffset)
- if (swipeTransition._toScene.key != target.sceneKey) {
- swipeTransition._toScene = layoutImpl.scenes.getValue(target.sceneKey)
- }
-
- if (swipeTransition._distance != target.distance) {
- swipeTransition._distance = target.distance
+ // TODO(b/290184746): support long scroll A => B => C? especially for non fullscreen scenes
+ if (
+ fromScene.key != swipeTransition.fromScene || targetScene.key != swipeTransition.toScene
+ ) {
+ updateTransition(
+ SwipeTransition(fromScene, targetScene, distance).apply {
+ this.dragOffset = swipeTransition.dragOffset
+ }
+ )
}
}
/**
* Change fromScene in the case where the user quickly swiped multiple times in the same
* direction to accelerate the transition from A => B then B => C.
+ *
+ * @return the new fromScene and a dragOffset to be added in case the scene has changed
+ *
+ * TODO(b/290184746): the second drag needs to pass B to work. Add support for flinging twice
+ * before B has been reached
*/
- private fun maybeHandleAcceleratedSwipe() {
+ private inline fun computeFromSceneConsideringAcceleratedSwipe(
+ swipeTransition: SwipeTransition,
+ ): Pair<Scene, Float> {
val toScene = swipeTransition._toScene
val fromScene = swipeTransition._fromScene
+ val absoluteDistance = swipeTransition.distance.absoluteValue
// If the swipe was not committed, don't do anything.
if (fromScene == toScene || swipeTransition._currentScene != toScene) {
- return
+ return Pair(fromScene, 0f)
}
// If the offset is past the distance then let's change fromScene so that the user can swipe
// to the next screen or go back to the previous one.
val offset = swipeTransition.dragOffset
- val absoluteDistance = swipeTransition.absoluteDistance
- if (offset <= -absoluteDistance && swipeTransition.upOrLeft(fromScene) == toScene.key) {
- swipeTransition.dragOffset += absoluteDistance
- swipeTransition._fromScene = toScene
- } else if (
- offset >= absoluteDistance && swipeTransition.downOrRight(fromScene) == toScene.key
- ) {
- swipeTransition.dragOffset -= absoluteDistance
- swipeTransition._fromScene = toScene
+ return if (offset <= -absoluteDistance && fromScene.upOrLeft() == toScene.key) {
+ Pair(toScene, absoluteDistance)
+ } else if (offset >= absoluteDistance && fromScene.downOrRight() == toScene.key) {
+ Pair(toScene, -absoluteDistance)
+ } else {
+ Pair(fromScene, 0f)
}
-
- // Important note: toScene and distance will be updated right after this function is called,
- // using fromScene and dragOffset.
}
- private class TargetScene(
- val sceneKey: SceneKey,
- val distance: Float,
- )
-
- private fun Scene.findTargetSceneAndDistance(directionOffset: Float): TargetScene {
- val upOrLeft = swipeTransition.upOrLeft(this)
- val downOrRight = swipeTransition.downOrRight(this)
+ // TODO(b/290184746): there are two bugs here:
+ // 1. if both upOrLeft and downOrRight become `null` during a transition this will crash
+ // 2. if one of them changes during a transition, the transition will jump cut to the new target
+ private inline fun Scene.findTargetSceneAndDistance(
+ directionOffset: Float
+ ): Pair<Scene, Float> {
+ val upOrLeft = upOrLeft()
+ val downOrRight = downOrRight()
+ val absoluteDistance = getAbsoluteDistance()
// Compute the target scene depending on the current offset.
- return when {
- directionOffset < 0f && upOrLeft != null -> {
- TargetScene(
- sceneKey = upOrLeft,
- distance = -swipeTransition.absoluteDistance,
- )
- }
- directionOffset > 0f && downOrRight != null -> {
- TargetScene(
- sceneKey = downOrRight,
- distance = swipeTransition.absoluteDistance,
- )
- }
- else -> {
- TargetScene(
- sceneKey = key,
- distance = 0f,
- )
- }
+ return if ((directionOffset < 0f && upOrLeft != null) || downOrRight == null) {
+ Pair(layoutImpl.scene(upOrLeft!!), -absoluteDistance)
+ } else {
+ Pair(layoutImpl.scene(downOrRight), absoluteDistance)
}
}
+ private fun Scene.upOrLeft(): SceneKey? {
+ return userActions[actionUpOrLeft] ?: userActions[actionUpOrLeftNoEdge]
+ }
+
+ private fun Scene.downOrRight(): SceneKey? {
+ return userActions[actionDownOrRight] ?: userActions[actionDownOrRightNoEdge]
+ }
+
internal fun onDragStopped(velocity: Float, canChangeScene: Boolean) {
// The state was changed since the drag started; don't do anything.
if (!isDrivingTransition) {
@@ -291,11 +286,6 @@
// velocity and offset of the transition, then we launch the animation.
val toScene = swipeTransition._toScene
- if (fromScene == toScene) {
- // We were not animating.
- transitionState = TransitionState.Idle(fromScene.key)
- return
- }
// Compute the destination scene (and therefore offset) to settle in.
val offset = swipeTransition.dragOffset
@@ -322,12 +312,14 @@
if (startFromIdlePosition) {
// If there is a next scene, we start the overscroll animation.
- val target = fromScene.findTargetSceneAndDistance(velocity)
- val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key
+ val (targetScene, distance) = fromScene.findTargetSceneAndDistance(velocity)
+ val isValidTarget = distance != 0f && targetScene.key != fromScene.key
if (isValidTarget) {
- swipeTransition._toScene = layoutImpl.scene(target.sceneKey)
- swipeTransition._distance = target.distance
-
+ updateTransition(
+ SwipeTransition(fromScene, targetScene, distance).apply {
+ _currentScene = swipeTransition._currentScene
+ }
+ )
animateTo(targetScene = fromScene, targetOffset = 0f)
} else {
// We will not animate
@@ -382,10 +374,10 @@
) {
swipeTransition.startOffsetAnimation {
coroutineScope.launch {
- if (!isAnimatingOffset) {
+ if (!swipeTransition.isAnimatingOffset) {
swipeTransition.offsetAnimatable.snapTo(swipeTransition.dragOffset)
}
- isAnimatingOffset = true
+ swipeTransition.isAnimatingOffset = true
swipeTransition.offsetAnimatable.animateTo(
targetOffset,
@@ -397,7 +389,7 @@
initialVelocity = initialVelocity,
)
- isAnimatingOffset = false
+ swipeTransition.finishOffsetAnimation()
// Now that the animation is done, the state should be idle. Note that if the state
// was changed since this animation started, some external code changed it and we
@@ -410,29 +402,26 @@
}
}
- internal class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
- var _currentScene by mutableStateOf(initialScene)
+ internal class SwipeTransition(
+ val _fromScene: Scene,
+ val _toScene: Scene,
+ /**
+ * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is
+ * above or to the left of [toScene].
+ */
+ val distance: Float
+ ) : TransitionState.Transition {
+ var _currentScene by mutableStateOf(_fromScene)
override val currentScene: SceneKey
get() = _currentScene.key
- var _fromScene by mutableStateOf(initialScene)
- override val fromScene: SceneKey
- get() = _fromScene.key
+ override val fromScene: SceneKey = _fromScene.key
- var _toScene by mutableStateOf(initialScene)
- override val toScene: SceneKey
- get() = _toScene.key
+ override val toScene: SceneKey = _toScene.key
override val progress: Float
get() {
val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
- if (distance == 0f) {
- // This can happen only if fromScene == toScene.
- error(
- "Transition.progress should be called only when Transition.fromScene != " +
- "Transition.toScene"
- )
- }
return offset / distance
}
@@ -459,46 +448,22 @@
/** Ends any previous [offsetAnimationJob] and runs the new [job]. */
fun startOffsetAnimation(job: () -> Job) {
- stopOffsetAnimation()
+ cancelOffsetAnimation()
offsetAnimationJob = job()
}
- /** Stops any ongoing offset animation. */
- fun stopOffsetAnimation() {
+ /** Cancel any ongoing offset animation. */
+ fun cancelOffsetAnimation() {
offsetAnimationJob?.cancel()
+ finishOffsetAnimation()
+ }
+ fun finishOffsetAnimation() {
if (isAnimatingOffset) {
isAnimatingOffset = false
dragOffset = offsetAnimatable.value
}
}
-
- /** The absolute distance between [fromScene] and [toScene]. */
- var absoluteDistance = 0f
-
- /**
- * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is
- * above or to the left of [toScene].
- */
- var _distance by mutableFloatStateOf(0f)
- val distance: Float
- get() = _distance
-
- /** The [UserAction]s associated to this swipe. */
- var actionUpOrLeft: UserAction = Back
- var actionDownOrRight: UserAction = Back
- var actionUpOrLeftNoEdge: UserAction? = null
- var actionDownOrRightNoEdge: UserAction? = null
-
- fun upOrLeft(scene: Scene): SceneKey? {
- return scene.userActions[actionUpOrLeft]
- ?: actionUpOrLeftNoEdge?.let { scene.userActions[it] }
- }
-
- fun downOrRight(scene: Scene): SceneKey? {
- return scene.userActions[actionDownOrRight]
- ?: actionDownOrRightNoEdge?.let { scene.userActions[it] }
- }
}
companion object {
@@ -509,9 +474,9 @@
private class SceneDraggableHandler(
private val gestureHandler: SceneGestureHandler,
) : DraggableHandler {
- override fun onDragStarted(layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) {
+ override fun onDragStarted(startedPosition: Offset, overSlop: Float, pointersDown: Int) {
gestureHandler.gestureWithPriority = this
- gestureHandler.onDragStarted(pointersDown, layoutSize, startedPosition)
+ gestureHandler.onDragStarted(pointersDown, startedPosition, overSlop)
}
override fun onDelta(pixels: Float) {
@@ -589,7 +554,7 @@
// The progress value can go beyond this range in the case of overscroll.
val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f)
if (shouldSnapToIdle) {
- gestureHandler.swipeTransition.stopOffsetAnimation()
+ gestureHandler.swipeTransition.cancelOffsetAnimation()
gestureHandler.transitionState =
TransitionState.Idle(gestureHandler.swipeTransition.currentScene)
}
@@ -612,15 +577,15 @@
canChangeScene = false // unused: added for consistency
false
}
- NestedScrollBehavior.EdgeNoOverscroll -> {
+ NestedScrollBehavior.EdgeNoPreview -> {
canChangeScene = isZeroOffset
isZeroOffset && hasNextScene(offsetAvailable)
}
- NestedScrollBehavior.EdgeWithOverscroll -> {
+ NestedScrollBehavior.EdgeWithPreview -> {
canChangeScene = isZeroOffset
hasNextScene(offsetAvailable)
}
- NestedScrollBehavior.Always -> {
+ NestedScrollBehavior.EdgeAlways -> {
canChangeScene = true
hasNextScene(offsetAvailable)
}
@@ -639,12 +604,12 @@
behavior.canStartOnPostFling && hasNextScene(velocityAvailable)
},
canContinueScroll = { true },
- onStart = {
+ onStart = { offsetAvailable ->
gestureHandler.gestureWithPriority = this
gestureHandler.onDragStarted(
pointersDown = 1,
- layoutSize = gestureHandler.currentScene.targetSize,
startedPosition = null,
+ overSlop = offsetAvailable,
)
},
onScroll = { offsetAvailable ->
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 07add77..afa184b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -132,8 +132,8 @@
*/
fun Modifier.nestedScrollToScene(
orientation: Orientation,
- startBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoOverscroll,
- endBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoOverscroll,
+ startBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
+ endBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
): Modifier
/**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 2c78dee..116a666 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -46,7 +46,7 @@
// user can't swipe in the other direction.
startDragImmediately =
gestureHandler.isDrivingTransition &&
- gestureHandler.isAnimatingOffset &&
+ gestureHandler.swipeTransition.isAnimatingOffset &&
!canOppositeSwipe,
onDragStarted = gestureHandler.draggable::onDragStarted,
onDragDelta = gestureHandler.draggable::onDelta,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index a5fd1bf..c49a2b8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -38,7 +38,7 @@
private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
private val canStartPostFling: (velocityAvailable: Velocity) -> Boolean,
private val canContinueScroll: () -> Boolean,
- private val onStart: () -> Unit,
+ private val onStart: (offsetAvailable: Offset) -> Unit,
private val onScroll: (offsetAvailable: Offset) -> Offset,
private val onStop: (velocityAvailable: Velocity) -> Velocity,
) : NestedScrollConnection {
@@ -131,7 +131,7 @@
// Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
// lifted (step 3b), or this object has been destroyed (step 3c).
- onStart()
+ onStart(available)
return onScroll(available)
}
@@ -156,7 +156,7 @@
canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
canStartPostFling: (velocityAvailable: Float) -> Boolean,
canContinueScroll: () -> Boolean,
- onStart: () -> Unit,
+ onStart: (offsetAvailable: Float) -> Unit,
onScroll: (offsetAvailable: Float) -> Float,
onStop: (velocityAvailable: Float) -> Float,
) =
@@ -172,7 +172,7 @@
canStartPostFling(velocityAvailable.toFloat())
},
canContinueScroll = canContinueScroll,
- onStart = onStart,
+ onStart = { offsetAvailable -> onStart(offsetAvailable.toFloat()) },
onScroll = { offsetAvailable: Offset ->
onScroll(offsetAvailable.toFloat()).toOffset()
},
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index b84cb36..aa942e0 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -29,10 +29,10 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.animation.scene.NestedScrollBehavior.Always
import com.android.compose.animation.scene.NestedScrollBehavior.DuringTransitionBetweenScenes
-import com.android.compose.animation.scene.NestedScrollBehavior.EdgeNoOverscroll
-import com.android.compose.animation.scene.NestedScrollBehavior.EdgeWithOverscroll
+import com.android.compose.animation.scene.NestedScrollBehavior.EdgeAlways
+import com.android.compose.animation.scene.NestedScrollBehavior.EdgeNoPreview
+import com.android.compose.animation.scene.NestedScrollBehavior.EdgeWithPreview
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
@@ -191,8 +191,13 @@
runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() }
}
- private fun DraggableHandler.onDragStarted() {
- onDragStarted(layoutSize = LAYOUT_SIZE, startedPosition = Offset.Zero)
+ private fun DraggableHandler.onDragStarted(
+ overSlop: Float = 0f,
+ startedPosition: Offset = Offset.Zero,
+ ) {
+ onDragStarted(startedPosition, overSlop)
+ // MultiPointerDraggable will always call onDelta with the initial overSlop right after
+ onDelta(overSlop)
}
@Test fun testPreconditions() = runGestureTest { assertIdle(currentScene = SceneA) }
@@ -291,6 +296,86 @@
}
@Test
+ fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
+ horizontalSceneGestureHandler.draggable.onDragStarted(up(0.3f))
+ assertIdle(currentScene = SceneA)
+ horizontalSceneGestureHandler.draggable.onDragStarted(down(0.3f))
+ assertIdle(currentScene = SceneA)
+ }
+
+ @Test
+ fun onDragIntoNoAction_startTransitionToOppositeDirection() = runGestureTest {
+ navigateToSceneC()
+
+ // We are on SceneC which has no action in Down direction
+ draggable.onDragStarted(down(0.1f))
+ assertTransition(
+ currentScene = SceneC,
+ fromScene = SceneC,
+ toScene = SceneB,
+ progress = -0.1f
+ )
+
+ // Reverse drag direction, it will consume the previous drag
+ draggable.onDelta(up(0.1f))
+ assertTransition(
+ currentScene = SceneC,
+ fromScene = SceneC,
+ toScene = SceneB,
+ progress = 0.0f
+ )
+
+ // Continue reverse drag direction, it should record progress to Scene B
+ draggable.onDelta(up(0.1f))
+ assertTransition(
+ currentScene = SceneC,
+ fromScene = SceneC,
+ toScene = SceneB,
+ progress = 0.1f
+ )
+ }
+
+ @Test
+ fun onDragFromEdge_startTransitionToEdgeAction() = runGestureTest {
+ navigateToSceneC()
+
+ // Start dragging from the bottom
+ draggable.onDragStarted(up(0.1f), Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE))
+ assertTransition(
+ currentScene = SceneC,
+ fromScene = SceneC,
+ toScene = SceneA,
+ progress = 0.1f
+ )
+ }
+
+ @Test
+ fun onDragToExactlyZero_toSceneIsSet() = runGestureTest {
+ draggable.onDragStarted(down(0.3f))
+ assertTransition(
+ currentScene = SceneA,
+ fromScene = SceneA,
+ toScene = SceneC,
+ progress = 0.3f
+ )
+ draggable.onDelta(up(0.3f))
+ assertTransition(
+ currentScene = SceneA,
+ fromScene = SceneA,
+ toScene = SceneC,
+ progress = 0.0f
+ )
+ }
+
+ private fun TestGestureScope.navigateToSceneC() {
+ assertIdle(currentScene = SceneA)
+ draggable.onDragStarted(down(1f))
+ draggable.onDragStopped(0f)
+ advanceUntilIdle()
+ assertIdle(currentScene = SceneC)
+ }
+
+ @Test
fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
// Drag A -> B with progress 0.2
draggable.onDragStarted()
@@ -339,29 +424,29 @@
)
// The stop animation is not started yet
- assertThat(sceneGestureHandler.isAnimatingOffset).isFalse()
+ assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
runCurrent()
- assertThat(sceneGestureHandler.isAnimatingOffset).isTrue()
+ assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
assertTransition(currentScene = SceneC)
// Start a new gesture while the offset is animating
draggable.onDragStarted()
- assertThat(sceneGestureHandler.isAnimatingOffset).isFalse()
+ assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
}
@Test
fun onInitialPreScroll_EdgeWithOverscroll_doNotChangeState() = runGestureTest {
- val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+ val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
assertIdle(currentScene = SceneA)
}
@Test
fun onPostScrollWithNothingAvailable_EdgeWithOverscroll_doNotChangeState() = runGestureTest {
- val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+ val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
val consumed =
nestedScroll.onPostScroll(
consumed = Offset.Zero,
@@ -375,7 +460,7 @@
@Test
fun onPostScrollWithSomethingAvailable_startSceneTransition() = runGestureTest {
- val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+ val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
val consumed =
nestedScroll.onPostScroll(
consumed = Offset.Zero,
@@ -404,7 +489,7 @@
@Test
fun afterSceneTransitionIsStarted_interceptPreScrollEvents() = runGestureTest {
- val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+ val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
nestedScroll.scroll(available = offsetY10)
assertTransition(currentScene = SceneA)
@@ -432,7 +517,7 @@
firstScroll: Float,
secondScroll: Float
) {
- val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+ val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
// start scene transition
nestedScroll.scroll(available = Offset(0f, SCREEN_SIZE * firstScroll))
@@ -485,7 +570,7 @@
@Test
fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
- val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+ val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
nestedScroll.scroll(available = offsetY10)
assertTransition(currentScene = SceneA)
@@ -517,7 +602,7 @@
@Test
fun flingAfterScroll_EdgeNoOverscroll_goToNextScene() = runGestureTest {
- flingAfterScroll(use = EdgeNoOverscroll, idleAfterScroll = false)
+ flingAfterScroll(use = EdgeNoPreview, idleAfterScroll = false)
assertTransition(currentScene = SceneC)
@@ -528,7 +613,7 @@
@Test
fun flingAfterScroll_EdgeWithOverscroll_goToNextScene() = runGestureTest {
- flingAfterScroll(use = EdgeWithOverscroll, idleAfterScroll = false)
+ flingAfterScroll(use = EdgeWithPreview, idleAfterScroll = false)
assertTransition(currentScene = SceneC)
@@ -539,7 +624,7 @@
@Test
fun flingAfterScroll_Always_goToNextScene() = runGestureTest {
- flingAfterScroll(use = Always, idleAfterScroll = false)
+ flingAfterScroll(use = EdgeAlways, idleAfterScroll = false)
assertTransition(currentScene = SceneC)
@@ -573,14 +658,14 @@
@Test
fun flingAfterScrollStartedInScene_EdgeNoOverscroll_doNothing() = runGestureTest {
- flingAfterScrollStartedInScene(use = EdgeNoOverscroll, idleAfterScroll = true)
+ flingAfterScrollStartedInScene(use = EdgeNoPreview, idleAfterScroll = true)
assertIdle(currentScene = SceneA)
}
@Test
fun flingAfterScrollStartedInScene_EdgeWithOverscroll_doOverscrollAnimation() = runGestureTest {
- flingAfterScrollStartedInScene(use = EdgeWithOverscroll, idleAfterScroll = false)
+ flingAfterScrollStartedInScene(use = EdgeWithPreview, idleAfterScroll = false)
assertTransition(currentScene = SceneA)
@@ -591,7 +676,7 @@
@Test
fun flingAfterScrollStartedInScene_Always_goToNextScene() = runGestureTest {
- flingAfterScrollStartedInScene(use = Always, idleAfterScroll = false)
+ flingAfterScrollStartedInScene(use = EdgeAlways, idleAfterScroll = false)
assertTransition(currentScene = SceneC)
@@ -614,14 +699,14 @@
@Test
fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
- val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+ val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
nestedScroll.onPreFling(Velocity(0f, velocityThreshold))
assertIdle(currentScene = SceneA)
}
@Test
fun startNestedScrollWhileDragging() = runGestureTest {
- val nestedScroll = nestedScrollConnection(nestedScrollBehavior = Always)
+ val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
draggable.onDragStarted()
assertTransition(currentScene = SceneA)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index 97ac8c6..d3049d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -6,6 +6,7 @@
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -38,6 +39,7 @@
private val testUtils = SceneTestUtils(this)
private val testScope = testUtils.testScope
private val userRepository = FakeUserRepository()
+ private val keyguardRepository = FakeKeyguardRepository()
private lateinit var underTest: DeviceEntryRepository
@@ -55,6 +57,7 @@
lockPatternUtils = lockPatternUtils,
keyguardBypassController = keyguardBypassController,
keyguardStateController = keyguardStateController,
+ keyguardRepository = keyguardRepository,
)
testScope.runCurrent()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 8c896a6..1e2784a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -25,11 +25,10 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE
+import com.android.systemui.Flags.FLAG_QS_NEW_TILES
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dump.nano.SystemUIProtoDump
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTile.BooleanState
import com.android.systemui.qs.FakeQSFactory
@@ -81,8 +80,7 @@
private val tileFactory = FakeQSFactory(::tileCreator)
private val customTileAddedRepository: CustomTileAddedRepository =
FakeCustomTileAddedRepository()
- private val featureFlags = FakeFeatureFlags()
- private val pipelineFlags = QSPipelineFlagsRepository(featureFlags)
+ private val pipelineFlags = QSPipelineFlagsRepository()
private val tileLifecycleManagerFactory = TLMFactory()
@Mock private lateinit var customTileStatePersister: CustomTileStatePersister
@@ -100,14 +98,12 @@
private lateinit var underTest: CurrentTilesInteractorImpl
- @OptIn(ExperimentalCoroutinesApi::class)
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE)
- // TODO(b/299909337): Add test checking the new factory is used when the flag is on
- featureFlags.set(Flags.QS_PIPELINE_NEW_TILES, true)
+ mSetFlagsRule.enableFlags(FLAG_QS_NEW_TILES)
userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 24cd9b5..b1a153a 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -66,8 +66,6 @@
public static final int MODE_ON = 1;
public static final int MODE_OFF = 2;
public static final int MODE_ESTIMATE = 3;
- @VisibleForTesting
- public static final long LAYOUT_TRANSITION_DURATION = 200;
private final AccessorizedBatteryDrawable mDrawable;
private final ImageView mBatteryIconView;
@@ -136,7 +134,7 @@
private void setupLayoutTransition() {
LayoutTransition transition = new LayoutTransition();
- transition.setDuration(LAYOUT_TRANSITION_DURATION);
+ transition.setDuration(200);
// Animates appearing/disappearing of the battery percentage text using fade-in/fade-out
// and disables all other animation types
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
index cd764c0..b915418 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -1,6 +1,5 @@
package com.android.systemui.deviceentry
-import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepositoryModule
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import dagger.Module
@@ -10,7 +9,6 @@
includes =
[
DeviceEntryRepositoryModule::class,
- DeviceEntryHapticsRepositoryModule::class,
],
)
abstract class DeviceEntryModule {
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt
deleted file mode 100644
index 1458404..0000000
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.deviceentry.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import dagger.Binds
-import dagger.Module
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Interface for classes that can access device-entry haptics application state. */
-interface DeviceEntryHapticsRepository {
- /**
- * Whether a successful biometric haptic has been requested. Has not yet been handled if true.
- */
- val successHapticRequest: Flow<Boolean>
-
- /** Whether an error biometric haptic has been requested. Has not yet been handled if true. */
- val errorHapticRequest: Flow<Boolean>
-
- fun requestSuccessHaptic()
- fun handleSuccessHaptic()
- fun requestErrorHaptic()
- fun handleErrorHaptic()
-}
-
-/** Encapsulates application state for device entry haptics. */
-@SysUISingleton
-class DeviceEntryHapticsRepositoryImpl @Inject constructor() : DeviceEntryHapticsRepository {
- private val _successHapticRequest = MutableStateFlow(false)
- override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow()
-
- private val _errorHapticRequest = MutableStateFlow(false)
- override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow()
-
- override fun requestSuccessHaptic() {
- _successHapticRequest.value = true
- }
-
- override fun handleSuccessHaptic() {
- _successHapticRequest.value = false
- }
-
- override fun requestErrorHaptic() {
- _errorHapticRequest.value = true
- }
-
- override fun handleErrorHaptic() {
- _errorHapticRequest.value = false
- }
-}
-
-@Module
-interface DeviceEntryHapticsRepositoryModule {
- @Binds fun repository(impl: DeviceEntryHapticsRepositoryImpl): DeviceEntryHapticsRepository
-}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index f27bbe6..08e8c2d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -7,26 +7,36 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.sample
import dagger.Binds
import dagger.Module
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.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
/** Interface for classes that can access device-entry-related application state. */
interface DeviceEntryRepository {
+ /** Whether the device is immediately entering the device after a biometric unlock. */
+ val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource>
+
/**
* Whether the device is unlocked.
*
@@ -73,7 +83,14 @@
private val lockPatternUtils: LockPatternUtils,
private val keyguardBypassController: KeyguardBypassController,
keyguardStateController: KeyguardStateController,
+ keyguardRepository: KeyguardRepository,
) : DeviceEntryRepository {
+ override val enteringDeviceFromBiometricUnlock =
+ keyguardRepository.biometricUnlockState
+ .filter { BiometricUnlockModel.dismissesKeyguard(it) }
+ .sample(
+ keyguardRepository.biometricUnlockSource.filterNotNull(),
+ )
private val _isUnlocked = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
new file mode 100644
index 0000000..1a6bd04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.shared.DeviceEntryBiometricMode
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+
+/** Business logic for device entry biometric states that may differ based on the biometric mode. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class DeviceEntryBiometricAuthInteractor
+@Inject
+constructor(
+ biometricSettingsRepository: BiometricSettingsRepository,
+ deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
+) {
+ private val biometricMode: Flow<DeviceEntryBiometricMode> =
+ combine(
+ biometricSettingsRepository.isFingerprintEnrolledAndEnabled,
+ biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
+ ) { fingerprintEnrolled, faceEnrolled ->
+ if (fingerprintEnrolled && faceEnrolled) {
+ DeviceEntryBiometricMode.CO_EXPERIENCE
+ } else if (fingerprintEnrolled) {
+ DeviceEntryBiometricMode.FINGERPRINT_ONLY
+ } else if (faceEnrolled) {
+ DeviceEntryBiometricMode.FACE_ONLY
+ } else {
+ DeviceEntryBiometricMode.NONE
+ }
+ }
+ private val faceOnly: Flow<Boolean> =
+ biometricMode.map { it == DeviceEntryBiometricMode.FACE_ONLY }
+
+ /**
+ * Triggered if face is the only biometric that can be used for device entry and a face failure
+ * occurs.
+ */
+ val faceOnlyFaceFailure: Flow<FailedFaceAuthenticationStatus> =
+ faceOnly.flatMapLatest { faceOnly ->
+ if (faceOnly) {
+ deviceEntryFaceAuthInteractor.faceFailure
+ } else {
+ emptyFlow()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
new file mode 100644
index 0000000..70716c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterIsInstance
+
+@SysUISingleton
+class DeviceEntryFaceAuthInteractor
+@Inject
+constructor(
+ repository: DeviceEntryFaceAuthRepository,
+) {
+ val faceFailure: Flow<FailedFaceAuthenticationStatus> =
+ repository.authenticationStatus.filterIsInstance<FailedFaceAuthenticationStatus>()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
new file mode 100644
index 0000000..efa1c0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterIsInstance
+
+@SysUISingleton
+class DeviceEntryFingerprintAuthInteractor
+@Inject
+constructor(
+ repository: DeviceEntryFingerprintAuthRepository,
+) {
+ val fingerprintFailure: Flow<FailFingerprintAuthenticationStatus> =
+ repository.authenticationStatus.filterIsInstance<FailFingerprintAuthenticationStatus>()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
index 53d6f73..649a971 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -19,7 +19,6 @@
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepository
import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -34,11 +33,12 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
/**
* Business logic for device entry haptic events. Determines whether the haptic should play. In
- * particular, there are extra guards for whether device entry error and successes hatpics should
+ * particular, there are extra guards for whether device entry error and successes haptics should
* play when the physical fingerprint sensor is located on the power button.
*/
@ExperimentalCoroutinesApi
@@ -46,7 +46,9 @@
class DeviceEntryHapticsInteractor
@Inject
constructor(
- private val repository: DeviceEntryHapticsRepository,
+ deviceEntryInteractor: DeviceEntryInteractor,
+ deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
+ deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor,
fingerprintPropertyRepository: FingerprintPropertyRepository,
biometricSettingsRepository: BiometricSettingsRepository,
keyEventInteractor: KeyEventInteractor,
@@ -77,9 +79,8 @@
emit(recentPowerButtonPressThresholdMs * -1L - 1L)
}
- val playSuccessHaptic: Flow<Boolean> =
- repository.successHapticRequest
- .filter { it }
+ val playSuccessHaptic: Flow<Unit> =
+ deviceEntryInteractor.enteringDeviceFromBiometricUnlock
.sample(
combine(
powerButtonSideFpsEnrolled,
@@ -88,7 +89,7 @@
::Triple
)
)
- .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
+ .filter { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
val sideFpsAllowsHaptic =
!powerButtonDown &&
systemClock.uptimeMillis() - lastPowerButtonWakeup >
@@ -96,38 +97,28 @@
val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic
if (!allowHaptic) {
logger.d("Skip success haptic. Recent power button press or button is down.")
- handleSuccessHaptic() // immediately handle, don't vibrate
}
allowHaptic
}
- val playErrorHaptic: Flow<Boolean> =
- repository.errorHapticRequest
- .filter { it }
+ .map {} // map to Unit
+
+ private val playErrorHapticForBiometricFailure: Flow<Unit> =
+ merge(
+ deviceEntryFingerprintAuthInteractor.fingerprintFailure,
+ deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure,
+ )
+ .map {} // map to Unit
+ val playErrorHaptic: Flow<Unit> =
+ playErrorHapticForBiometricFailure
.sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair))
- .map { (sideFpsEnrolled, powerButtonDown) ->
+ .filter { (sideFpsEnrolled, powerButtonDown) ->
val allowHaptic = !sideFpsEnrolled || !powerButtonDown
if (!allowHaptic) {
logger.d("Skip error haptic. Power button is down.")
- handleErrorHaptic() // immediately handle, don't vibrate
}
allowHaptic
}
-
- fun vibrateSuccess() {
- repository.requestSuccessHaptic()
- }
-
- fun vibrateError() {
- repository.requestErrorHaptic()
- }
-
- fun handleSuccessHaptic() {
- repository.handleSuccessHaptic()
- }
-
- fun handleErrorHaptic() {
- repository.handleErrorHaptic()
- }
+ .map {} // map to Unit
private val recentPowerButtonPressThresholdMs = 400L
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 4cddb9c..47be8ab 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -23,6 +23,7 @@
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
@@ -30,6 +31,7 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
@@ -60,6 +62,9 @@
trustRepository: TrustRepository,
flags: SceneContainerFlags,
) {
+ val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> =
+ repository.enteringDeviceFromBiometricUnlock
+
/**
* Whether the device is unlocked.
*
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryBiometricMode.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryBiometricMode.kt
new file mode 100644
index 0000000..6d885b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryBiometricMode.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.deviceentry.shared
+
+/** Models the biometrics that can be used to enter the device. */
+enum class DeviceEntryBiometricMode {
+ /** No biometrics can be used to enter the device from the lockscreen. */
+ NONE,
+ /** Only face can be used to enter the device from the lockscreen. */
+ FACE_ONLY,
+ /** Only fingerprint can be used to enter the device from the lockscreen. */
+ FINGERPRINT_ONLY,
+ /** Both face and fingerprint can be used to enter the device from the lockscreen. */
+ CO_EXPERIENCE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e8ceabf..5a763b1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -278,10 +278,6 @@
"qs_user_detail_shortcut"
)
- // TODO(b/296357483): Tracking Bug
- @JvmField
- val QS_PIPELINE_NEW_TILES = unreleasedFlag("qs_pipeline_new_tiles")
-
// TODO(b/254512383): Tracking Bug
@JvmField
val FULL_SCREEN_USER_SWITCHER =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
index 8fe6309f..2ae5ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
@@ -51,9 +51,21 @@
companion object {
private val wakeAndUnlockModes =
setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
+ private val dismissesKeyguardModes =
+ setOf(
+ WAKE_AND_UNLOCK,
+ WAKE_AND_UNLOCK_PULSING,
+ UNLOCK_COLLAPSING,
+ WAKE_AND_UNLOCK_FROM_DREAM,
+ DISMISS_BOUNCER
+ )
fun isWakeAndUnlock(model: BiometricUnlockModel): Boolean {
return wakeAndUnlockModes.contains(model)
}
+
+ fun dismissesKeyguard(model: BiometricUnlockModel): Boolean {
+ return dismissesKeyguardModes.contains(model)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
index 3c143fe..cc385a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -49,7 +49,7 @@
}
/** Fingerprint authentication failed message. */
-object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
+data object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
/** Fingerprint authentication error message */
data class ErrorFingerprintAuthenticationStatus(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index 4b4a19e..dcf4284 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -19,6 +19,7 @@
import android.annotation.SuppressLint
import android.content.res.ColorStateList
+import android.view.HapticFeedbackConstants
import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -30,6 +31,7 @@
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
@@ -51,6 +53,7 @@
fgViewModel: DeviceEntryForegroundViewModel,
bgViewModel: DeviceEntryBackgroundViewModel,
falsingManager: FalsingManager,
+ vibratorHelper: VibratorHelper,
) {
DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()
val longPressHandlingView = view.longPressHandlingView
@@ -62,6 +65,10 @@
if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
return
}
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.CONFIRM,
+ )
viewModel.onLongPress()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index e603ead..01a1ca3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -68,7 +68,6 @@
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -253,27 +252,21 @@
if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
launch {
- deviceEntryHapticsInteractor.playSuccessHaptic
- .filter { it }
- .collect {
- vibratorHelper.performHapticFeedback(
- view,
- HapticFeedbackConstants.CONFIRM,
- )
- deviceEntryHapticsInteractor.handleSuccessHaptic()
- }
+ deviceEntryHapticsInteractor.playSuccessHaptic.collect {
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.CONFIRM,
+ )
+ }
}
launch {
- deviceEntryHapticsInteractor.playErrorHaptic
- .filter { it }
- .collect {
- vibratorHelper.performHapticFeedback(
- view,
- HapticFeedbackConstants.REJECT,
- )
- deviceEntryHapticsInteractor.handleErrorHaptic()
- }
+ deviceEntryHapticsInteractor.playErrorHaptic.collect {
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.REJECT,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 77ab9f4..a693ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -49,6 +49,7 @@
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.gesture.TapGestureDetector
import dagger.Lazy
import javax.inject.Inject
@@ -76,6 +77,7 @@
@Application private val scope: CoroutineScope,
private val swipeUpAnywhereGestureHandler: Lazy<SwipeUpAnywhereGestureHandler>,
private val tapGestureDetector: Lazy<TapGestureDetector>,
+ private val vibratorHelper: Lazy<VibratorHelper>,
) : KeyguardSection() {
private val deviceEntryIconViewId = R.id.device_entry_icon_view
private val alternateBouncerViewId = R.id.alternate_bouncer
@@ -114,6 +116,7 @@
deviceEntryForegroundViewModel.get(),
deviceEntryBackgroundViewModel.get(),
falsingManager.get(),
+ vibratorHelper.get(),
)
}
constraintLayout.findViewById<FrameLayout?>(alternateBouncerViewId)?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index bd6aae8..f95713b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -19,7 +19,6 @@
import android.animation.FloatEvaluator
import android.animation.IntEvaluator
import com.android.keyguard.KeyguardViewController
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
@@ -56,7 +55,6 @@
val shadeDependentFlows: ShadeDependentFlows,
private val sceneContainerFlags: SceneContainerFlags,
private val keyguardViewController: Lazy<KeyguardViewController>,
- private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
private val deviceEntryInteractor: DeviceEntryInteractor,
) {
private val intEvaluator = IntEvaluator()
@@ -182,8 +180,6 @@
}
fun onLongPress() {
- deviceEntryHapticsInteractor.vibrateSuccess()
-
// TODO (b/309804148): play auth ripple via an interactor
if (sceneContainerFlags.isEnabled()) {
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 dc55179f..d8bb3e6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -188,7 +188,7 @@
LogBufferFactory factory,
QSPipelineFlagsRepository flags
) {
- if (flags.getPipelineTilesEnabled()) {
+ if (flags.getTilesEnabled()) {
// we use
return factory.create("QSLog", 450 /* maxSize */, false /* systrace */);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 1fab58e..828d6ed 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -57,6 +57,8 @@
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.settings.SecureSettings;
+import dagger.Lazy;
+
import org.jetbrains.annotations.NotNull;
import java.io.PrintWriter;
@@ -73,8 +75,6 @@
import javax.inject.Inject;
import javax.inject.Provider;
-import dagger.Lazy;
-
/** Platform implementation of the quick settings tile host
*
* This class keeps track of the set of current tiles and is the in memory source of truth
@@ -151,7 +151,7 @@
mShadeController = shadeController;
- if (featureFlags.getPipelineTilesEnabled()) {
+ if (featureFlags.getTilesEnabled()) {
mQsFactories.add(newQsTileFactoryProvider.get());
}
mQsFactories.add(defaultFactory);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 4bda730..5d28c8c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -336,7 +336,7 @@
private suspend fun createTile(spec: TileSpec): QSTile? {
val tile =
withContext(mainDispatcher) {
- if (featureFlags.pipelineTilesEnabled) {
+ if (featureFlags.tilesEnabled) {
newQSTileFactory.get().createTile(spec.spec)
} else {
null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
index 5c7420c..935d072 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
@@ -2,24 +2,18 @@
import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.flags.RefactorFlagUtils
import javax.inject.Inject
/** Encapsulate the different QS pipeline flags and their dependencies */
@SysUISingleton
-class QSPipelineFlagsRepository
-@Inject
-constructor(
- private val featureFlags: FeatureFlagsClassic,
-) {
+class QSPipelineFlagsRepository @Inject constructor() {
+
val pipelineEnabled: Boolean
get() = AconfigFlags.qsNewPipeline()
- /** @see Flags.QS_PIPELINE_NEW_TILES */
- val pipelineTilesEnabled: Boolean
- get() = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_TILES)
+ val tilesEnabled: Boolean
+ get() = AconfigFlags.qsNewTiles()
companion object Utils {
fun assertInLegacyMode() =
@@ -27,5 +21,11 @@
AconfigFlags.qsNewPipeline(),
AconfigFlags.FLAG_QS_NEW_PIPELINE
)
+
+ fun assertNewTilesInLegacyMode() =
+ RefactorFlagUtils.assertInLegacyMode(
+ AconfigFlags.qsNewTiles(),
+ AconfigFlags.FLAG_QS_NEW_TILES
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
index 27007bb..52e49f9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
@@ -19,6 +19,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.qs.QSFactory
import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent
@@ -44,6 +45,7 @@
) : QSFactory {
init {
+ QSPipelineFlagsRepository.assertNewTilesInLegacyMode()
for (viewModelTileSpec in tileMap.keys) {
require(qsTileConfigProvider.hasConfig(viewModelTileSpec)) {
"No config for $viewModelTileSpec"
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index c885492..c43d20c 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -35,6 +35,8 @@
import com.android.systemui.Gefingerpoken;
import com.android.systemui.res.R;
+import java.util.Collections;
+
/**
* {@code FrameLayout} used to show and manipulate a {@link ToggleSeekBar}.
*
@@ -48,6 +50,7 @@
@Nullable
private Drawable mProgressDrawable;
private float mScale = 1f;
+ private final Rect mSystemGestureExclusionRect = new Rect();
public BrightnessSliderView(Context context) {
this(context, null);
@@ -176,6 +179,11 @@
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
applySliderScale();
+ int horizontalMargin =
+ getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
+ mSystemGestureExclusionRect.set(-horizontalMargin, 0, right - left + horizontalMargin,
+ bottom - top);
+ setSystemGestureExclusionRects(Collections.singletonList(mSystemGestureExclusionRect));
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index dd194eaa..8397caa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -794,13 +794,6 @@
/** update Qs height state */
public void setExpansionHeight(float height) {
- // TODO(b/277909752): remove below log when bug is fixed
- if (mSplitShadeEnabled && mShadeExpandedFraction == 1.0f && height == 0
- && mBarState == SHADE) {
- Log.wtf(TAG,
- "setting QS height to 0 in split shade while shade is open(ing). "
- + "Value of isExpandImmediate() = " + isExpandImmediate());
- }
int maxHeight = getMaxExpansionHeight();
height = Math.min(Math.max(
height, getMinExpansionHeight()), maxHeight);
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 4e77801..97fc35a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -47,7 +47,6 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -180,8 +179,6 @@
private final SystemClock mSystemClock;
private final boolean mOrderUnlockAndWake;
private final Lazy<SelectedUserInteractor> mSelectedUserInteractor;
- private final DeviceEntryHapticsInteractor mHapticsInteractor;
-
private long mLastFpFailureUptimeMillis;
private int mNumConsecutiveFpFailures;
@@ -288,7 +285,6 @@
ScreenOffAnimationController screenOffAnimationController,
VibratorHelper vibrator,
SystemClock systemClock,
- DeviceEntryHapticsInteractor hapticsInteractor,
Lazy<SelectedUserInteractor> selectedUserInteractor,
BiometricUnlockInteractor biometricUnlockInteractor
) {
@@ -320,7 +316,6 @@
mSystemClock = systemClock;
mOrderUnlockAndWake = resources.getBoolean(
com.android.internal.R.bool.config_orderUnlockAndWake);
- mHapticsInteractor = hapticsInteractor;
mSelectedUserInteractor = selectedUserInteractor;
dumpManager.registerDumpable(this);
@@ -442,7 +437,6 @@
if (mode == MODE_WAKE_AND_UNLOCK
|| mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING
|| mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) {
- mHapticsInteractor.vibrateSuccess();
onBiometricUnlockedWithKeyguardDismissal(biometricSourceType);
}
startWakeAndUnlock(mode);
@@ -726,14 +720,6 @@
}
}
- // Suppress all face auth errors if fingerprint can be used to authenticate
- if ((biometricSourceType == BiometricSourceType.FACE
- && !mUpdateMonitor.isUnlockWithFingerprintPossible(
- mSelectedUserInteractor.get().getSelectedUserId()))
- || (biometricSourceType == BiometricSourceType.FINGERPRINT)) {
- mHapticsInteractor.vibrateError();
- }
-
cleanup();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
index 9b8e581..59bcf01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
@@ -18,157 +18,165 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.keyguard.logging.BiometricUnlockLogger
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
-import com.android.systemui.keyevent.data.repository.FakeKeyEventRepository
-import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
+import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.powerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.testKosmos
+import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
-
- private lateinit var repository: DeviceEntryHapticsRepository
- private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
- private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
- private lateinit var keyEventRepository: FakeKeyEventRepository
- private lateinit var powerRepository: FakePowerRepository
- private lateinit var systemClock: FakeSystemClock
- private lateinit var underTest: DeviceEntryHapticsInteractor
-
- @Before
- fun setUp() {
- repository = DeviceEntryHapticsRepositoryImpl()
- fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
- biometricSettingsRepository = FakeBiometricSettingsRepository()
- keyEventRepository = FakeKeyEventRepository()
- powerRepository = FakePowerRepository()
- systemClock = FakeSystemClock()
- underTest =
- DeviceEntryHapticsInteractor(
- repository = repository,
- fingerprintPropertyRepository = fingerprintPropertyRepository,
- biometricSettingsRepository = biometricSettingsRepository,
- keyEventInteractor = KeyEventInteractor(keyEventRepository),
- powerInteractor =
- PowerInteractor(
- powerRepository,
- mock(FalsingCollector::class.java),
- mock(ScreenOffAnimationController::class.java),
- mock(StatusBarStateController::class.java),
- ),
- systemClock = systemClock,
- logger = mock(BiometricUnlockLogger::class.java),
- )
- }
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.deviceEntryHapticsInteractor
@Test
- fun nonPowerButtonFPS_vibrateSuccess() = runTest {
- val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
- setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
- underTest.vibrateSuccess()
- assertThat(playSuccessHaptic).isTrue()
- }
+ fun nonPowerButtonFPS_vibrateSuccess() =
+ testScope.runTest {
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+ runCurrent()
+ enterDeviceFromBiometricUnlock()
+ assertThat(playSuccessHaptic).isNotNull()
+ }
@Test
- fun powerButtonFPS_vibrateSuccess() = runTest {
- val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
- setPowerButtonFingerprintProperty()
- setFingerprintEnrolled()
- keyEventRepository.setPowerButtonDown(false)
+ fun powerButtonFPS_vibrateSuccess() =
+ testScope.runTest {
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ setPowerButtonFingerprintProperty()
+ setFingerprintEnrolled()
+ kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
- // It's been 10 seconds since the last power button wakeup
- setAwakeFromPowerButton()
- runCurrent()
- systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000)
+ // It's been 10 seconds since the last power button wakeup
+ setAwakeFromPowerButton()
+ runCurrent()
+ kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 10000)
- underTest.vibrateSuccess()
- assertThat(playSuccessHaptic).isTrue()
- }
+ enterDeviceFromBiometricUnlock()
+ assertThat(playSuccessHaptic).isNotNull()
+ }
@Test
- fun powerButtonFPS_powerDown_doNotVibrateSuccess() = runTest {
- val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
- setPowerButtonFingerprintProperty()
- setFingerprintEnrolled()
- keyEventRepository.setPowerButtonDown(true) // power button is currently DOWN
+ fun powerButtonFPS_powerDown_doNotVibrateSuccess() =
+ testScope.runTest {
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ setPowerButtonFingerprintProperty()
+ setFingerprintEnrolled()
+ kosmos.fakeKeyEventRepository.setPowerButtonDown(true) // power button is currently DOWN
- // It's been 10 seconds since the last power button wakeup
- setAwakeFromPowerButton()
- runCurrent()
- systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000)
+ // It's been 10 seconds since the last power button wakeup
+ setAwakeFromPowerButton()
+ runCurrent()
+ kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 10000)
- underTest.vibrateSuccess()
- assertThat(playSuccessHaptic).isFalse()
- }
+ enterDeviceFromBiometricUnlock()
+ assertThat(playSuccessHaptic).isNull()
+ }
@Test
- fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = runTest {
- val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
- setPowerButtonFingerprintProperty()
- setFingerprintEnrolled()
- keyEventRepository.setPowerButtonDown(false)
+ fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() =
+ testScope.runTest {
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ setPowerButtonFingerprintProperty()
+ setFingerprintEnrolled()
+ kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
- // It's only been 50ms since the last power button wakeup
- setAwakeFromPowerButton()
- runCurrent()
- systemClock.setUptimeMillis(systemClock.uptimeMillis() + 50)
+ // It's only been 50ms since the last power button wakeup
+ setAwakeFromPowerButton()
+ runCurrent()
+ kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 50)
- underTest.vibrateSuccess()
- assertThat(playSuccessHaptic).isFalse()
- }
+ enterDeviceFromBiometricUnlock()
+ assertThat(playSuccessHaptic).isNull()
+ }
@Test
- fun nonPowerButtonFPS_vibrateError() = runTest {
- val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
- setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
- underTest.vibrateError()
- assertThat(playErrorHaptic).isTrue()
- }
+ fun nonPowerButtonFPS_vibrateError() =
+ testScope.runTest {
+ val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+ setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+ runCurrent()
+ fingerprintFailure()
+ assertThat(playErrorHaptic).isNotNull()
+ }
@Test
- fun powerButtonFPS_vibrateError() = runTest {
- val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
- setPowerButtonFingerprintProperty()
- setFingerprintEnrolled()
- underTest.vibrateError()
- assertThat(playErrorHaptic).isTrue()
- }
+ fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() =
+ testScope.runTest {
+ val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+ setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+ coExEnrolledAndEnabled()
+ runCurrent()
+ faceFailure()
+ assertThat(playErrorHaptic).isNull()
+ }
@Test
- fun powerButtonFPS_powerDown_doNotVibrateError() = runTest {
- val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
- setPowerButtonFingerprintProperty()
- setFingerprintEnrolled()
- keyEventRepository.setPowerButtonDown(true)
- underTest.vibrateError()
- assertThat(playErrorHaptic).isFalse()
+ fun powerButtonFPS_vibrateError() =
+ testScope.runTest {
+ val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+ setPowerButtonFingerprintProperty()
+ setFingerprintEnrolled()
+ runCurrent()
+ fingerprintFailure()
+ assertThat(playErrorHaptic).isNotNull()
+ }
+
+ @Test
+ fun powerButtonFPS_powerDown_doNotVibrateError() =
+ testScope.runTest {
+ val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+ setPowerButtonFingerprintProperty()
+ setFingerprintEnrolled()
+ kosmos.fakeKeyEventRepository.setPowerButtonDown(true)
+ runCurrent()
+ fingerprintFailure()
+ assertThat(playErrorHaptic).isNull()
+ }
+
+ private suspend fun enterDeviceFromBiometricUnlock() {
+ kosmos.fakeDeviceEntryRepository.enteringDeviceFromBiometricUnlock(
+ BiometricUnlockSource.FINGERPRINT_SENSOR
+ )
+ }
+
+ private fun fingerprintFailure() {
+ kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ FailFingerprintAuthenticationStatus
+ )
+ }
+
+ private fun faceFailure() {
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+ FailedFaceAuthenticationStatus()
+ )
}
private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) {
- fingerprintPropertyRepository.setProperties(
+ kosmos.fingerprintPropertyRepository.setProperties(
sensorId = 0,
strength = SensorStrength.STRONG,
sensorType = fingerprintSensorType,
@@ -181,15 +189,20 @@
}
private fun setFingerprintEnrolled() {
- biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
}
private fun setAwakeFromPowerButton() {
- powerRepository.updateWakefulness(
+ kosmos.powerRepository.updateWakefulness(
WakefulnessState.AWAKE,
WakeSleepReason.POWER_BUTTON,
WakeSleepReason.POWER_BUTTON,
powerButtonLaunchGestureTriggered = false,
)
}
+
+ private fun coExEnrolledAndEnabled() {
+ setFingerprintEnrolled()
+ kosmos.biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 67fba42..22569e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.gesture.TapGestureDetector
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -91,6 +92,7 @@
TestScope().backgroundScope,
{ mock(SwipeUpAnywhereGestureHandler::class.java) },
{ mock(TapGestureDetector::class.java) },
+ { mock(VibratorHelper::class.java) },
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 5245b22..5e2423a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -18,6 +18,7 @@
import static com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE;
+import static com.android.systemui.Flags.FLAG_QS_NEW_TILES;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -53,7 +54,6 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.nano.SystemUIProtoDump;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QSFactory;
@@ -79,6 +79,8 @@
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
+import dagger.Lazy;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -93,8 +95,6 @@
import javax.inject.Provider;
-import dagger.Lazy;
-
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class QSTileHostTest extends SysuiTestCase {
@@ -147,9 +147,8 @@
mFeatureFlags = new FakeFeatureFlags();
mSetFlagsRule.disableFlags(FLAG_QS_NEW_PIPELINE);
- // TODO(b/299909337): Add test checking the new factory is used when the flag is on
- mFeatureFlags.set(Flags.QS_PIPELINE_NEW_TILES, false);
- mQSPipelineFlagsRepository = new QSPipelineFlagsRepository(mFeatureFlags);
+ mSetFlagsRule.disableFlags(FLAG_QS_NEW_TILES);
+ mQSPipelineFlagsRepository = new QSPipelineFlagsRepository();
mMainExecutor = new FakeExecutor(new FakeSystemClock());
@@ -704,7 +703,7 @@
TileLifecycleManager.Factory tileLifecycleManagerFactory,
UserFileManager userFileManager, QSPipelineFlagsRepository featureFlags) {
super(context, newQSTileFactoryProvider, defaultFactory, mainExecutor, pluginManager,
- tunerService, autoTiles, shadeController, qsLogger,
+ tunerService, autoTiles, shadeController, qsLogger,
userTracker, secureSettings, customTileStatePersister,
tileLifecycleManagerFactory, userFileManager, featureFlags);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
index 2e63708..970cd17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
@@ -4,7 +4,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -13,9 +12,7 @@
@RunWith(AndroidJUnit4::class)
class QSPipelineFlagsRepositoryTest : SysuiTestCase() {
- private val fakeFeatureFlagsClassic = FakeFeatureFlagsClassic()
-
- private val underTest = QSPipelineFlagsRepository(fakeFeatureFlagsClassic)
+ private val underTest = QSPipelineFlagsRepository()
@Test
fun pipelineFlagDisabled() {
@@ -30,4 +27,18 @@
assertThat(underTest.pipelineEnabled).isTrue()
}
+
+ @Test
+ fun tilesFlagDisabled() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_QS_NEW_TILES)
+
+ assertThat(underTest.tilesEnabled).isFalse()
+ }
+
+ @Test
+ fun tilesFlagEnabled() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_TILES)
+
+ assertThat(underTest.tilesEnabled).isTrue()
+ }
}
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 051a4c1..6f65eb4 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
@@ -47,7 +47,6 @@
import com.android.keyguard.logging.BiometricUnlockLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -122,8 +121,6 @@
@Mock
private ViewRootImpl mViewRootImpl;
@Mock
- private DeviceEntryHapticsInteractor mDeviceEntryHapticsInteractor;
- @Mock
private SelectedUserInteractor mSelectedUserInteractor;
@Mock
private BiometricUnlockInteractor mBiometricUnlockInteractor;
@@ -160,7 +157,6 @@
mAuthController, mStatusBarStateController,
mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,
mSystemClock,
- mDeviceEntryHapticsInteractor,
() -> mSelectedUserInteractor,
mBiometricUnlockInteractor
);
@@ -466,26 +462,6 @@
}
@Test
- public void onFingerprintSuccess_requestSuccessHaptic() {
- // WHEN biometric fingerprint succeeds
- givenFingerprintModeUnlockCollapsing();
- mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
- true);
-
- // THEN always vibrate the device
- verify(mDeviceEntryHapticsInteractor).vibrateSuccess();
- }
-
- @Test
- public void onFingerprintFail_requestErrorHaptic() {
- // WHEN biometric fingerprint fails
- mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
-
- // THEN always vibrate the device
- verify(mDeviceEntryHapticsInteractor).vibrateError();
- }
-
- @Test
public void onFingerprintDetect_showBouncer() {
// WHEN fingerprint detect occurs
mBiometricUnlockController.onBiometricDetected(UserHandle.USER_CURRENT,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
index d97cb56..8ff04a63 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
@@ -16,7 +16,6 @@
package com.android.systemui.deviceentry.data
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepositoryModule
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryHapticsRepositoryModule
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositoryModule
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepositoryModule
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepositoryModule
@@ -29,7 +28,6 @@
[
FakeBiometricSettingsRepositoryModule::class,
FakeDeviceEntryRepositoryModule::class,
- FakeDeviceEntryHapticsRepositoryModule::class,
FakeDeviceEntryFaceAuthRepositoryModule::class,
FakeDeviceEntryFingerprintAuthRepositoryModule::class,
FakeFingerprintPropertyRepositoryModule::class,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryHapticsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryHapticsRepository.kt
deleted file mode 100644
index b29b67d..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryHapticsRepository.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.deviceentry.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import dagger.Binds
-import dagger.Module
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Fake implementation of [DeviceEntryHapticsRepository] */
-@SysUISingleton
-class FakeDeviceEntryHapticsRepository @Inject constructor() : DeviceEntryHapticsRepository {
- private var _successHapticRequest: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow()
-
- private var _errorHapticRequest: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow()
-
- override fun requestSuccessHaptic() {
- _successHapticRequest.value = true
- }
-
- override fun handleSuccessHaptic() {
- _successHapticRequest.value = false
- }
-
- override fun requestErrorHaptic() {
- _errorHapticRequest.value = true
- }
-
- override fun handleErrorHaptic() {
- _errorHapticRequest.value = false
- }
-}
-
-@Module
-interface FakeDeviceEntryHapticsRepositoryModule {
- @Binds fun bindFake(fake: FakeDeviceEntryHapticsRepository): DeviceEntryHapticsRepository
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index ba70d46..6436a38 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -16,16 +16,24 @@
package com.android.systemui.deviceentry.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import dagger.Binds
import dagger.Module
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
/** Fake implementation of [DeviceEntryRepository] */
@SysUISingleton
class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository {
+ private val _enteringDeviceFromBiometricUnlock: MutableSharedFlow<BiometricUnlockSource> =
+ MutableSharedFlow()
+ override val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> =
+ _enteringDeviceFromBiometricUnlock.asSharedFlow()
private var isLockscreenEnabled = true
@@ -54,6 +62,10 @@
fun setBypassEnabled(isBypassEnabled: Boolean) {
_isBypassEnabled.value = isBypassEnabled
}
+
+ suspend fun enteringDeviceFromBiometricUnlock(sourceType: BiometricUnlockSource) {
+ _enteringDeviceFromBiometricUnlock.emit(sourceType)
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt
new file mode 100644
index 0000000..1bd1056
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.deviceEntryBiometricAuthInteractor by
+ Kosmos.Fixture {
+ DeviceEntryBiometricAuthInteractor(
+ biometricSettingsRepository = biometricSettingsRepository,
+ deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
similarity index 61%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index 8bb07d9..d2dff78 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -14,10 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.deviceentry.data.repository
+@file:OptIn(ExperimentalCoroutinesApi::class)
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
-var Kosmos.deviceEntryHapticsRepository: DeviceEntryHapticsRepository by
- Kosmos.Fixture { fakeDeviceEntryHapticsRepository }
-val Kosmos.fakeDeviceEntryHapticsRepository by Kosmos.Fixture { FakeDeviceEntryHapticsRepository() }
+val Kosmos.deviceEntryFaceAuthInteractor by
+ Kosmos.Fixture {
+ DeviceEntryFaceAuthInteractor(
+ repository = deviceEntryFaceAuthRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
new file mode 100644
index 0000000..66c6f86
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.deviceEntryFingerprintAuthInteractor by
+ Kosmos.Fixture {
+ DeviceEntryFingerprintAuthInteractor(
+ repository = deviceEntryFingerprintAuthRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
index de6cacb..6bf527d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
@@ -20,7 +20,6 @@
import com.android.keyguard.logging.biometricUnlockLogger
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryHapticsRepository
import com.android.systemui.keyevent.domain.interactor.keyEventInteractor
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.kosmos.Kosmos
@@ -31,7 +30,9 @@
val Kosmos.deviceEntryHapticsInteractor by
Kosmos.Fixture {
DeviceEntryHapticsInteractor(
- repository = fakeDeviceEntryHapticsRepository,
+ deviceEntryInteractor = deviceEntryInteractor,
+ deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+ deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor,
fingerprintPropertyRepository = fingerprintPropertyRepository,
biometricSettingsRepository = biometricSettingsRepository,
keyEventInteractor = keyEventInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 299262b..6557bcf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -16,7 +16,6 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.burnInInteractor
@@ -45,7 +44,6 @@
shadeDependentFlows = shadeDependentFlows,
sceneContainerFlags = sceneContainerFlags,
keyguardViewController = { statusBarKeyguardViewManager },
- deviceEntryHapticsInteractor = deviceEntryHapticsInteractor,
deviceEntryInteractor = deviceEntryInteractor,
)
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 45d7314..13c7924 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -104,6 +104,7 @@
import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
import com.android.server.companion.virtual.audio.VirtualAudioController;
import com.android.server.companion.virtual.camera.VirtualCameraController;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -352,6 +353,14 @@
flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
}
mBaseVirtualDisplayFlags = flags;
+
+ if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) {
+ final String imeId = mParams.getInputMethodComponent().flattenToShortString();
+ Slog.d(TAG, "Setting custom input method " + imeId + " as default for virtual device "
+ + deviceId);
+ InputMethodManagerInternal.get().setVirtualDeviceInputMethodForAllUsers(
+ mDeviceId, imeId);
+ }
}
@VisibleForTesting
@@ -556,6 +565,12 @@
mCameraAccessController.stopObservingIfNeeded();
}
+ // Clear any previously set custom IME components.
+ if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) {
+ InputMethodManagerInternal.get().setVirtualDeviceInputMethodForAllUsers(
+ mDeviceId, null);
+ }
+
mInputController.close();
mSensorController.close();
} finally {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 9b78ed4..923728f 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -859,6 +859,11 @@
}
@Override
+ public boolean isValidVirtualDeviceId(int deviceId) {
+ return mImpl.isValidVirtualDeviceId(deviceId);
+ }
+
+ @Override
public @Nullable String getPersistentIdForDevice(int deviceId) {
if (deviceId == Context.DEVICE_ID_DEFAULT) {
return VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e54bf64..d077ebc 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1501,7 +1501,7 @@
} else {
status = addOp.deviceRoleAction(useCase, role, devices);
if (status == AudioSystem.SUCCESS) {
- rolesMap.put(key, devices);
+ rolesMap.put(key, new ArrayList(devices));
}
}
return status;
diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java
index 394e4af..e012d17 100644
--- a/services/core/java/com/android/server/audio/RotationHelper.java
+++ b/services/core/java/com/android/server/audio/RotationHelper.java
@@ -80,6 +80,7 @@
sContext = context;
sHandler = handler;
sDisplayListener = new AudioDisplayListener();
+ sFoldStateListener = new FoldStateListener(sContext, RotationHelper::updateFoldState);
sRotationCallback = rotationCallback;
sFoldStateCallback = foldStateCallback;
enable();
@@ -90,7 +91,6 @@
.registerDisplayListener(sDisplayListener, sHandler);
updateOrientation();
- sFoldStateListener = new FoldStateListener(sContext, folded -> updateFoldState(folded));
sContext.getSystemService(DeviceStateManager.class)
.registerCallback(new HandlerExecutor(sHandler), sFoldStateListener);
}
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index cecde55..823788f 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -21,6 +21,7 @@
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.VirtualDevice;
import android.companion.virtual.sensor.VirtualSensor;
+import android.content.Context;
import android.os.LocaleList;
import android.util.ArraySet;
@@ -149,6 +150,14 @@
public abstract @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId);
/**
+ * Checks whether the passed {@code deviceId} is a valid virtual device ID or not.
+ *
+ * <p>{@link Context#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
+ * device which is not a virtual device.</p>
+ */
+ public abstract boolean isValidVirtualDeviceId(int deviceId);
+
+ /**
* Returns the ID of the device which owns the display with the given ID.
*
* <p>In case the virtual display ID is invalid or doesn't belong to a virtual device, then
diff --git a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
index f57bf29..405c149 100644
--- a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
+++ b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
@@ -130,8 +130,8 @@
}
sendErrorNotification(createErrorNotification(
- R.string.connected_display_cable_dont_support_displays_notification_title,
- R.string.connected_display_cable_dont_support_displays_notification_content,
+ R.string.connected_display_unavailable_notification_title,
+ R.string.connected_display_unavailable_notification_content,
R.drawable.usb_cable_unknown_issue));
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index ffd714b..f526dbe 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -120,6 +120,25 @@
@UserIdInt int userId);
/**
+ * Makes the input method associated with {@code imeId} the default input method for all users
+ * on displays that are owned by the virtual device with the given {@code deviceId}. If the
+ * input method associated with {@code imeId} is not available, there will be no IME on the
+ * relevant displays.
+ *
+ * <p>The caller of this method is responsible for resetting it to {@code null} after the
+ * virtual device is closed.</p>
+ *
+ * @param deviceId the device ID on which to use the given input method as default.
+ * @param imeId the input method ID to be used as default on the given device. If {@code null},
+ * then any existing input method association with that device will be removed.
+ * @throws IllegalArgumentException if a non-{@code null} input method ID is passed for a
+ * device ID that already has a custom input method set or if
+ * the device ID is not a valid virtual device.
+ */
+ public abstract void setVirtualDeviceInputMethodForAllUsers(
+ int deviceId, @Nullable String imeId);
+
+ /**
* Registers a new {@link InputMethodListListener}.
*
* @param listener the listener to add
@@ -250,6 +269,11 @@
}
@Override
+ public void setVirtualDeviceInputMethodForAllUsers(
+ int deviceId, @Nullable String imeId) {
+ }
+
+ @Override
public void registerInputMethodListListener(InputMethodListListener listener) {
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 85c57de..09c388f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -169,6 +169,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
import com.android.internal.view.IInputMethodManager;
import com.android.server.AccessibilityManagerInternal;
import com.android.server.EventLogTags;
@@ -312,6 +313,9 @@
// All known input methods.
final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
private final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>();
+ // Mapping from deviceId to the device-specific imeId for that device.
+ private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>();
+
final InputMethodSubtypeSwitchingController mSwitchingController;
final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController =
new HardwareKeyboardShortcutController();
@@ -5620,6 +5624,23 @@
}
@Override
+ public void setVirtualDeviceInputMethodForAllUsers(int deviceId, @Nullable String imeId) {
+ // TODO(b/287269288): validate that id belongs to a valid virtual device instead.
+ Preconditions.checkArgument(deviceId == Context.DEVICE_ID_DEFAULT,
+ "DeviceId " + deviceId + " does not belong to a virtual device.");
+ synchronized (ImfLock.class) {
+ if (imeId == null) {
+ mVirtualDeviceMethodMap.remove(deviceId);
+ } else if (mVirtualDeviceMethodMap.contains(deviceId)) {
+ throw new IllegalArgumentException("Virtual device " + deviceId
+ + " already has a custom input method component");
+ } else {
+ mVirtualDeviceMethodMap.put(deviceId, imeId);
+ }
+ }
+ }
+
+ @Override
public void registerInputMethodListListener(InputMethodListListener listener) {
mInputMethodListListeners.addIfAbsent(listener);
}
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index f8b22c9..07dac54 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -443,6 +443,8 @@
// indicates BAL would be blocked because only creator of the PI has the privilege to allow
// BAL, the sender does not have the privilege to allow BAL.
private boolean mOnlyCreatorAllows;
+ /** indicates that this verdict is based on the real calling UID and not the calling UID */
+ private boolean mBasedOnRealCaller;
BalVerdict(@BalCode int balCode, boolean background, String message) {
this.mBackground = background;
@@ -472,6 +474,15 @@
return mOnlyCreatorAllows;
}
+ private BalVerdict setBasedOnRealCaller() {
+ mBasedOnRealCaller = true;
+ return this;
+ }
+
+ private boolean isBasedOnRealCaller() {
+ return mBasedOnRealCaller;
+ }
+
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(balCodeToString(mCode));
@@ -495,7 +506,15 @@
return builder.toString();
}
+ public @BalCode int getRawCode() {
+ return mCode;
+ }
+
public @BalCode int getCode() {
+ if (mBasedOnRealCaller && mCode != BAL_BLOCK) {
+ // for compatibility always return BAL_ALLOW_PENDING_INTENT if based on real caller
+ return BAL_ALLOW_PENDING_INTENT;
+ }
return mCode;
}
}
@@ -580,7 +599,8 @@
// PendingIntents is null).
BalVerdict resultForRealCaller = state.callerIsRealCaller() && resultForCaller.allows()
? resultForCaller
- : checkBackgroundActivityStartAllowedBySender(state, checkedOptions);
+ : checkBackgroundActivityStartAllowedBySender(state, checkedOptions)
+ .setBasedOnRealCaller();
if (state.isPendingIntent()) {
resultForCaller.setOnlyCreatorAllows(
resultForCaller.allows() && resultForRealCaller.blocks());
@@ -828,7 +848,7 @@
&& ActivityManager.checkComponentPermission(
android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
state.mRealCallingUid, NO_PROCESS_UID, true) == PackageManager.PERMISSION_GRANTED) {
- return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+ return new BalVerdict(BAL_ALLOW_PERMISSION,
/*background*/ false,
"realCallingUid has BAL permission.");
}
@@ -839,18 +859,18 @@
|| state.mAppSwitchState == APP_SWITCH_FG_ONLY;
if (Flags.balImproveRealCallerVisibilityCheck()) {
if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) {
- return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+ return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
/*background*/ false, "realCallingUid has visible window");
}
if (mService.mActiveUids.hasNonAppVisibleWindow(state.mRealCallingUid)) {
- return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+ return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
/*background*/ false, "realCallingUid has non-app visible window");
}
} else {
// don't abort if the realCallingUid has a visible window
// TODO(b/171459802): We should check appSwitchAllowed also
if (state.mRealCallingUidHasAnyVisibleWindow) {
- return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+ return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
/*background*/ false,
"realCallingUid has visible (non-toast) window.");
}
@@ -860,7 +880,7 @@
// wasn't allowed to start an activity
if (state.mForcedBalByPiSender.allowsBackgroundActivityStarts()
&& state.mIsRealCallingUidPersistentSystemProcess) {
- return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID,
/*background*/ false,
"realCallingUid is persistent system process AND intent "
+ "sender forced to allow.");
@@ -868,7 +888,7 @@
// don't abort if the realCallingUid is an associated companion app
if (mService.isAssociatedCompanionApp(
UserHandle.getUserId(state.mRealCallingUid), state.mRealCallingUid)) {
- return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
/*background*/ false,
"realCallingUid is a companion app.");
}
@@ -1469,7 +1489,7 @@
intent != null ? intent.getComponent().flattenToShortString() : "";
FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
activityName,
- code,
+ BAL_ALLOW_PENDING_INTENT,
callingUid,
realCallingUid);
}
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 9a32dc8..478524b 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -22,7 +22,7 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
-import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
+import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_DISALLOW;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_FOREGROUND;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_GRACE_PERIOD;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
@@ -49,6 +49,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -113,13 +114,17 @@
"process allowed by token");
}
// Allow if the caller is bound by a UID that's currently foreground.
- if (isBoundByForegroundUid()) {
+ // But still respect the appSwitchState.
+ boolean allowBoundByForegroundUid =
+ Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid()
+ ? appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid()
+ : isBoundByForegroundUid();
+ if (allowBoundByForegroundUid) {
return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
"process bound by foreground uid");
}
// Allow if the caller has an activity in any foreground task.
- if (hasActivityInVisibleTask
- && (appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY)) {
+ if (hasActivityInVisibleTask && appSwitchState != APP_SWITCH_DISALLOW) {
return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false,
"process has activity in foreground task");
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 50376fe..a840973 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5502,6 +5502,10 @@
configureDisplayPolicy();
}
+ if (!isDefaultDisplay) {
+ mDisplayRotation.updateRotationUnchecked(true);
+ }
+
reconfigureDisplayLocked();
onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
mWmService.mDisplayNotificationController.dispatchDisplayAdded(this);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
index 25d331f..23886a1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
@@ -167,6 +167,11 @@
}
private void setStoppedState(int uid, String pkgName, boolean stopped) {
+ doReturn(stopped).when(mPackageManagerInternal).isPackageStopped(pkgName, uid);
+ sendPackageStoppedBroadcast(uid, pkgName, stopped);
+ }
+
+ private void sendPackageStoppedBroadcast(int uid, String pkgName, boolean stopped) {
Intent intent = new Intent(
stopped ? Intent.ACTION_PACKAGE_RESTARTED : Intent.ACTION_PACKAGE_UNSTOPPED);
intent.putExtra(Intent.EXTRA_UID, uid);
@@ -174,14 +179,6 @@
mStoppedReceiver.onReceive(mContext, intent);
}
- private void setUidBias(int uid, int bias) {
- int prevBias = mJobSchedulerService.getUidBias(uid);
- doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
- synchronized (mBackgroundJobsController.mLock) {
- mBackgroundJobsController.onUidBiasChangedLocked(uid, prevBias, bias);
- }
- }
-
private void trackJobs(JobStatus... jobs) {
for (JobStatus job : jobs) {
mJobStore.add(job);
@@ -208,6 +205,47 @@
}
@Test
+ public void testRestartedBroadcastWithoutStopping() {
+ mSetFlagsRule.enableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED);
+ // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself.
+ JobStatus directJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, SOURCE_UID,
+ createBaseJobInfoBuilder(SOURCE_PACKAGE, 1).build());
+ // Scheduled by ALTERNATE_UID:ALTERNATE_SOURCE_PACKAGE for itself.
+ JobStatus directJob2 = createJobStatus("testStopped",
+ ALTERNATE_SOURCE_PACKAGE, ALTERNATE_UID,
+ createBaseJobInfoBuilder(ALTERNATE_SOURCE_PACKAGE, 2).build());
+ // Scheduled by CALLING_PACKAGE for SOURCE_PACKAGE.
+ JobStatus proxyJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(CALLING_PACKAGE, 3).build());
+ // Scheduled by CALLING_PACKAGE for ALTERNATE_SOURCE_PACKAGE.
+ JobStatus proxyJob2 = createJobStatus("testStopped",
+ ALTERNATE_SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(CALLING_PACKAGE, 4).build());
+
+ trackJobs(directJob1, directJob2, proxyJob1, proxyJob2);
+
+ sendPackageStoppedBroadcast(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, true);
+ assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob1.isUserBgRestricted());
+ assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob2.isUserBgRestricted());
+ assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob1.isUserBgRestricted());
+ assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob2.isUserBgRestricted());
+
+ sendPackageStoppedBroadcast(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, false);
+ assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob1.isUserBgRestricted());
+ assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob2.isUserBgRestricted());
+ assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob1.isUserBgRestricted());
+ assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob2.isUserBgRestricted());
+ }
+
+ @Test
public void testStopped_disabled() {
mSetFlagsRule.disableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED);
// Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself.
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 9213601a..995d1f4 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -444,18 +444,27 @@
}
@Test
+ public void isDeviceIdValid_invalidDeviceId_returnsFalse() {
+ assertThat(mVdm.isValidVirtualDeviceId(DEVICE_ID_INVALID)).isFalse();
+ assertThat(mLocalService.isValidVirtualDeviceId(DEVICE_ID_INVALID)).isFalse();
+ }
+
+ @Test
public void isDeviceIdValid_defaultDeviceId_returnsFalse() {
assertThat(mVdm.isValidVirtualDeviceId(DEVICE_ID_DEFAULT)).isFalse();
+ assertThat(mLocalService.isValidVirtualDeviceId(DEVICE_ID_DEFAULT)).isFalse();
}
@Test
public void isDeviceIdValid_validVirtualDeviceId_returnsTrue() {
assertThat(mVdm.isValidVirtualDeviceId(mDeviceImpl.getDeviceId())).isTrue();
+ assertThat(mLocalService.isValidVirtualDeviceId(mDeviceImpl.getDeviceId())).isTrue();
}
@Test
public void isDeviceIdValid_nonExistentDeviceId_returnsFalse() {
assertThat(mVdm.isValidVirtualDeviceId(mDeviceImpl.getDeviceId() + 1)).isFalse();
+ assertThat(mLocalService.isValidVirtualDeviceId(mDeviceImpl.getDeviceId() + 1)).isFalse();
}
@Test
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 0e1e0c8..3d2340c 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -332,6 +332,7 @@
mUsageEventListeners.valueAt(i).onUsageEvent(userId, event);
}
}
+ return true;
}
}
return false;
@@ -1973,6 +1974,8 @@
+ ": " + Flags.userInteractionTypeApi());
pw.println(" " + Flags.FLAG_USE_PARCELED_LIST
+ ": " + Flags.useParceledList());
+ pw.println(" " + Flags.FLAG_FILTER_BASED_EVENT_QUERY_API
+ + ": " + Flags.filterBasedEventQueryApi());
final int[] userIds;
synchronized (mLock) {
@@ -2245,7 +2248,7 @@
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
- callingUid, userId);
+ callingUid, UserHandle.getCallingUserId());
final long token = Binder.clearCallingIdentity();
try {
@@ -2384,6 +2387,7 @@
if (!hasQueryPermission(callingPackage)) {
return null;
}
+
return queryEventsHelper(UserHandle.getCallingUserId(), query.getBeginTimeMillis(),
query.getEndTimeMillis(), callingPackage, query.getEventTypeFilter());
}