Merge "Fix reorder of UMO in carousel" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 0ccdf37..ab5d503 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -1043,20 +1043,12 @@
name: "device_policy_aconfig_flags",
package: "android.app.admin.flags",
container: "system",
- exportable: true,
srcs: [
"core/java/android/app/admin/flags/flags.aconfig",
],
}
java_aconfig_library {
- name: "device_policy_exported_aconfig_flags_lib",
- aconfig_declarations: "device_policy_aconfig_flags",
- defaults: ["framework-minus-apex-aconfig-java-defaults"],
- mode: "exported",
-}
-
-java_aconfig_library {
name: "device_policy_aconfig_flags_lib",
aconfig_declarations: "device_policy_aconfig_flags",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 02e8eec..e680103 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -262,6 +262,7 @@
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/OsuLogin)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system_other/system/app/OsuLogin)
+$(call add-clean-step, rm -rf $(OUT_DIR)/host/linux-x86/testcases/ravenwood-runtime)
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
# ******************************************************************
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 255ec92..74b34fb 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -246,12 +246,20 @@
visibility: ["//visibility:private"],
}
+java_genrule {
+ name: "z00-all-updatable-modules-system-stubs",
+ cmd: "cp $(in) $(out)",
+ srcs: [":all-updatable-modules-system-stubs"],
+ out: ["z00-all-updatable-modules-system-stubs.jar"],
+ visibility: ["//visibility:private"],
+}
+
android_ravenwood_libgroup {
name: "ravenwood-runtime",
libs: [
"100-framework-minus-apex.ravenwood",
"200-kxml2-android",
- "all-updatable-modules-system-stubs",
+
"android.test.mock.ravenwood",
"ravenwood-helper-runtime",
"hoststubgen-helper-runtime.ravenwood",
@@ -267,6 +275,9 @@
"ravenwood-junit-impl-flag",
"mockito-ravenwood-prebuilt",
"inline-mockito-ravenwood-prebuilt",
+
+ // It's a stub, so it should be towards the end.
+ "z00-all-updatable-modules-system-stubs",
],
jni_libs: [
"libandroid_runtime",
diff --git a/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py b/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py
index eea3b84..373355a 100755
--- a/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py
+++ b/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py
@@ -61,8 +61,8 @@
imports = """
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
@@ -118,4 +118,4 @@
print(" default void f{}() {{}}".format(i*imt_size + j))
print(" }")
-print("}")
\ No newline at end of file
+print("}")
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py b/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py
index f3a1fff..01abdb6 100755
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py
@@ -160,8 +160,8 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
index a4a2e80..9b0f5c9 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
@@ -133,7 +133,7 @@
pw.println(" --tag: Tag of the blob to delete.");
pw.println("idle-maintenance");
pw.println(" Run idle maintenance which takes care of removing stale data.");
- pw.println("query-blob-existence [-b BLOB_ID]");
+ pw.println("query-blob-existence [-b BLOB_ID] [-u | --user USER_ID]");
pw.println(" Prints 1 if blob exists, otherwise 0.");
pw.println();
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index d59d430..ad54cd39 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -491,8 +491,10 @@
* Returns a list of all currently-executing jobs.
* @hide
*/
- @SuppressWarnings("HiddenAbstractMethod")
- public abstract List<JobInfo> getStartedJobs();
+ @Nullable
+ public List<JobInfo> getStartedJobs() {
+ return null;
+ }
/**
* <b>For internal system callers only!</b>
@@ -501,8 +503,10 @@
* <p class="note">This is a slow operation, so it should be called sparingly.
* @hide
*/
- @SuppressWarnings("HiddenAbstractMethod")
- public abstract List<JobSnapshot> getAllJobSnapshots();
+ @Nullable
+ public List<JobSnapshot> getAllJobSnapshots() {
+ return null;
+ }
/**
* @hide
@@ -510,8 +514,8 @@
@RequiresPermission(allOf = {
android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
- @SuppressWarnings("HiddenAbstractMethod")
- public abstract void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer);
+ public void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
+ }
/**
* @hide
@@ -519,9 +523,10 @@
@RequiresPermission(allOf = {
android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
- @SuppressWarnings("HiddenAbstractMethod")
- public abstract void unregisterUserVisibleJobObserver(
- @NonNull IUserVisibleJobObserver observer);
+ public void unregisterUserVisibleJobObserver(
+ @NonNull IUserVisibleJobObserver observer) {
+
+ }
/**
* @hide
@@ -529,7 +534,7 @@
@RequiresPermission(allOf = {
android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
- @SuppressWarnings("HiddenAbstractMethod")
- public abstract void notePendingUserRequestedAppStop(@NonNull String packageName, int userId,
- @Nullable String debugReason);
+ public void notePendingUserRequestedAppStop(@NonNull String packageName, int userId,
+ @Nullable String debugReason) {
+ }
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 852b00b..d5a58d1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -1771,7 +1771,13 @@
final int logicalIndex = mapping.getLogicalSlotIndex();
if (mCarrierPrivilegedCallbacks.contains(logicalIndex)) {
// Callback already exists. No need to create a new one or remove it.
- callbacksToRemove.remove(logicalIndex);
+ for (int i = callbacksToRemove.size() - 1; i >= 0; i--) {
+ if (callbacksToRemove.get(i) == logicalIndex) {
+ callbacksToRemove.remove(i);
+ break;
+ }
+ }
+
continue;
}
final LogicalIndexCarrierPrivilegesCallback callback =
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 77b74e9..5adcd93 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -707,11 +707,11 @@
eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroySurface(mDisplay, mSurface);
- mFlingerSurfaceControl->updateDefaultBufferSize(newWidth, newHeight);
const auto limitedSize = limitSurfaceSize(newWidth, newHeight);
mWidth = limitedSize.width;
mHeight = limitedSize.height;
+ mFlingerSurfaceControl->updateDefaultBufferSize(mWidth, mHeight);
EGLConfig config = getEglConfig(mDisplay);
EGLSurface surface = eglCreateWindowSurface(mDisplay, config, mFlingerSurface.get(), nullptr);
if (eglMakeCurrent(mDisplay, surface, surface, mContext) == EGL_FALSE) {
diff --git a/cmds/bootanimation/FORMAT.md b/cmds/bootanimation/FORMAT.md
index 01e8fe1..da8331a 100644
--- a/cmds/bootanimation/FORMAT.md
+++ b/cmds/bootanimation/FORMAT.md
@@ -126,7 +126,7 @@
Use `zopflipng` if you have it, otherwise `pngcrush` will do. e.g.:
for fn in *.png ; do
- zopflipng -m ${fn}s ${fn}s.new && mv -f ${fn}s.new ${fn}
+ zopflipng -m ${fn} ${fn}.new && mv -f ${fn}.new ${fn}
# or: pngcrush -q ....
done
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index eabe1f1..96315eb 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1143,16 +1143,13 @@
field public static final int NAV_BAR_MODE_KIDS = 1; // 0x1
}
- public static final class StatusBarManager.DisableInfo implements android.os.Parcelable {
+ public static final class StatusBarManager.DisableInfo {
method public boolean areAllComponentsEnabled();
- method public int describeContents();
method public boolean isNavigateToHomeDisabled();
method public boolean isNotificationPeekingDisabled();
method public boolean isRecentsDisabled();
method public boolean isSearchDisabled();
method public boolean isStatusBarExpansionDisabled();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.StatusBarManager.DisableInfo> CREATOR;
}
public final class SystemServiceRegistry {
@@ -10342,7 +10339,7 @@
method public int getDeviceType();
method @NonNull public android.os.Bundle getExtras();
method @NonNull public String getModelName();
- method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging();
+ method @FlaggedApi("android.net.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR;
field public static final int DEVICE_TYPE_AUTO = 5; // 0x5
@@ -10356,7 +10353,7 @@
public static final class NetworkProviderInfo.Builder {
ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
- method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
+ method @FlaggedApi("android.net.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 62fc67b..78577e2 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -1939,10 +1939,6 @@
New API must be flagged with @FlaggedApi: method android.app.ActivityManager.getExternalHistoricalProcessStartReasons(String,int)
UnflaggedApi: android.app.AppOpsManager#OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO:
New API must be flagged with @FlaggedApi: field android.app.AppOpsManager.OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO
-UnflaggedApi: android.app.StatusBarManager.DisableInfo#CREATOR:
- New API must be flagged with @FlaggedApi: field android.app.StatusBarManager.DisableInfo.CREATOR
-UnflaggedApi: android.app.StatusBarManager.DisableInfo#isBackDisabled():
- New API must be flagged with @FlaggedApi: method android.app.StatusBarManager.DisableInfo.isBackDisabled()
UnflaggedApi: android.companion.virtual.VirtualDeviceManager.VirtualDevice#getPersistentDeviceId():
New API must be flagged with @FlaggedApi: method android.companion.virtual.VirtualDeviceManager.VirtualDevice.getPersistentDeviceId()
UnflaggedApi: android.content.Context#THREAD_NETWORK_SERVICE:
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 2437be8..624227d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -463,7 +463,7 @@
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void togglePanel();
}
- public static final class StatusBarManager.DisableInfo implements android.os.Parcelable {
+ public static final class StatusBarManager.DisableInfo {
method public boolean isRotationSuggestionDisabled();
}
@@ -966,7 +966,6 @@
ctor public AttributionSource(int, @Nullable String, @Nullable String);
ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder);
ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource);
- ctor @FlaggedApi("android.permission.flags.attribution_source_constructor") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource);
ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource);
method public void enforceCallingPid();
}
@@ -1775,16 +1774,16 @@
}
public final class InputManager {
- method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociation(@NonNull String, @NonNull String);
method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociationByDescriptor(@NonNull String, @NonNull String);
+ method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociationByPort(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings();
method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptors();
method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping();
method public int getMousePointerSpeed();
method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
- method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociation(@NonNull String);
method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByDescriptor(@NonNull String);
+ method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByPort(@NonNull String);
field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL
}
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index c4fe061..6cc71e5 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -975,14 +975,14 @@
Method 'getHdmiCecVersion' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.hardware.hdmi.HdmiControlManager#setHdmiCecVersion(int):
Method 'setHdmiCecVersion' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociation(String, String):
- Method 'addUniqueIdAssociation' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociationByDescriptor(String, String):
Method 'addUniqueIdAssociationByDescriptor' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociation(String):
- Method 'removeUniqueIdAssociation' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociationByPort(String, String):
+ Method 'addUniqueIdAssociationByPort' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociationByDescriptor(String):
Method 'removeUniqueIdAssociationByDescriptor' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociationByPort(String):
+ Method 'removeUniqueIdAssociationByPort' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.hardware.location.GeofenceHardware#addGeofence(int, int, android.hardware.location.GeofenceHardwareRequest, android.hardware.location.GeofenceHardwareCallback):
Method 'addGeofence' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.hardware.location.GeofenceHardware#getMonitoringTypes():
@@ -2035,6 +2035,10 @@
New API must be flagged with @FlaggedApi: method android.content.pm.UserInfo.isPrivateProfile()
UnflaggedApi: android.credentials.CredentialProviderInfo#isPrimary():
New API must be flagged with @FlaggedApi: method android.credentials.CredentialProviderInfo.isPrimary()
+UnflaggedApi: android.hardware.input.InputManager#addUniqueIdAssociationByPort(String, String):
+ New API must be flagged with @FlaggedApi: method android.hardware.input.InputManager.addUniqueIdAssociationByPort(String,String)
+UnflaggedApi: android.hardware.input.InputManager#removeUniqueIdAssociationByPort(String):
+ New API must be flagged with @FlaggedApi: method android.hardware.input.InputManager.removeUniqueIdAssociationByPort(String)
UnflaggedApi: android.media.AudioManager#enterAudioFocusFreezeForTest(java.util.List<java.lang.Integer>):
New API must be flagged with @FlaggedApi: method android.media.AudioManager.enterAudioFocusFreezeForTest(java.util.List<java.lang.Integer>)
UnflaggedApi: android.media.AudioManager#exitAudioFocusFreezeForTest():
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index caaaf51..d4812dd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -50,6 +50,7 @@
import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
import android.app.RemoteServiceException.CrashedByAdbException;
import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
+import android.app.RemoteServiceException.ForegroundServiceDidNotStopInTimeException;
import android.app.RemoteServiceException.MissingRequestPasswordComplexityPermissionException;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
@@ -2236,6 +2237,9 @@
case ForegroundServiceDidNotStartInTimeException.TYPE_ID:
throw generateForegroundServiceDidNotStartInTimeException(message, extras);
+ case ForegroundServiceDidNotStopInTimeException.TYPE_ID:
+ throw generateForegroundServiceDidNotStopInTimeException(message, extras);
+
case CannotPostForegroundServiceNotificationException.TYPE_ID:
throw new CannotPostForegroundServiceNotificationException(message);
@@ -2266,6 +2270,15 @@
throw new ForegroundServiceDidNotStartInTimeException(message, inner);
}
+ private ForegroundServiceDidNotStopInTimeException
+ generateForegroundServiceDidNotStopInTimeException(String message, Bundle extras) {
+ final String serviceClassName =
+ ForegroundServiceDidNotStopInTimeException.getServiceClassNameFromExtras(extras);
+ final Exception inner = (serviceClassName == null) ? null
+ : Service.getStartForegroundServiceStackTrace(serviceClassName);
+ throw new ForegroundServiceDidNotStopInTimeException(message, inner);
+ }
+
class H extends Handler {
public static final int BIND_APPLICATION = 110;
@UnsupportedAppUsage
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4ac6bac..0caea7f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -114,6 +114,7 @@
import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ContrastColorUtil;
+import com.android.internal.util.NewlineNormalizer;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -3190,7 +3191,7 @@
return charSequence;
}
- return charSequence.toString().replaceAll("[\r\n]+", "\n");
+ return NewlineNormalizer.normalizeNewlines(charSequence.toString());
}
private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
@@ -6594,6 +6595,11 @@
* @hide
*/
public RemoteViews createCompactHeadsUpContentView() {
+ // Don't show compact heads up for FSI notifications.
+ if (mN.fullScreenIntent != null) {
+ return createHeadsUpContentView(/* increasedHeight= */ false);
+ }
+
if (mStyle != null) {
final RemoteViews styleView = mStyle.makeCompactHeadsUpContentView();
if (styleView != null) {
@@ -10351,7 +10357,7 @@
@Nullable
@Override
public RemoteViews makeCompactHeadsUpContentView() {
- // TODO(b/336228700): Apply minimal HUN treatment for Call Style.
+ // Use existing heads up for call style.
return makeHeadsUpContentView(false);
}
diff --git a/core/java/android/app/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java
index c5ad110..c624c43 100644
--- a/core/java/android/app/RemoteServiceException.java
+++ b/core/java/android/app/RemoteServiceException.java
@@ -71,6 +71,33 @@
}
/**
+ * Exception used to crash an app process when it didn't stop after hitting its time limit.
+ *
+ * @hide
+ */
+ public static class ForegroundServiceDidNotStopInTimeException extends RemoteServiceException {
+ /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
+ public static final int TYPE_ID = 7;
+
+ private static final String KEY_SERVICE_CLASS_NAME = "serviceclassname";
+
+ public ForegroundServiceDidNotStopInTimeException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+ public static Bundle createExtrasForService(@NonNull ComponentName service) {
+ Bundle b = new Bundle();
+ b.putString(KEY_SERVICE_CLASS_NAME, service.getClassName());
+ return b;
+ }
+
+ @Nullable
+ public static String getServiceClassNameFromExtras(@Nullable Bundle extras) {
+ return (extras == null) ? null : extras.getString(KEY_SERVICE_CLASS_NAME);
+ }
+ }
+
+ /**
* Exception used to crash an app process when the system received a RemoteException
* while posting a notification of a foreground service.
*
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 726064e..aaddaa6 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1198,8 +1198,7 @@
* Callback called when a particular foreground service type has timed out.
*
* <p>This callback is meant to give the app a small grace period of a few seconds to finish
- * the foreground service of the associated type - if it fails to do so, the app will be
- * declared an ANR.
+ * the foreground service of the associated type - if it fails to do so, the app will crash.
*
* <p>The foreground service of the associated type can be stopped within the time limit by
* {@link android.app.Service#stopSelf()},
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 301fef8..14195c4 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -21,7 +21,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -44,7 +43,6 @@
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -59,7 +57,6 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.IUndoMediaTransferCallback;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.internal.util.DataClass;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -632,49 +629,38 @@
}
/**
- * @deprecated
- * Disable some features in the status bar. Pass the bitwise-or of the DISABLE_*
- * flags. To re-enable everything, pass {@link #DISABLE_NONE}.
- *
- * This method is deprecated and callers should use
- * {@link #requestDisabledComponent(DisableInfo, String)}
+ * Disable some features in the status bar. Pass the bitwise-or of the DISABLE_* flags.
+ * To re-enable everything, pass {@link #DISABLE_NONE}.
*
* @hide
*/
- @Deprecated
@UnsupportedAppUsage
public void disable(int what) {
- requestDisabledComponent(new DisableInfo(what & DISABLE_MASK, what & DISABLE2_MASK), null);
- }
-
- /**
- * @deprecated
- * Disable some features in the status bar. Pass the bitwise-or of the DISABLE_2*
- * flags. To re-enable everything, pass {@link #DISABLE2_NONE}.
- *
- * This method is deprecated and callers should use
- * {@link #requestDisabledComponent(DisableInfo, String)}
- *
- * @hide
- */
- @Deprecated
- @UnsupportedAppUsage
- public void disable2(int what) {
- requestDisabledComponent(new DisableInfo(what & DISABLE_MASK, what & DISABLE2_MASK), null);
- }
-
- /**
- * Disable some features in the status bar. Pass a DisableInfo object with the required flags.
- *
- * @hide
- */
- @UnsupportedAppUsage
- public void requestDisabledComponent(DisableInfo disableInfo, String reason) {
try {
final int userId = Binder.getCallingUserHandle().getIdentifier();
final IStatusBarService svc = getService();
if (svc != null) {
- svc.disableForUser(disableInfo, mToken, mContext.getPackageName(), userId, reason);
+ svc.disableForUser(what, mToken, mContext.getPackageName(), userId);
+ }
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Disable additional status bar features. Pass the bitwise-or of the DISABLE2_* flags.
+ * To re-enable everything, pass {@link #DISABLE_NONE}.
+ *
+ * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+ *
+ * @hide
+ */
+ public void disable2(@Disable2Flags int what) {
+ try {
+ final int userId = Binder.getCallingUserHandle().getIdentifier();
+ final IStatusBarService svc = getService();
+ if (svc != null) {
+ svc.disable2ForUser(what, mToken, mContext.getPackageName(), userId);
}
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
@@ -902,9 +888,18 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.STATUS_BAR)
public void setDisabledForSetup(boolean disabled) {
- int flags1 = disabled ? DEFAULT_SETUP_DISABLE_FLAGS : DISABLE_NONE;
- DisableInfo info = new DisableInfo(flags1, DISABLE2_NONE);
- requestDisabledComponent(info, "setDisabledForSetup");
+ try {
+ final int userId = Binder.getCallingUserHandle().getIdentifier();
+ final IStatusBarService svc = getService();
+ if (svc != null) {
+ svc.disableForUser(disabled ? DEFAULT_SETUP_DISABLE_FLAGS : DISABLE_NONE,
+ mToken, mContext.getPackageName(), userId);
+ svc.disable2ForUser(disabled ? DEFAULT_SETUP_DISABLE2_FLAGS : DISABLE2_NONE,
+ mToken, mContext.getPackageName(), userId);
+ }
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
}
/**
@@ -919,9 +914,16 @@
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.STATUS_BAR)
public void setExpansionDisabledForSimNetworkLock(boolean disabled) {
- int flags1 = disabled ? DEFAULT_SIM_LOCKED_DISABLED_FLAGS : DISABLE_NONE;
- DisableInfo info = new DisableInfo(flags1, DISABLE2_NONE);
- requestDisabledComponent(info, "setExpansionDisabledForSimNetworkLock");
+ try {
+ final int userId = Binder.getCallingUserHandle().getIdentifier();
+ final IStatusBarService svc = getService();
+ if (svc != null) {
+ svc.disableForUser(disabled ? DEFAULT_SIM_LOCKED_DISABLED_FLAGS : DISABLE_NONE,
+ mToken, mContext.getPackageName(), userId);
+ }
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
}
/**
@@ -1313,75 +1315,33 @@
* @hide
*/
@SystemApi
- @DataClass
- public static final class DisableInfo implements Parcelable {
+ public static final class DisableInfo {
- /**
- * @hide
- */
private boolean mStatusBarExpansion;
- /**
- * @hide
- */
private boolean mNavigateHome;
- /**
- * @hide
- */
private boolean mNotificationPeeking;
- /**
- * @hide
- */
private boolean mRecents;
- /**
- * @hide
- */
- private boolean mBack;
- /**
- * @hide
- */
private boolean mSearch;
- /**
- * @hide
- */
private boolean mSystemIcons;
- /**
- * @hide
- */
private boolean mClock;
- /**
- * @hide
- */
private boolean mNotificationIcons;
- /**
- * @hide
- */
private boolean mRotationSuggestion;
- /**
- * @hide
- */
- private boolean mNotificationTicker;
/** @hide */
- @SuppressLint("UnflaggedApi")
public DisableInfo(int flags1, int flags2) {
mStatusBarExpansion = (flags1 & DISABLE_EXPAND) != 0;
mNavigateHome = (flags1 & DISABLE_HOME) != 0;
mNotificationPeeking = (flags1 & DISABLE_NOTIFICATION_ALERTS) != 0;
mRecents = (flags1 & DISABLE_RECENT) != 0;
- mBack = (flags1 & DISABLE_BACK) != 0;
mSearch = (flags1 & DISABLE_SEARCH) != 0;
mSystemIcons = (flags1 & DISABLE_SYSTEM_INFO) != 0;
mClock = (flags1 & DISABLE_CLOCK) != 0;
mNotificationIcons = (flags1 & DISABLE_NOTIFICATION_ICONS) != 0;
- mNotificationTicker = (flags1 & DISABLE_NOTIFICATION_TICKER) != 0;
mRotationSuggestion = (flags2 & DISABLE2_ROTATE_SUGGESTIONS) != 0;
}
/** @hide */
- @SuppressLint("UnflaggedApi")
- public DisableInfo() {
- setEnableAll();
- }
+ public DisableInfo() {}
/**
* @return {@code true} if expanding the notification shade is disabled
@@ -1409,7 +1369,7 @@
}
/** * @hide */
- public void setNavigationHomeDisabled(boolean disabled) {
+ public void setNagivationHomeDisabled(boolean disabled) {
mNavigateHome = disabled;
}
@@ -1444,20 +1404,6 @@
}
/**
- * @return {@code true} if mBack is disabled
- *
- * @hide
- */
- public boolean isBackDisabled() {
- return mBack;
- }
-
- /** @hide */
- public void setBackDisabled(boolean disabled) {
- mBack = disabled;
- }
-
- /**
* @return {@code true} if mSearch is disabled
*
* @hide
@@ -1515,20 +1461,6 @@
}
/**
- * @return {@code true} if notification ticker is disabled
- *
- * @hide
- */
- public boolean isNotificationTickerDisabled() {
- return mNotificationTicker;
- }
-
- /** * @hide */
- public void setNotificationTickerDisabled(boolean disabled) {
- mNotificationTicker = disabled;
- }
-
- /**
* Returns whether the rotation suggestion is disabled.
*
* @hide
@@ -1538,11 +1470,6 @@
return mRotationSuggestion;
}
- /** * @hide */
- public void setRotationSuggestionDisabled(boolean disabled) {
- mNotificationIcons = disabled;
- }
-
/**
* @return {@code true} if no components are disabled (default state)
* @hide
@@ -1550,8 +1477,8 @@
@SystemApi
public boolean areAllComponentsEnabled() {
return !mStatusBarExpansion && !mNavigateHome && !mNotificationPeeking && !mRecents
- && !mBack && !mSearch && !mSystemIcons && !mClock && !mNotificationIcons
- && !mNotificationTicker && !mRotationSuggestion;
+ && !mSearch && !mSystemIcons && !mClock && !mNotificationIcons
+ && !mRotationSuggestion;
}
/** @hide */
@@ -1560,12 +1487,10 @@
mNavigateHome = false;
mNotificationPeeking = false;
mRecents = false;
- mBack = false;
mSearch = false;
mSystemIcons = false;
mClock = false;
mNotificationIcons = false;
- mNotificationTicker = false;
mRotationSuggestion = false;
}
@@ -1575,9 +1500,9 @@
* @hide
*/
public boolean areAllComponentsDisabled() {
- return mStatusBarExpansion && mNavigateHome && mNotificationPeeking && mRecents && mBack
- && mSearch && mSystemIcons && mClock && mNotificationIcons
- && mNotificationTicker && mRotationSuggestion;
+ return mStatusBarExpansion && mNavigateHome && mNotificationPeeking
+ && mRecents && mSearch && mSystemIcons && mClock && mNotificationIcons
+ && mRotationSuggestion;
}
/** @hide */
@@ -1586,12 +1511,10 @@
mNavigateHome = true;
mNotificationPeeking = true;
mRecents = true;
- mBack = true;
mSearch = true;
mSystemIcons = true;
mClock = true;
mNotificationIcons = true;
- mNotificationTicker = true;
mRotationSuggestion = true;
}
@@ -1599,19 +1522,16 @@
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
-
- sb.append("Disable Info: ");
+ sb.append("DisableInfo: ");
sb.append(" mStatusBarExpansion=").append(mStatusBarExpansion ? "disabled" : "enabled");
sb.append(" mNavigateHome=").append(mNavigateHome ? "disabled" : "enabled");
sb.append(" mNotificationPeeking=")
.append(mNotificationPeeking ? "disabled" : "enabled");
sb.append(" mRecents=").append(mRecents ? "disabled" : "enabled");
- sb.append(" mBack=").append(mBack ? "disabled" : "enabled");
sb.append(" mSearch=").append(mSearch ? "disabled" : "enabled");
sb.append(" mSystemIcons=").append(mSystemIcons ? "disabled" : "enabled");
sb.append(" mClock=").append(mClock ? "disabled" : "enabled");
sb.append(" mNotificationIcons=").append(mNotificationIcons ? "disabled" : "enabled");
- sb.append(" mNotificationTicker=").append(mNotificationTicker ? "disabled" : "enabled");
sb.append(" mRotationSuggestion=").append(mRotationSuggestion ? "disabled" : "enabled");
return sb.toString();
@@ -1619,7 +1539,7 @@
}
/**
- * Convert a DisableInfo to equivalent flags.
+ * Convert a DisableInfo to equivalent flags
* @return a pair of equivalent disable flags
*
* @hide
@@ -1632,278 +1552,14 @@
if (mNavigateHome) disable1 |= DISABLE_HOME;
if (mNotificationPeeking) disable1 |= DISABLE_NOTIFICATION_ALERTS;
if (mRecents) disable1 |= DISABLE_RECENT;
- if (mBack) disable1 |= DISABLE_BACK;
if (mSearch) disable1 |= DISABLE_SEARCH;
if (mSystemIcons) disable1 |= DISABLE_SYSTEM_INFO;
if (mClock) disable1 |= DISABLE_CLOCK;
if (mNotificationIcons) disable1 |= DISABLE_NOTIFICATION_ICONS;
- if (mNotificationTicker) disable1 |= DISABLE_NOTIFICATION_TICKER;
if (mRotationSuggestion) disable2 |= DISABLE2_ROTATE_SUGGESTIONS;
return new Pair<Integer, Integer>(disable1, disable2);
}
-
-
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/StatusBarManager.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- /**
- * Creates a new DisableInfo.
- * @hide
- */
- @DataClass.Generated.Member
- public DisableInfo(
- boolean statusBarExpansion,
- boolean navigateHome,
- boolean notificationPeeking,
- boolean recents,
- boolean back,
- boolean search,
- boolean systemIcons,
- boolean clock,
- boolean notificationIcons,
- boolean rotationSuggestion,
- boolean notificationTicker) {
- this.mStatusBarExpansion = statusBarExpansion;
- this.mNavigateHome = navigateHome;
- this.mNotificationPeeking = notificationPeeking;
- this.mRecents = recents;
- this.mBack = back;
- this.mSearch = search;
- this.mSystemIcons = systemIcons;
- this.mClock = clock;
- this.mNotificationIcons = notificationIcons;
- this.mRotationSuggestion = rotationSuggestion;
- this.mNotificationTicker = notificationTicker;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- /**
- * @hide
- */
- @DataClass.Generated.Member
- public boolean isStatusBarExpansion() {
- return mStatusBarExpansion;
- }
-
- /**
- * @hide
- */
- @DataClass.Generated.Member
- public boolean isNavigateHome() {
- return mNavigateHome;
- }
-
- /**
- * @hide
- */
- @DataClass.Generated.Member
- public boolean isNotificationPeeking() {
- return mNotificationPeeking;
- }
-
- /**
- * @hide
- */
- @DataClass.Generated.Member
- public boolean isRecents() {
- return mRecents;
- }
-
- /**
- * @hide
- */
- @DataClass.Generated.Member
- public boolean isBack() {
- return mBack;
- }
-
- /**
- * @hide
- */
- @DataClass.Generated.Member
- public boolean isSearch() {
- return mSearch;
- }
-
- /**
- * @hide
- */
- @DataClass.Generated.Member
- public boolean isSystemIcons() {
- return mSystemIcons;
- }
-
- /**
- * @hide
- */
- @DataClass.Generated.Member
- public boolean isClock() {
- return mClock;
- }
-
- /**
- * @hide
- */
- @DataClass.Generated.Member
- public boolean isNotificationIcons() {
- return mNotificationIcons;
- }
-
- /**
- * @hide
- */
- @DataClass.Generated.Member
- public boolean isRotationSuggestion() {
- return mRotationSuggestion;
- }
-
- /**
- * @hide
- */
- @DataClass.Generated.Member
- public boolean isNotificationTicker() {
- return mNotificationTicker;
- }
-
- /**
- * @hide
- */
- @SuppressLint("UnflaggedApi")
- @Override
- @DataClass.Generated.Member
- public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
- int flg = 0;
- if (mStatusBarExpansion) flg |= 0x1;
- if (mNavigateHome) flg |= 0x2;
- if (mNotificationPeeking) flg |= 0x4;
- if (mRecents) flg |= 0x8;
- if (mBack) flg |= 0x10;
- if (mSearch) flg |= 0x20;
- if (mSystemIcons) flg |= 0x40;
- if (mClock) flg |= 0x80;
- if (mNotificationIcons) flg |= 0x100;
- if (mRotationSuggestion) flg |= 0x200;
- if (mNotificationTicker) flg |= 0x400;
- dest.writeInt(flg);
- }
-
- /**
- * @hide
- */
- @SuppressLint("UnflaggedApi")
- @Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
-
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- /* package-private */ DisableInfo(@NonNull android.os.Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- int flg = in.readInt();
- boolean statusBarExpansion = (flg & 0x1) != 0;
- boolean navigateHome = (flg & 0x2) != 0;
- boolean notificationPeeking = (flg & 0x4) != 0;
- boolean recents = (flg & 0x8) != 0;
- boolean back = (flg & 0x10) != 0;
- boolean search = (flg & 0x20) != 0;
- boolean systemIcons = (flg & 0x40) != 0;
- boolean clock = (flg & 0x80) != 0;
- boolean notificationIcons = (flg & 0x100) != 0;
- boolean rotationSuggestion = (flg & 0x200) != 0;
- boolean notificationTicker = (flg & 0x400) != 0;
-
- this.mStatusBarExpansion = statusBarExpansion;
- this.mNavigateHome = navigateHome;
- this.mNotificationPeeking = notificationPeeking;
- this.mRecents = recents;
- this.mBack = back;
- this.mSearch = search;
- this.mSystemIcons = systemIcons;
- this.mClock = clock;
- this.mNotificationIcons = notificationIcons;
- this.mRotationSuggestion = rotationSuggestion;
- this.mNotificationTicker = notificationTicker;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<DisableInfo> CREATOR
- = new Parcelable.Creator<DisableInfo>() {
- @Override
- public DisableInfo[] newArray(int size) {
- return new DisableInfo[size];
- }
-
- @Override
- public DisableInfo createFromParcel(@NonNull android.os.Parcel in) {
- return new DisableInfo(in);
- }
- };
-
- @DataClass.Generated(
- time = 1708625947132L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/app/StatusBarManager.java",
- inputSignatures = "private boolean mStatusBarExpansion\nprivate boolean "
- + "mNavigateHome\nprivate boolean mNotificationPeeking\nprivate "
- + "boolean mRecents\nprivate boolean mBack\nprivate boolean mSearch\n"
- + "private boolean mSystemIcons\nprivate boolean mClock\nprivate "
- + "boolean mNotificationIcons\nprivate boolean mRotationSuggestion\n"
- + "private boolean mNotificationTicker\npublic "
- + "@android.annotation.SystemApi boolean isStatusBarExpansionDisabled()\n"
- + "public void setStatusBarExpansionDisabled(boolean)\npublic "
- + "@android.annotation.SystemApi boolean isNavigateToHomeDisabled()\npublic"
- + " void setNavigationHomeDisabled(boolean)\npublic "
- + "@android.annotation.SystemApi boolean isNotificationPeekingDisabled()"
- + "\npublic void setNotificationPeekingDisabled(boolean)\npublic "
- + "@android.annotation.SystemApi boolean isRecentsDisabled()\npublic "
- + "void setRecentsDisabled(boolean)\npublic boolean isBackDisabled()"
- + "\npublic void setBackDisabled(boolean)\npublic "
- + "@android.annotation.SystemApi boolean isSearchDisabled()\npublic "
- + "void setSearchDisabled(boolean)\npublic boolean "
- + "areSystemIconsDisabled()\npublic void setSystemIconsDisabled(boolean)\n"
- + "public boolean isClockDisabled()\npublic "
- + "void setClockDisabled(boolean)\npublic boolean "
- + "areNotificationIconsDisabled()\npublic void "
- + "setNotificationIconsDisabled(boolean)\npublic boolean "
- + "isNotificationTickerDisabled()\npublic void "
- + "setNotificationTickerDisabled(boolean)\npublic "
- + "@android.annotation.TestApi boolean isRotationSuggestionDisabled()\n"
- + "public void setRotationSuggestionDisabled(boolean)\npublic "
- + "@android.annotation.SystemApi boolean areAllComponentsEnabled()\npublic"
- + " void setEnableAll()\npublic boolean areAllComponentsDisabled()\n"
- + "public void setDisableAll()\npublic @android.annotation.NonNull "
- + "@java.lang.Override java.lang.String toString()\npublic "
- + "android.util.Pair<java.lang.Integer,java.lang.Integer> toFlags()\n"
- + "class DisableInfo extends java.lang.Object implements "
- + "[android.os.Parcelable]\n@com.android.internal.util.DataClass")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
}
/**
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index efd5a45..ef8501f 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -351,6 +351,12 @@
}
/** @hide */
+ public boolean isFreeform() {
+ return configuration.windowConfiguration.getWindowingMode()
+ == WindowConfiguration.WINDOWING_MODE_FREEFORM;
+ }
+
+ /** @hide */
@WindowConfiguration.ActivityType
public int getActivityType() {
return configuration.windowConfiguration.getActivityType();
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 8c4667f..bb24fd1 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -50,3 +50,23 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "backstage_power"
+ name: "gate_fgs_timeout_anr_behavior"
+ description: "Gate the new behavior where an ANR is thrown once an FGS times out."
+ bug: "339315145"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ namespace: "backstage_power"
+ name: "enable_fgs_timeout_crash_behavior"
+ description: "Enable the new behavior where the app is crashed once an FGS times out."
+ bug: "339526947"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 83daa45..3d6ec19 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -217,6 +217,16 @@
}
flag {
+ name: "disallow_user_control_stopped_state_fix"
+ namespace: "enterprise"
+ description: "Ensure DPM.setUserControlDisabledPackages() clears FLAG_STOPPED for the app"
+ bug: "330688482"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "esim_management_ux_enabled"
namespace: "enterprise"
description: "Enable UX changes for esim management"
@@ -309,7 +319,6 @@
namespace: "enterprise"
description: "Fix for compatibility issue introduced from using single_user mode on pre-Android V builds"
bug: "338050276"
- is_exported: true
metadata {
purpose: PURPOSE_BUGFIX
}
diff --git a/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl b/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl
index 9f09d04..5a13255 100644
--- a/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl
+++ b/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl
@@ -48,6 +48,8 @@
const int POLICY_TYPE_AUDIO = 1;
const int POLICY_TYPE_RECENTS = 2;
const int POLICY_TYPE_ACTIVITY = 3;
+ const int POLICY_TYPE_CLIPBOARD = 4;
+ const int POLICY_TYPE_CAMERA = 5;
/**
* Returns the IDs for all VirtualDevices where an app with the given is running.
@@ -62,4 +64,4 @@
* Returns the device policy for the given virtual device and policy type.
*/
int getDevicePolicy(int deviceId, int policyType);
-}
\ No newline at end of file
+}
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index b070742..37f419d 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -162,17 +162,6 @@
/** @hide */
@TestApi
- @FlaggedApi(Flags.FLAG_ATTRIBUTION_SOURCE_CONSTRUCTOR)
- public AttributionSource(int uid, int pid, @Nullable String packageName,
- @Nullable String attributionTag, @NonNull IBinder token,
- @Nullable String[] renouncedPermissions,
- @Nullable AttributionSource next) {
- this(uid, pid, packageName, attributionTag, token, renouncedPermissions,
- Context.DEVICE_ID_DEFAULT, next);
- }
-
- /** @hide */
- @TestApi
@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public AttributionSource(int uid, int pid, @Nullable String packageName,
@Nullable String attributionTag, @NonNull IBinder token,
@@ -473,6 +462,20 @@
}
/**
+ * @return The next package's device Id from its context.
+ * This device ID is used for permissions checking during attribution source validation.
+ *
+ * @hide
+ */
+ public int getNextDeviceId() {
+ if (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
+ return mAttributionSourceState.next[0].deviceId;
+ }
+ return Context.DEVICE_ID_DEFAULT;
+ }
+
+ /**
* Checks whether this attribution source can be trusted. That is whether
* the app it refers to created it and provided to the attribution chain.
*
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index be40143..cd3ce87 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -1492,12 +1492,12 @@
/**
* Sets which surfaces a shortcut will be excluded from.
*
- * If the shortcut is set to be excluded from {@link #SURFACE_LAUNCHER}, shortcuts will be
- * excluded from the search result of {@link android.content.pm.LauncherApps#getShortcuts(
- * android.content.pm.LauncherApps.ShortcutQuery, UserHandle)} nor
- * {@link android.content.pm.ShortcutManager#getShortcuts(int)}. This generally means the
- * shortcut would not be displayed by a launcher app (e.g. in Long-Press menu), while
- * remain visible in other surfaces such as assistant or on-device-intelligence.
+ * This API is reserved for future extension. Currently, marking a shortcut to be
+ * excluded from {@link #SURFACE_LAUNCHER} will not publish the shortcut, thus
+ * the following operations will be a no-op:
+ * {@link android.content.pm.ShortcutManager#pushDynamicShortcut(android.content.pm.ShortcutInfo)},
+ * {@link android.content.pm.ShortcutManager#addDynamicShortcuts(List)}, and
+ * {@link android.content.pm.ShortcutManager#setDynamicShortcuts(List)}.
*/
@NonNull
public Builder setExcludedFromSurfaces(final int surfaces) {
diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java
index 3d8ccaa..c70eff4 100644
--- a/core/java/android/credentials/GetCandidateCredentialsResponse.java
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java
@@ -18,6 +18,8 @@
import android.annotation.Hide;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
import android.content.Intent;
import android.credentials.selection.GetCredentialProviderData;
import android.os.Parcel;
@@ -39,6 +41,9 @@
@NonNull
private final List<GetCredentialProviderData> mCandidateProviderDataList;
+ @Nullable
+ private final ComponentName mPrimaryProviderComponentName;
+
@NonNull
private final Intent mIntent;
@@ -48,13 +53,15 @@
@Hide
public GetCandidateCredentialsResponse(
@NonNull List<GetCredentialProviderData> candidateProviderDataList,
- @NonNull Intent intent
+ @NonNull Intent intent,
+ @Nullable ComponentName primaryProviderComponentName
) {
Preconditions.checkCollectionNotEmpty(
candidateProviderDataList,
/*valueName=*/ "candidateProviderDataList");
mCandidateProviderDataList = new ArrayList<>(candidateProviderDataList);
mIntent = intent;
+ mPrimaryProviderComponentName = primaryProviderComponentName;
}
/**
@@ -67,6 +74,16 @@
}
/**
+ * Returns the primary provider component name.
+ *
+ * @hide
+ */
+ @Nullable
+ public ComponentName getPrimaryProviderComponentName() {
+ return mPrimaryProviderComponentName;
+ }
+
+ /**
* Returns candidate provider data list.
*
* @hide
@@ -83,12 +100,15 @@
AnnotationValidations.validate(NonNull.class, null, mCandidateProviderDataList);
mIntent = in.readTypedObject(Intent.CREATOR);
+
+ mPrimaryProviderComponentName = in.readTypedObject(ComponentName.CREATOR);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedList(mCandidateProviderDataList);
dest.writeTypedObject(mIntent, flags);
+ dest.writeTypedObject(mPrimaryProviderComponentName, flags);
}
@Override
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index a019612..2b7d8f1 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -838,7 +838,10 @@
return new CameraExtensionCharacteristics(mContext, cameraId, characteristicsMap);
}
- private Map<String, CameraCharacteristics> getPhysicalIdToCharsMap(
+ /**
+ * @hide
+ */
+ public Map<String, CameraCharacteristics> getPhysicalIdToCharsMap(
CameraCharacteristics chars) throws CameraAccessException {
HashMap<String, CameraCharacteristics> physicalIdsToChars =
new HashMap<String, CameraCharacteristics>();
@@ -974,8 +977,6 @@
final int oomScoreOffset, int rotationOverride) throws CameraAccessException {
CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
CameraDevice device = null;
- Map<String, CameraCharacteristics> physicalIdsToChars =
- getPhysicalIdToCharsMap(characteristics);
synchronized (mLock) {
ICameraDeviceUser cameraUser = null;
CameraDevice.CameraDeviceSetup cameraDeviceSetup = null;
@@ -990,7 +991,7 @@
callback,
executor,
characteristics,
- physicalIdsToChars,
+ this,
mContext.getApplicationInfo().targetSdkVersion,
mContext, cameraDeviceSetup);
ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 6d9b51cb..2e1e90c 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -200,6 +200,8 @@
supportedCaptureSizes.put(format, supportedSizes);
}
}
+
+ int captureFormat = ImageFormat.UNKNOWN;
Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface(
config.getOutputConfigurations(), supportedCaptureSizes);
OutputConfiguration burstCaptureOutputConfig = null;
@@ -210,6 +212,12 @@
}
}
suitableSurfaceCount++;
+
+ if (Flags.analytics24q3()) {
+ CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo =
+ CameraExtensionUtils.querySurface(burstCaptureSurface);
+ captureFormat = burstCaptureSurfaceInfo.mFormat;
+ }
}
if (suitableSurfaceCount != config.getOutputConfigurations().size()) {
@@ -249,6 +257,9 @@
burstCaptureOutputConfig, postviewOutputConfig, config.getStateCallback(),
config.getExecutor(), sessionId, token, config.getExtension());
+ if (Flags.analytics24q3()) {
+ ret.mStatsAggregator.setCaptureFormat(captureFormat);
+ }
ret.mStatsAggregator.setClientName(ctx.getOpPackageName());
ret.mStatsAggregator.setExtensionType(config.getExtension());
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 81bb9ac..e2b409f 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -31,6 +31,7 @@
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CameraOfflineSession;
import android.hardware.camera2.CaptureFailure;
@@ -146,7 +147,8 @@
private final String mCameraId;
private final CameraCharacteristics mCharacteristics;
- private final Map<String, CameraCharacteristics> mPhysicalIdsToChars;
+ private Map<String, CameraCharacteristics> mPhysicalIdsToChars;
+ private final CameraManager mCameraManager;
private final int mTotalPartialCount;
private final Context mContext;
@@ -341,11 +343,12 @@
public CameraDeviceImpl(String cameraId, StateCallback callback, Executor executor,
CameraCharacteristics characteristics,
- Map<String, CameraCharacteristics> physicalIdsToChars,
+ @NonNull CameraManager manager,
int appTargetSdkVersion,
Context ctx,
@Nullable CameraDevice.CameraDeviceSetup cameraDeviceSetup) {
- if (cameraId == null || callback == null || executor == null || characteristics == null) {
+ if (cameraId == null || callback == null || executor == null || characteristics == null
+ || manager == null) {
throw new IllegalArgumentException("Null argument given");
}
mCameraId = cameraId;
@@ -357,7 +360,7 @@
mDeviceExecutor = executor;
}
mCharacteristics = characteristics;
- mPhysicalIdsToChars = physicalIdsToChars;
+ mCameraManager = manager;
mAppTargetSdkVersion = appTargetSdkVersion;
mContext = ctx;
mCameraDeviceSetup = cameraDeviceSetup;
@@ -379,6 +382,18 @@
}
}
+ private Map<String, CameraCharacteristics> getPhysicalIdToChars() {
+ if (mPhysicalIdsToChars == null) {
+ try {
+ mPhysicalIdsToChars = mCameraManager.getPhysicalIdToCharsMap(mCharacteristics);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Unable to query the physical characteristics map!");
+ }
+ }
+
+ return mPhysicalIdsToChars;
+ }
+
public CameraDeviceCallbacks getCallbacks() {
return mCallbacks;
}
@@ -1598,7 +1613,7 @@
return true;
}
- for (Map.Entry<String, CameraCharacteristics> entry : mPhysicalIdsToChars.entrySet()) {
+ for (Map.Entry<String, CameraCharacteristics> entry : getPhysicalIdToChars().entrySet()) {
configMap = entry.getValue().get(ck);
if (configMap != null &&
@@ -2621,7 +2636,7 @@
public void createExtensionSession(ExtensionSessionConfiguration extensionConfiguration)
throws CameraAccessException {
HashMap<String, CameraCharacteristics> characteristicsMap = new HashMap<>(
- mPhysicalIdsToChars);
+ getPhysicalIdToChars());
characteristicsMap.put(mCameraId, mCharacteristics);
boolean initializationFailed = true;
IBinder token = new Binder(TAG + " : " + mNextSessionId++);
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 3ae3199..a4ae398 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -200,10 +200,18 @@
supportedCaptureSizes.put(format, supportedSizes);
}
}
+
+ int captureFormat = ImageFormat.UNKNOWN;
Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface(
config.getOutputConfigurations(), supportedCaptureSizes);
if (burstCaptureSurface != null) {
suitableSurfaceCount++;
+
+ if (Flags.analytics24q3()) {
+ CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo =
+ CameraExtensionUtils.querySurface(burstCaptureSurface);
+ captureFormat = burstCaptureSurfaceInfo.mFormat;
+ }
}
if (suitableSurfaceCount != config.getOutputConfigurations().size()) {
@@ -258,6 +266,9 @@
extensionChars.getAvailableCaptureResultKeys(config.getExtension()),
config.getExtension());
+ if (Flags.analytics24q3()) {
+ session.mStatsAggregator.setCaptureFormat(captureFormat);
+ }
session.mStatsAggregator.setClientName(ctx.getOpPackageName());
session.mStatsAggregator.setExtensionType(config.getExtension());
diff --git a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
index 3050a51..c75e418 100644
--- a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
+++ b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
@@ -70,6 +70,23 @@
}
/**
+ * Set the capture format.
+ *
+ * @param format Format of requested capture.
+ */
+ public void setCaptureFormat(int format) {
+ synchronized (mLock) {
+ if (mIsDone) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "Setting capture format: " + format);
+ }
+ mStats.captureFormat = format;
+ }
+ }
+
+ /**
* Set extension type.
*
* @param extensionType Type of extension. Must match one of
@@ -116,7 +133,8 @@
+ " cameraId: '" + stats.cameraId + "'\n"
+ " clientName: '" + stats.clientName + "'\n"
+ " type: '" + stats.type + "'\n"
- + " isAdvanced: '" + stats.isAdvanced + "'\n";
+ + " isAdvanced: '" + stats.isAdvanced + "'\n"
+ + " captureFormat: '" + stats.captureFormat + "'\n";
}
/**
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index ac043d3..91b05c2 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -1353,9 +1353,6 @@
/**
* Get a snapshot of the real-time status of the devices on the CEC bus.
*
- * <p>This only applies to devices with switch functionality, which are devices with one
- * or more than one HDMI inputs.
- *
* @return a list of {@link HdmiDeviceInfo} of the connected CEC devices on the CEC bus. An
* empty list will be returned if there is none.
*/
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 243ae14..45b316a 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -148,8 +148,6 @@
IInputDeviceBatteryState getBatteryState(int deviceId);
- void setPointerIconType(int typeId);
- void setCustomPointerIcon(in PointerIcon icon);
boolean setPointerIcon(in PointerIcon icon, int displayId, int deviceId, int pointerId,
in IBinder inputToken);
@@ -173,9 +171,9 @@
void removeUniqueIdAssociationByDescriptor(in String inputDeviceDescriptor);
// Add a runtime association between the input device and display, using device's port.
- void addUniqueIdAssociation(in String inputPort, in String displayUniqueId);
+ void addUniqueIdAssociationByPort(in String inputPort, in String displayUniqueId);
// Remove the runtime association between the input device and display, using device's port.
- void removeUniqueIdAssociation(in String inputPort);
+ void removeUniqueIdAssociationByPort(in String inputPort);
InputSensorInfo[] getSensorList(int deviceId);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index dd4ea31..7527aa7 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -992,21 +992,14 @@
}
/**
- * Changes the mouse pointer's icon shape into the specified id.
+ * This method exists for backwards-compatibility, and is a no-op.
*
- * @param iconId The id of the pointer graphic, as a value between
- * {@link PointerIcon#TYPE_ARROW} and {@link PointerIcon#TYPE_HANDWRITING}.
- *
+ * @deprecated
* @hide
*/
@UnsupportedAppUsage
public void setPointerIconType(int iconId) {
- mGlobal.setPointerIconType(iconId);
- }
-
- /** @hide */
- public void setCustomPointerIcon(PointerIcon icon) {
- mGlobal.setCustomPointerIcon(icon);
+ Log.e(TAG, "setPointerIcon: Unsupported app usage!");
}
/** @hide */
@@ -1101,10 +1094,9 @@
*/
@RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
@TestApi
- // TODO(b/324075859): Rename to addUniqueIdAssociationByPort
- public void addUniqueIdAssociation(@NonNull String inputPort,
+ public void addUniqueIdAssociationByPort(@NonNull String inputPort,
@NonNull String displayUniqueId) {
- mGlobal.addUniqueIdAssociation(inputPort, displayUniqueId);
+ mGlobal.addUniqueIdAssociationByPort(inputPort, displayUniqueId);
}
/**
@@ -1117,9 +1109,8 @@
*/
@RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
@TestApi
- // TODO(b/324075859): Rename to removeUniqueIdAssociationByPort
- public void removeUniqueIdAssociation(@NonNull String inputPort) {
- mGlobal.removeUniqueIdAssociation(inputPort);
+ public void removeUniqueIdAssociationByPort(@NonNull String inputPort) {
+ mGlobal.removeUniqueIdAssociationByPort(inputPort);
}
/**
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index a9c97b1..fcd5a3e 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -1411,28 +1411,6 @@
}
/**
- * @see InputManager#setPointerIconType(int)
- */
- public void setPointerIconType(int iconId) {
- try {
- mIm.setPointerIconType(iconId);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
-
- /**
- * @see InputManager#setCustomPointerIcon(PointerIcon)
- */
- public void setCustomPointerIcon(PointerIcon icon) {
- try {
- mIm.setCustomPointerIcon(icon);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
-
- /**
* @see InputManager#setPointerIcon(PointerIcon, int, int, int, IBinder)
*/
public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId,
@@ -1467,22 +1445,23 @@
}
/**
- * @see InputManager#addUniqueIdAssociation(String, String)
+ * @see InputManager#addUniqueIdAssociationByPort(String, String)
*/
- public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
+ public void addUniqueIdAssociationByPort(@NonNull String inputPort,
+ @NonNull String displayUniqueId) {
try {
- mIm.addUniqueIdAssociation(inputPort, displayUniqueId);
+ mIm.addUniqueIdAssociationByPort(inputPort, displayUniqueId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * @see InputManager#removeUniqueIdAssociation(String)
+ * @see InputManager#removeUniqueIdAssociationByPort(String)
*/
- public void removeUniqueIdAssociation(@NonNull String inputPort) {
+ public void removeUniqueIdAssociationByPort(@NonNull String inputPort) {
try {
- mIm.removeUniqueIdAssociation(inputPort);
+ mIm.removeUniqueIdAssociationByPort(inputPort);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 9a63394..49ab15a 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -429,10 +429,9 @@
"Lazy values ref count below 0");
// No more lazy values in mMap, so we can recycle the parcel early rather than
// waiting for the next GC run
- if (mLazyValues == 0) {
- Preconditions.checkState(mWeakParcelledData.get() != null,
- "Parcel recycled earlier than expected");
- recycleParcel(mWeakParcelledData.get());
+ Parcel parcel = mWeakParcelledData.get();
+ if (mLazyValues == 0 && parcel != null) {
+ recycleParcel(parcel);
mWeakParcelledData = null;
}
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c6a9203..2f0d634 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1930,12 +1930,10 @@
public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
/**
- * This user restriction specifies if the user is able to add SIMs to the device.
+ * This user restriction specifies if the user is able to add embedded SIMs to the device.
*
* <p>
- * This restriction blocks the download of embedded SIMs, and disables any physical SIMs.
- * If any embedded SIMs are already on the device, then they are removed. This restriction
- * does not affect SIMs provisioned to the device by device owners or profile owners.
+ * This restriction blocks the download of embedded SIMs.
*
* <p>
* This restriction can only be set by a device owner or a profile owner of an
@@ -1951,6 +1949,7 @@
*
* <p>Key for user restrictions.
* <p>Type: Boolean
+ *
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index b588308..0e28560 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -44,14 +44,6 @@
}
flag {
- name: "attribution_source_constructor"
- is_exported: true
- namespace: "permissions"
- description: "enable AttributionSource(int, int, String, String, IBinder, String[], AttributionSource)"
- bug: "304478648"
-}
-
-flag {
name: "enhanced_confirmation_mode_apis_enabled"
is_exported: true
is_fixed_read_only: true
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e6ddf35..009713f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5123,13 +5123,6 @@
public static final String SCREEN_BRIGHTNESS = "screen_brightness";
/**
- * The screen backlight brightness between 0.0f and 1.0f.
- * @hide
- */
- @Readable
- public static final String SCREEN_BRIGHTNESS_FLOAT = "screen_brightness_float";
-
- /**
* Control whether to enable automatic brightness mode.
*/
@Readable
@@ -6273,7 +6266,6 @@
PUBLIC_SETTINGS.add(DIM_SCREEN);
PUBLIC_SETTINGS.add(SCREEN_OFF_TIMEOUT);
PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS);
- PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_FLOAT);
PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_MODE);
PUBLIC_SETTINGS.add(MODE_RINGER_STREAMS_AFFECTED);
PUBLIC_SETTINGS.add(MUTE_STREAMS_AFFECTED);
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 51758aa..ee5e533 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -81,8 +81,15 @@
}
flag {
- name: "report_primary_auth_attempts"
- namespace: "biometrics"
- description: "Report primary auth attempts from LockSettingsService"
- bug: "285053096"
+ name: "report_primary_auth_attempts"
+ namespace: "biometrics"
+ description: "Report primary auth attempts from LockSettingsService"
+ bug: "285053096"
+}
+
+flag {
+ name: "dump_attestation_verifications"
+ namespace: "hardware_backed_security"
+ description: "Add a dump capability for attestation_verification service"
+ bug: "335498868"
}
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 92f2c32..3cd705a 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -86,7 +86,8 @@
@NonNull Context context,
@NonNull ComponentName serviceComponent,
int userId,
- boolean isSystemProvider)
+ boolean isSystemProvider,
+ boolean isPrimary)
throws PackageManager.NameNotFoundException {
return create(
context,
@@ -94,7 +95,7 @@
isSystemProvider,
/* disableSystemAppVerificationForTests= */ false,
/* isEnabled= */ false,
- /* isPrimary= */ false);
+ isPrimary);
}
/**
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index db665a9..c4becea 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -1392,10 +1392,6 @@
private static Rect computeSafeInsets(int displayW, int displayH, Insets waterFallInsets,
Rect[] bounds) {
- if (displayW == displayH) {
- throw new UnsupportedOperationException("not implemented: display=" + displayW + "x"
- + displayH + " bounding rects=" + Arrays.toString(bounds));
- }
int leftInset = Math.max(waterFallInsets.left, findCutoutInsetForSide(
displayW, displayH, bounds[BOUNDS_POSITION_LEFT], Gravity.LEFT));
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 1c0834f..3743035 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -108,11 +108,6 @@
void dispatchDragEvent(in DragEvent event);
/**
- * Pointer icon events
- */
- void updatePointerIcon(float x, float y);
-
- /**
* Called for non-application windows when the enter animation has completed.
*/
void dispatchWindowShown();
@@ -128,4 +123,9 @@
* @param callbacks to receive responses
*/
void requestScrollCapture(in IScrollCaptureResponseListener callbacks);
+
+ /**
+ * Dump the details of a window.
+ */
+ void dumpWindow(in ParcelFileDescriptor pfd);
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 86264eb..e3e4fc0 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -288,8 +288,6 @@
oneway void finishMovingTask(IWindow window);
- oneway void updatePointerIcon(IWindow window);
-
/**
* Update a tap exclude region identified by provided id in the window. Touches on this region
* will neither be dispatched to this window nor change the focus to this window. Passing an
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 60ad926..1cb2765 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -30695,21 +30695,11 @@
*/
public void setPointerIcon(PointerIcon pointerIcon) {
mMousePointerIcon = pointerIcon;
- if (com.android.input.flags.Flags.enablePointerChoreographer()) {
- final ViewRootImpl viewRootImpl = getViewRootImpl();
- if (viewRootImpl == null) {
- return;
- }
- viewRootImpl.refreshPointerIcon();
- } else {
- if (mAttachInfo == null || mAttachInfo.mHandlingPointerEvent) {
- return;
- }
- try {
- mAttachInfo.mSession.updatePointerIcon(mAttachInfo.mWindow);
- } catch (RemoteException e) {
- }
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl == null) {
+ return;
}
+ viewRootImpl.refreshPointerIcon();
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index fdf3cb1..155c053 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -74,7 +74,6 @@
import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
import static android.view.ViewRootImplProto.WIN_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -96,6 +95,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -122,7 +122,6 @@
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
-import static com.android.input.flags.Flags.enablePointerChoreographer;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.window.flags.Flags.activityWindowInfoFlag;
import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
@@ -195,6 +194,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
@@ -268,11 +268,15 @@
import com.android.internal.os.IResultReceiver;
import com.android.internal.os.SomeArgs;
import com.android.internal.policy.PhoneFallbackEventHandler;
+import com.android.internal.util.FastPrintWriter;
import com.android.internal.view.BaseSurfaceHolder;
import com.android.internal.view.RootViewSurfaceTaker;
import com.android.internal.view.SurfaceCallbackHelper;
import com.android.modules.expresslog.Counter;
+import libcore.io.IoUtils;
+
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
@@ -423,6 +427,12 @@
private static final long NANOS_PER_SEC = 1000000000;
+ // If the ViewRootImpl has been idle for more than 200ms, clear the preferred
+ // frame rate category and frame rate.
+ private static final int IDLE_TIME_MILLIS = 250;
+
+ private static final long NANOS_PER_MILLI = 1_000_000;
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
@@ -655,6 +665,8 @@
private int mMinusOneFrameIntervalMillis = 0;
// VRR interval between the previous and the frame before
private int mMinusTwoFrameIntervalMillis = 0;
+ // VRR has the invalidation idle message been posted?
+ private boolean mInvalidationIdleMessagePosted = false;
/**
* Update the Choreographer's FrameInfo object with the timing information for the current
@@ -4266,6 +4278,10 @@
// when the values are applicable.
if (mDrawnThisFrame) {
mDrawnThisFrame = false;
+ if (!mInvalidationIdleMessagePosted) {
+ mInvalidationIdleMessagePosted = true;
+ mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS);
+ }
setCategoryFromCategoryCounts();
updateInfrequentCount();
setPreferredFrameRate(mPreferredFrameRate);
@@ -6507,6 +6523,8 @@
return "MSG_WINDOW_TOUCH_MODE_CHANGED";
case MSG_KEEP_CLEAR_RECTS_CHANGED:
return "MSG_KEEP_CLEAR_RECTS_CHANGED";
+ case MSG_CHECK_INVALIDATION_IDLE:
+ return "MSG_CHECK_INVALIDATION_IDLE";
case MSG_REFRESH_POINTER_ICON:
return "MSG_REFRESH_POINTER_ICON";
case MSG_TOUCH_BOOST_TIMEOUT:
@@ -6771,6 +6789,30 @@
mNumPausedForSync = 0;
scheduleTraversals();
break;
+ case MSG_CHECK_INVALIDATION_IDLE: {
+ long delta;
+ if (mIsTouchBoosting || mIsFrameRateBoosting || mInsetsAnimationRunning) {
+ delta = 0;
+ } else {
+ delta = System.nanoTime() / NANOS_PER_MILLI - mLastUpdateTimeMillis;
+ }
+ if (delta >= IDLE_TIME_MILLIS) {
+ mFrameRateCategoryHighCount = 0;
+ mFrameRateCategoryHighHintCount = 0;
+ mFrameRateCategoryNormalCount = 0;
+ mFrameRateCategoryLowCount = 0;
+ mPreferredFrameRate = 0;
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ setPreferredFrameRateCategory(FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ setPreferredFrameRate(0f);
+ mInvalidationIdleMessagePosted = false;
+ } else {
+ mInvalidationIdleMessagePosted = true;
+ mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE,
+ IDLE_TIME_MILLIS - delta);
+ }
+ break;
+ }
case MSG_TOUCH_BOOST_TIMEOUT:
/**
* Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
@@ -7937,46 +7979,20 @@
if (event.isStylusPointer() && mIsStylusPointerIconEnabled) {
pointerIcon = mHandwritingInitiator.onResolvePointerIcon(mContext, event);
}
-
if (pointerIcon == null) {
pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);
}
-
- if (enablePointerChoreographer()) {
- if (pointerIcon == null) {
- pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED);
- }
- if (Objects.equals(mResolvedPointerIcon, pointerIcon)) {
- return true;
- }
- mResolvedPointerIcon = pointerIcon;
-
- InputManagerGlobal.getInstance()
- .setPointerIcon(pointerIcon, event.getDisplayId(),
- event.getDeviceId(), event.getPointerId(0), getInputToken());
+ if (pointerIcon == null) {
+ pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED);
+ }
+ if (Objects.equals(mResolvedPointerIcon, pointerIcon)) {
return true;
}
+ mResolvedPointerIcon = pointerIcon;
- final int pointerType = (pointerIcon != null) ?
- pointerIcon.getType() : PointerIcon.TYPE_NOT_SPECIFIED;
-
- if (mPointerIconType == null || mPointerIconType != pointerType) {
- mPointerIconType = pointerType;
- mCustomPointerIcon = null;
- if (mPointerIconType != PointerIcon.TYPE_CUSTOM) {
- InputManagerGlobal
- .getInstance()
- .setPointerIconType(pointerType);
- return true;
- }
- }
- if (mPointerIconType == PointerIcon.TYPE_CUSTOM &&
- !pointerIcon.equals(mCustomPointerIcon)) {
- mCustomPointerIcon = pointerIcon;
- InputManagerGlobal
- .getInstance()
- .setCustomPointerIcon(mCustomPointerIcon);
- }
+ InputManagerGlobal.getInstance()
+ .setPointerIcon(pointerIcon, event.getDisplayId(),
+ event.getDeviceId(), event.getPointerId(0), getInputToken());
return true;
}
@@ -10580,16 +10596,6 @@
mHandler.sendMessage(msg);
}
- public void updatePointerIcon(float x, float y) {
- final int what = MSG_UPDATE_POINTER_ICON;
- mHandler.removeMessages(what);
- final long now = SystemClock.uptimeMillis();
- final MotionEvent event = MotionEvent.obtain(
- 0, now, MotionEvent.ACTION_HOVER_MOVE, x, y, 0);
- Message msg = mHandler.obtainMessage(what, event);
- mHandler.sendMessage(msg);
- }
-
public void dispatchCheckFocus() {
if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {
// This will result in a call to checkFocus() below.
@@ -11453,14 +11459,6 @@
}
@Override
- public void updatePointerIcon(float x, float y) {
- final ViewRootImpl viewAncestor = mViewAncestor.get();
- if (viewAncestor != null) {
- viewAncestor.updatePointerIcon(x, y);
- }
- }
-
- @Override
public void dispatchWindowShown() {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
@@ -11483,6 +11481,26 @@
viewAncestor.dispatchScrollCaptureRequest(listener);
}
}
+
+ @Override
+ public void dumpWindow(ParcelFileDescriptor pfd) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor == null) {
+ return;
+ }
+ viewAncestor.mHandler.postAtFrontOfQueue(() -> {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+ try {
+ PrintWriter pw = new FastPrintWriter(new FileOutputStream(
+ pfd.getFileDescriptor()));
+ viewAncestor.dump("", pw);
+ pw.flush();
+ } finally {
+ IoUtils.closeQuietly(pfd);
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ });
+ }
}
public static final class CalledFromWrongThreadException extends AndroidRuntimeException {
@@ -13016,6 +13034,10 @@
private void removeVrrMessages() {
mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT);
mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
+ if (mInvalidationIdleMessagePosted) {
+ mInvalidationIdleMessagePosted = false;
+ mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE);
+ }
}
/**
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index e6367ff..d7d764b 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -603,10 +603,6 @@
}
@Override
- public void updatePointerIcon(android.view.IWindow window) {
- }
-
- @Override
public void updateTapExcludeRegion(android.view.IWindow window,
android.graphics.Region region) {
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index bfe4e6f..9cc4191 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1978,6 +1978,13 @@
if (Objects.equals(mLastAutofilledData.get(id), value)) {
view.setAutofilled(true, hideHighlight);
+ try {
+ mService.setViewAutofilled(mSessionId, id, mContext.getUserId());
+ } catch (RemoteException e) {
+ // The failure could be a consequence of something going wrong on the
+ // server side. Do nothing here since it's just logging, but it's
+ // possible follow-up actions may fail.
+ }
} else {
view.setAutofilled(false, false);
mLastAutofilledData.remove(id);
@@ -2978,6 +2985,13 @@
mLastAutofilledData.put(view.getAutofillId(), targetValue);
}
view.setAutofilled(true, hideHighlight);
+ try {
+ mService.setViewAutofilled(mSessionId, view.getAutofillId(), mContext.getUserId());
+ } catch (RemoteException e) {
+ // The failure could be a consequence of something going wrong on the server side.
+ // Do nothing here since it's just logging, but it's possible follow-up actions may
+ // fail.
+ }
}
}
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index 1a9322e..2039b4d 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -49,6 +49,7 @@
void updateSession(int sessionId, in AutofillId id, in Rect bounds,
in AutofillValue value, int action, int flags, int userId);
void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId);
+ void setViewAutofilled(int sessionId, in AutofillId id, int userId);
void finishSession(int sessionId, int userId, int commitReason);
void cancelSession(int sessionId, int userId);
void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId);
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index c5114b9..fb3e083 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -720,6 +720,20 @@
private boolean mIsStylusHandwritingEnabled;
+
+ /**
+ * AndroidX Core library 1.13.0 introduced EditorInfoCompat#setStylusHandwritingEnabled and
+ * EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the EditorInfo
+ * extras bundle. These methods do not set or check the Android V property since the Android V
+ * SDK was not yet available. In order for EditorInfoCompat#isStylusHandwritingEnabled to return
+ * the correct value for EditorInfo created by Android V TextView, the extras bundle value
+ * should be set. This is the extras bundle key.
+ *
+ * @hide
+ */
+ public static final String STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY =
+ "androidx.core.view.inputmethod.EditorInfoCompat.STYLUS_HANDWRITING_ENABLED";
+
/**
* Set {@code true} if the {@code Editor} has
* {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index a073873..cf128fb 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2530,18 +2530,6 @@
view, /* delegatorPackageName= */ null, /* handwritingDelegateFlags= */ 0);
}
- private void startStylusHandwritingInternalAsync(
- @NonNull View view, @Nullable String delegatorPackageName,
- @HandwritingDelegateFlags int handwritingDelegateFlags,
- @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
- Objects.requireNonNull(view);
- Objects.requireNonNull(executor);
- Objects.requireNonNull(callback);
-
- startStylusHandwritingInternal(
- view, delegatorPackageName, handwritingDelegateFlags, executor, callback);
- }
-
private void sendFailureCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<Boolean> callback) {
if (executor == null || callback == null) {
@@ -2891,7 +2879,7 @@
if (Flags.homeScreenHandwritingDelegator()) {
flags = delegateView.getHandwritingDelegateFlags();
}
- startStylusHandwritingInternalAsync(
+ acceptStylusHandwritingDelegation(
delegateView, delegatorPackageName, flags, executor, callback);
}
@@ -2926,6 +2914,9 @@
@HandwritingDelegateFlags int flags, @NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<Boolean> callback) {
Objects.requireNonNull(delegatorPackageName);
+ Objects.requireNonNull(delegateView);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
startStylusHandwritingInternal(
delegateView, delegatorPackageName, flags, executor, callback);
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index fa9458d..56e5bcf 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -62,6 +62,17 @@
}
flag {
+ name: "use_input_method_info_safe_list"
+ namespace: "input_method"
+ description: "Use InputMethodInfoSafeList for more reliable binder IPCs"
+ bug: "339761278"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "ime_switcher_revamp"
is_exported: true
namespace: "input_method"
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index a07141b..b7ee0b8 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -520,6 +520,13 @@
* To cancel the request, call <code>filePathCallback.onReceiveValue(null)</code> and
* return {@code true}.
*
+ * <p class="note"><b>Note:</b> WebView does not enforce any restrictions on
+ * the chosen file(s). WebView can access all files that your app can access.
+ * In case the file(s) are chosen through an untrusted source such as a third-party
+ * app, it is your own app's responsibility to check what the returned Uris
+ * refer to before calling the <code>filePathCallback</code>. See
+ * {@link #createIntent} and {@link #parseResult} for more details.</p>
+ *
* @param webView The WebView instance that is initiating the request.
* @param filePathCallback Invoke this callback to supply the list of paths to files to upload,
* or {@code null} to cancel. Must only be called if the
@@ -556,6 +563,15 @@
* Parse the result returned by the file picker activity. This method should be used with
* {@link #createIntent}. Refer to {@link #createIntent} for how to use it.
*
+ * <p class="note"><b>Note:</b> The intent returned by the file picker activity
+ * should be treated as untrusted. A third-party app handling the implicit
+ * intent created by {@link #createIntent} might return Uris that the third-party
+ * app itself does not have access to, such as your own app's sensitive data files.
+ * WebView does not enforce any restrictions on the returned Uris. It is the
+ * app's responsibility to ensure that the untrusted source (such as a third-party
+ * app) has access the Uris it has returned and that the Uris are not pointing
+ * to any sensitive data files.</p>
+ *
* @param resultCode the integer result code returned by the file picker activity.
* @param data the intent returned by the file picker activity.
* @return the Uris of selected file(s) or {@code null} if the resultCode indicates
@@ -618,6 +634,12 @@
* WebChromeClient#onShowFileChooser}</li>
* </ol>
*
+ * <p class="note"><b>Note:</b> The created intent may be handled by
+ * third-party applications on device. The received result must be treated
+ * as untrusted as it can contain Uris pointing to your own app's sensitive
+ * data files. Your app should check the resultant Uris in {@link #parseResult}
+ * before calling the <code>filePathCallback</code>.</p>
+ *
* @return an Intent that supports basic file chooser sources.
*/
public abstract Intent createIntent();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fb1c331..78dd3b1 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -27,6 +27,7 @@
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY;
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
@@ -10062,9 +10063,22 @@
outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
outAttrs.setInitialSurroundingText(mText);
outAttrs.contentMimeTypes = getReceiveContentMimeTypes();
- if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()
- && isAutoHandwritingEnabled()) {
- outAttrs.setStylusHandwritingEnabled(true);
+ if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()) {
+ boolean handwritingEnabled = isAutoHandwritingEnabled();
+ outAttrs.setStylusHandwritingEnabled(handwritingEnabled);
+ // AndroidX Core library 1.13.0 introduced
+ // EditorInfoCompat#setStylusHandwritingEnabled and
+ // EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the
+ // EditorInfo extras bundle. These methods do not set or check the Android V
+ // property since the Android V SDK was not yet available. In order for
+ // EditorInfoCompat#isStylusHandwritingEnabled to return the correct value for
+ // EditorInfo created by Android V TextView, the extras bundle value is also set
+ // here.
+ if (outAttrs.extras == null) {
+ outAttrs.extras = new Bundle();
+ }
+ outAttrs.extras.putBoolean(
+ STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY, handwritingEnabled);
}
ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>();
gestures.add(SelectGesture.class);
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index b83b2d2..fc60f06 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -17,7 +17,6 @@
package com.android.internal.statusbar;
import android.app.Notification;
-import android.app.StatusBarManager;
import android.content.ComponentName;
import android.graphics.drawable.Icon;
import android.graphics.Rect;
@@ -53,9 +52,9 @@
void togglePanel();
@UnsupportedAppUsage
void disable(int what, IBinder token, String pkg);
+ void disableForUser(int what, IBinder token, String pkg, int userId);
void disable2(int what, IBinder token, String pkg);
- void disableForUser(in StatusBarManager.DisableInfo info, IBinder token, String pkg, int userId, String reason);
-
+ void disable2ForUser(int what, IBinder token, String pkg, int userId);
int[] getDisableFlags(IBinder token, int userId);
void setIcon(String slot, String iconPackage, int iconId, int iconLevel, String contentDescription);
@UnsupportedAppUsage
diff --git a/core/java/com/android/internal/util/NewlineNormalizer.java b/core/java/com/android/internal/util/NewlineNormalizer.java
new file mode 100644
index 0000000..0104d1f
--- /dev/null
+++ b/core/java/com/android/internal/util/NewlineNormalizer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+
+import java.util.regex.Pattern;
+
+/**
+ * Utility class that replaces consecutive empty lines with single new line.
+ * @hide
+ */
+public class NewlineNormalizer {
+
+ private static final Pattern MULTIPLE_NEWLINES = Pattern.compile("\\v(\\s*\\v)?");
+
+ // Private constructor to prevent instantiation
+ private NewlineNormalizer() {}
+
+ /**
+ * Replaces consecutive newlines with a single newline in the input text.
+ */
+ public static String normalizeNewlines(String text) {
+ return MULTIPLE_NEWLINES.matcher(text).replaceAll("\n");
+ }
+}
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index e33704b..3fc4fff 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -18,7 +18,6 @@
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
-import android.hardware.input.InputManagerGlobal;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -29,7 +28,6 @@
import android.view.IWindowSession;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.PointerIcon;
import android.view.ScrollCaptureResponse;
import android.view.WindowInsets.Type.InsetsType;
import android.view.inputmethod.ImeTracker;
@@ -128,12 +126,6 @@
}
@Override
- public void updatePointerIcon(float x, float y) {
- InputManagerGlobal.getInstance()
- .setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
- }
-
- @Override
public void dispatchWallpaperCommand(String action, int x, int y,
int z, Bundle extras, boolean sync) {
if (sync) {
@@ -162,4 +154,9 @@
// ignore
}
}
+
+ @Override
+ public void dumpWindow(ParcelFileDescriptor pfd) {
+
+ }
}
diff --git a/core/jni/android_hardware_display_DisplayViewport.cpp b/core/jni/android_hardware_display_DisplayViewport.cpp
index 7f630cb..5d7b33e 100644
--- a/core/jni/android_hardware_display_DisplayViewport.cpp
+++ b/core/jni/android_hardware_display_DisplayViewport.cpp
@@ -59,7 +59,8 @@
static const jclass intClass = FindClassOrDie(env, "java/lang/Integer");
static const jmethodID byteValue = env->GetMethodID(intClass, "byteValue", "()B");
- viewport->displayId = env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId);
+ viewport->displayId = ui::LogicalDisplayId{
+ env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId)};
viewport->isActive = env->GetBooleanField(viewportObj, gDisplayViewportClassInfo.isActive);
jint orientation = env->GetIntField(viewportObj, gDisplayViewportClassInfo.orientation);
viewport->orientation = static_cast<ui::Rotation>(orientation);
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index bed7768..69f6334 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -165,8 +165,8 @@
mInfo.ownerUid = gui::Uid{
static_cast<uid_t>(env->GetIntField(obj, gInputWindowHandleClassInfo.ownerUid))};
mInfo.packageName = getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>");
- mInfo.displayId = env->GetIntField(obj,
- gInputWindowHandleClassInfo.displayId);
+ mInfo.displayId =
+ ui::LogicalDisplayId{env->GetIntField(obj, gInputWindowHandleClassInfo.displayId)};
jobject inputApplicationHandleObj = env->GetObjectField(obj,
gInputWindowHandleClassInfo.inputApplicationHandle);
diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp
index ca8752f..06e0d2d 100644
--- a/core/jni/android_view_KeyEvent.cpp
+++ b/core/jni/android_view_KeyEvent.cpp
@@ -135,8 +135,8 @@
jlong eventTime = env->GetLongField(eventObj, gKeyEventClassInfo.mEventTime);
KeyEvent event;
- event.initialize(id, deviceId, source, displayId, *hmac, action, flags, keyCode, scanCode,
- metaState, repeatCount, downTime, eventTime);
+ event.initialize(id, deviceId, source, ui::LogicalDisplayId{displayId}, *hmac, action, flags,
+ keyCode, scanCode, metaState, repeatCount, downTime, eventTime);
return event;
}
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 3e3af40..f914bee 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -22,7 +22,6 @@
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <attestation/HmacKeyManager.h>
-#include <gui/constants.h>
#include <input/Input.h>
#include <log/log.h>
#include <nativehelper/JNIHelp.h>
@@ -367,8 +366,8 @@
ui::Transform transform;
transform.set(xOffset, yOffset);
ui::Transform identityTransform;
- event->initialize(InputEvent::nextId(), deviceId, source, displayId, INVALID_HMAC, action, 0,
- flags, edgeFlags, metaState, buttonState,
+ event->initialize(InputEvent::nextId(), deviceId, source, ui::LogicalDisplayId{displayId},
+ INVALID_HMAC, action, 0, flags, edgeFlags, metaState, buttonState,
static_cast<MotionClassification>(classification), transform, xPrecision,
yPrecision, AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, downTimeNanos,
@@ -646,13 +645,13 @@
static jint android_view_MotionEvent_nativeGetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getDisplayId();
+ return static_cast<jint>(event->getDisplayId().val());
}
static void android_view_MotionEvent_nativeSetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
jint displayId) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->setDisplayId(displayId);
+ event->setDisplayId(ui::LogicalDisplayId{displayId});
}
static jint android_view_MotionEvent_nativeGetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp
index bc69d1e6..c39d5e2 100644
--- a/core/jni/android_window_WindowInfosListener.cpp
+++ b/core/jni/android_window_WindowInfosListener.cpp
@@ -60,7 +60,7 @@
}
ScopedLocalRef<jobject> matrixObj(env, AMatrix_newInstance(env, transformValues));
return env->NewObject(gDisplayInfoClassInfo.clazz, gDisplayInfoClassInfo.ctor,
- displayInfo.displayId, displayInfo.logicalWidth,
+ displayInfo.displayId.val(), displayInfo.logicalWidth,
displayInfo.logicalHeight, matrixObj.get());
}
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 12d62cc..062fab3 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -116,7 +116,7 @@
using android::zygote::ZygoteFailure;
-using Action = android_mallopt_gwp_asan_options_t::Action;
+using Mode = android_mallopt_gwp_asan_options_t::Mode;
// This type is duplicated in fd_utils.h
typedef const std::function<void(std::string)>& fail_fn_t;
@@ -2101,21 +2101,21 @@
switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) {
default:
case RuntimeFlags::GWP_ASAN_LEVEL_DEFAULT:
- gwp_asan_options.desire = GetBoolProperty(kGwpAsanAppRecoverableSysprop, true)
- ? Action::TURN_ON_FOR_APP_SAMPLED_NON_CRASHING
- : Action::DONT_TURN_ON_UNLESS_OVERRIDDEN;
+ gwp_asan_options.mode = GetBoolProperty(kGwpAsanAppRecoverableSysprop, true)
+ ? Mode::APP_MANIFEST_DEFAULT
+ : Mode::APP_MANIFEST_NEVER;
android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options));
break;
case RuntimeFlags::GWP_ASAN_LEVEL_NEVER:
- gwp_asan_options.desire = Action::DONT_TURN_ON_UNLESS_OVERRIDDEN;
+ gwp_asan_options.mode = Mode::APP_MANIFEST_NEVER;
android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options));
break;
case RuntimeFlags::GWP_ASAN_LEVEL_ALWAYS:
- gwp_asan_options.desire = Action::TURN_ON_FOR_APP;
+ gwp_asan_options.mode = Mode::APP_MANIFEST_ALWAYS;
android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options));
break;
case RuntimeFlags::GWP_ASAN_LEVEL_LOTTERY:
- gwp_asan_options.desire = Action::TURN_ON_WITH_SAMPLING;
+ gwp_asan_options.mode = Mode::APP_MANIFEST_DEFAULT;
android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options));
break;
}
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 0433855..bf2fdda 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -329,7 +329,8 @@
InputDeviceInfo info = InputDeviceInfo();
info.initialize(keyboardId, 0, 0, InputDeviceIdentifier(),
- "keyboard " + std::to_string(keyboardId), true, false, 0);
+ "keyboard " + std::to_string(keyboardId), true, false,
+ ui::ADISPLAY_ID_DEFAULT);
info.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC);
info.setKeyCharacterMap(*charMap);
diff --git a/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml b/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml
index c0fe536..7c45c20 100644
--- a/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml
@@ -22,7 +22,11 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,7v13H7L20,7 M22,2L2,22h20V2L22,2z" />
- <path
- android:fillColor="@android:color/white"
- android:pathData="M 11 13 L 2 22 L 11 22 Z" />
+ <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z">
+ <!-- 1 bar. move to higher ground. -->
+ <path
+ android:name="ic_signal_cellular_1_4_bar"
+ android:fillColor="@android:color/white"
+ android:pathData="M6,0 H11 V20 H6 z" />
+ </clip-path>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml
index 816da22..02b646d 100644
--- a/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml
@@ -22,7 +22,11 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" />
- <path
- android:fillColor="@android:color/white"
- android:pathData="M8.72,15.28,2,22H8.72V15.28Z" />
+ <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z">
+ <!-- 1 bar. might have to call you back. -->
+ <path
+ android:name="ic_signal_cellular_1_5_bar"
+ android:fillColor="@android:color/white"
+ android:pathData="M6,0 H12 V20 H6 z" />
+ </clip-path>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml b/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml
index 69a966b..514d169 100644
--- a/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml
@@ -22,7 +22,11 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,7v13H7L20,7 M22,2L2,22h20V2L22,2z" />
- <path
- android:fillColor="@android:color/white"
- android:pathData="M 13 11 L 2 22 L 13 22 Z" />
+ <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z">
+ <!-- 2 bars. 2 out of 4 ain't bad. -->
+ <path
+ android:name="ic_signal_cellular_2_4_bar"
+ android:fillColor="@android:color/white"
+ android:pathData="M6,0 H14 V20 H6 z" />
+ </clip-path>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml
index 02c7a43..a97f771 100644
--- a/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml
@@ -23,7 +23,11 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" />
- <path
- android:fillColor="@android:color/white"
- android:pathData="M 11.45 12.55 L 2 22 L 11.45 22 L 11.45 12.55 Z" />
+ <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z">
+ <!-- 2 bars. hanging in there. -->
+ <path
+ android:name="ic_signal_cellular_2_5_bar"
+ android:fillColor="@android:color/white"
+ android:pathData="M6,0 H14 V20 H6 z" />
+ </clip-path>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml b/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml
index 46ce47c..1bacf4a 100644
--- a/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml
@@ -22,7 +22,11 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,7v13H7L20,7 M22,2L2,22h20V2L22,2z" />
- <path
- android:fillColor="@android:color/white"
- android:pathData="M 2 22 L 16 22 L 16 21 L 16 20 L 16 11 L 16 10 L 16 8 Z" />
+ <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z">
+ <!-- 3 bars. quite nice. -->
+ <path
+ android:name="ic_signal_cellular_3_4_bar"
+ android:fillColor="@android:color/white"
+ android:pathData="M6,0 H17 V20 H6 z" />
+ </clip-path>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml
index 37435e6b..2789d3e 100644
--- a/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml
@@ -22,7 +22,11 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" />
- <path
- android:fillColor="@android:color/white"
- android:pathData="M 14.96 9.04 L 2 22 L 14.96 22 L 14.96 9.04 Z" />
+ <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z">
+ <!-- 3 bars. not great, not terrible. -->
+ <path
+ android:name="ic_signal_cellular_3_5_bar"
+ android:fillColor="@android:color/white"
+ android:pathData="M6,0 H16 V20 H6 z" />
+ </clip-path>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml
index 6dc3646..8286dbb 100644
--- a/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml
@@ -22,7 +22,11 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" />
- <path
- android:fillColor="@android:color/white"
- android:pathData="M 18.48 5.52 L 2 22 L 18.48 22 L 18.48 5.52 Z" />
+ <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z">
+ <!-- 4 bars. extremely respectable. -->
+ <path
+ android:name="ic_signal_cellular_4_5_bar"
+ android:fillColor="@android:color/white"
+ android:pathData="M6,0 H18 V20 H6 z" />
+ </clip-path>
</vector>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 0b1b40c..bc0ae9f 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -623,6 +623,28 @@
assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT, mViewRoot.getLastPreferredFrameRateCategory());
}
+ @Test
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
+ })
+ public void idleDetected() throws Throwable {
+ waitForFrameRateCategoryToSettle();
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_HIGH);
+ mMovingView.setFrameContentVelocity(Float.MAX_VALUE);
+ mMovingView.invalidate();
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_HIGH,
+ mViewRoot.getLastPreferredFrameRateCategory()));
+ });
+ waitForAfterDraw();
+
+ // Wait for idle timeout
+ Thread.sleep(500);
+ assertEquals(0f, mViewRoot.getLastPreferredFrameRate());
+ assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
+ mViewRoot.getLastPreferredFrameRateCategory());
+ }
+
private void runAfterDraw(@NonNull Runnable runnable) {
Handler handler = new Handler(Looper.getMainLooper());
mAfterDrawLatch = new CountDownLatch(1);
diff --git a/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java b/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java
new file mode 100644
index 0000000..bcdac61
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import static junit.framework.Assert.assertEquals;
+
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for {@link NewlineNormalizer}
+ * @hide
+ */
+@DisabledOnRavenwood(blockedBy = NewlineNormalizer.class)
+@RunWith(AndroidJUnit4.class)
+public class NewlineNormalizerTest {
+
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ @Test
+ public void testEmptyInput() {
+ assertEquals("", NewlineNormalizer.normalizeNewlines(""));
+ }
+
+ @Test
+ public void testSingleNewline() {
+ assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n"));
+ }
+
+ @Test
+ public void testMultipleConsecutiveNewlines() {
+ assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n\n\n\n\n"));
+ }
+
+ @Test
+ public void testNewlinesWithSpacesAndTabs() {
+ String input = "Line 1\n \n \t \n\tLine 2";
+ // Adjusted expected output to include the tab character
+ String expected = "Line 1\n\tLine 2";
+ assertEquals(expected, NewlineNormalizer.normalizeNewlines(input));
+ }
+
+ @Test
+ public void testMixedNewlineCharacters() {
+ String input = "Line 1\r\nLine 2\u000BLine 3\fLine 4\u2028Line 5\u2029Line 6";
+ String expected = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6";
+ assertEquals(expected, NewlineNormalizer.normalizeNewlines(input));
+ }
+}
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
index 635e78e..cc5b3b9 100644
--- a/graphics/java/android/graphics/RecordingCanvas.java
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -40,7 +40,7 @@
/** @hide */
private static int getPanelFrameSize() {
- final int DefaultSize = 100 * 1024 * 1024; // 100 MB;
+ final int DefaultSize = 150 * 1024 * 1024; // 150 MB;
return Math.max(SystemProperties.getInt("ro.hwui.max_texture_allocation_size", DefaultSize),
DefaultSize);
}
@@ -262,7 +262,7 @@
protected void throwIfCannotDraw(Bitmap bitmap) {
super.throwIfCannotDraw(bitmap);
int bitmapSize = bitmap.getByteCount();
- if (bitmapSize > MAX_BITMAP_SIZE) {
+ if (bitmap.getConfig() != Bitmap.Config.HARDWARE && bitmapSize > MAX_BITMAP_SIZE) {
throw new RuntimeException(
"Canvas: trying to draw too large(" + bitmapSize + "bytes) bitmap.");
}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
deleted file mode 100644
index d1d7c14..0000000
--- a/keystore/java/android/security/KeyStore.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2009 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.security;
-
-/**
- * This class provides some constants and helper methods related to Android's Keystore service.
- * This class was originally much larger, but its functionality was superseded by other classes.
- * It now just contains a few remaining pieces for which the users haven't been updated yet.
- * You may be looking for {@link java.security.KeyStore} instead.
- *
- * @hide
- */
-public class KeyStore {
-
- // Used for UID field to indicate the calling UID.
- public static final int UID_SELF = -1;
-}
diff --git a/ktfmt_includes.txt b/ktfmt_includes.txt
index fe47503..0ac6265 100644
--- a/ktfmt_includes.txt
+++ b/ktfmt_includes.txt
@@ -5,8 +5,6 @@
-packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
-packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
-packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
-packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
-packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
-packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java
index d923a46..d241641 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java
@@ -16,6 +16,8 @@
package androidx.window.common;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
@@ -26,30 +28,30 @@
*/
public class EmptyLifecycleCallbacksAdapter implements Application.ActivityLifecycleCallbacks {
@Override
- public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+ public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
}
@Override
- public void onActivityStarted(Activity activity) {
+ public void onActivityStarted(@NonNull Activity activity) {
}
@Override
- public void onActivityResumed(Activity activity) {
+ public void onActivityResumed(@NonNull Activity activity) {
}
@Override
- public void onActivityPaused(Activity activity) {
+ public void onActivityPaused(@NonNull Activity activity) {
}
@Override
- public void onActivityStopped(Activity activity) {
+ public void onActivityStopped(@NonNull Activity activity) {
}
@Override
- public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+ public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
}
@Override
- public void onActivityDestroyed(Activity activity) {
+ public void onActivityDestroyed(@NonNull Activity activity) {
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index a0d6fce..e93b0bf 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -35,6 +35,7 @@
import android.annotation.DimenRes;
import android.annotation.Nullable;
+import android.app.Activity;
import android.app.ActivityThread;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -42,6 +43,7 @@
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RotateDrawable;
import android.hardware.display.DisplayManager;
@@ -213,7 +215,11 @@
isVerticalSplit,
isReversedLayout,
parentInfo.getDisplayId(),
- isDraggableExpandType
+ isDraggableExpandType,
+ getContainerBackgroundColor(topSplitContainer.getPrimaryContainer(),
+ DEFAULT_PRIMARY_VEIL_COLOR),
+ getContainerBackgroundColor(topSplitContainer.getSecondaryContainer(),
+ DEFAULT_SECONDARY_VEIL_COLOR)
));
}
}
@@ -242,6 +248,27 @@
}
/**
+ * Returns the window background color of the top activity in the container if set, or the
+ * default color if the background color of the top activity is unavailable.
+ */
+ @VisibleForTesting
+ @NonNull
+ static Color getContainerBackgroundColor(
+ @NonNull TaskFragmentContainer container, @NonNull Color defaultColor) {
+ final Activity activity = container.getTopNonFinishingActivity();
+ if (activity == null || !activity.isResumed()) {
+ // This can happen when the top activity in the container is from a different process.
+ return defaultColor;
+ }
+
+ final Drawable drawable = activity.getWindow().getDecorView().getBackground();
+ if (drawable instanceof ColorDrawable colorDrawable) {
+ return Color.valueOf(colorDrawable.getColor());
+ }
+ return defaultColor;
+ }
+
+ /**
* Creates a decor surface for the TaskFragment if no decor surface exists, or changes the owner
* of the existing decor surface to be the specified TaskFragment.
*
@@ -439,10 +466,6 @@
}
if (dividerAttributes.getDividerType() == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
- // Draggable divider width must be larger than the drag handle size.
- widthDp = Math.max(widthDp,
- getDimensionDp(R.dimen.activity_embedding_divider_touch_target_width));
-
// Update minRatio and maxRatio only when it is a draggable divider.
if (minRatio == RATIO_SYSTEM_DEFAULT) {
minRatio = DEFAULT_MIN_RATIO;
@@ -800,6 +823,8 @@
private final int mDisplayId;
private final boolean mIsReversedLayout;
private final boolean mIsDraggableExpandType;
+ private final Color mPrimaryVeilColor;
+ private final Color mSecondaryVeilColor;
@VisibleForTesting
Properties(
@@ -810,7 +835,9 @@
boolean isVerticalSplit,
boolean isReversedLayout,
int displayId,
- boolean isDraggableExpandType) {
+ boolean isDraggableExpandType,
+ @NonNull Color primaryVeilColor,
+ @NonNull Color secondaryVeilColor) {
mConfiguration = configuration;
mDividerAttributes = dividerAttributes;
mDecorSurface = decorSurface;
@@ -819,6 +846,8 @@
mIsReversedLayout = isReversedLayout;
mDisplayId = displayId;
mIsDraggableExpandType = isDraggableExpandType;
+ mPrimaryVeilColor = primaryVeilColor;
+ mSecondaryVeilColor = secondaryVeilColor;
}
/**
@@ -840,7 +869,9 @@
&& a.mIsVerticalSplit == b.mIsVerticalSplit
&& a.mDisplayId == b.mDisplayId
&& a.mIsReversedLayout == b.mIsReversedLayout
- && a.mIsDraggableExpandType == b.mIsDraggableExpandType;
+ && a.mIsDraggableExpandType == b.mIsDraggableExpandType
+ && a.mPrimaryVeilColor.equals(b.mPrimaryVeilColor)
+ && a.mSecondaryVeilColor.equals(b.mSecondaryVeilColor);
}
private static boolean areSameSurfaces(
@@ -877,17 +908,21 @@
@NonNull
private final FrameLayout mDividerLayout;
@NonNull
+ private final View mDividerLine;
+ private View mDragHandle;
+ @NonNull
private final View.OnTouchListener mListener;
@NonNull
private Properties mProperties;
private int mDividerWidthPx;
+ private int mHandleWidthPx;
@Nullable
private SurfaceControl mPrimaryVeil;
@Nullable
private SurfaceControl mSecondaryVeil;
private boolean mIsDragging;
private int mDividerPosition;
- private View mDragHandle;
+ private int mDividerSurfaceWidthPx;
private Renderer(@NonNull Properties properties, @NonNull View.OnTouchListener listener) {
mProperties = properties;
@@ -905,6 +940,7 @@
context, displayManager.getDisplay(mProperties.mDisplayId),
mWindowlessWindowManager, "DividerContainer");
mDividerLayout = new FrameLayout(context);
+ mDividerLine = new View(context);
update();
}
@@ -921,6 +957,17 @@
mDividerWidthPx = getDividerWidthPx(mProperties.mDividerAttributes);
mDividerPosition = mProperties.mInitialDividerPosition;
mWindowlessWindowManager.setConfiguration(mProperties.mConfiguration);
+
+ if (mProperties.mDividerAttributes.getDividerType()
+ == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
+ // TODO(b/329193115) support divider on secondary display
+ final Context context = ActivityThread.currentActivityThread().getApplication();
+ mHandleWidthPx = context.getResources().getDimensionPixelSize(
+ R.dimen.activity_embedding_divider_touch_target_width);
+ } else {
+ mHandleWidthPx = 0;
+ }
+
// TODO handle synchronization between surface transactions and WCT.
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
updateSurface(t);
@@ -950,13 +997,45 @@
*/
private void updateSurface(@NonNull SurfaceControl.Transaction t) {
final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
- if (mProperties.mIsVerticalSplit) {
- t.setPosition(mDividerSurface, mDividerPosition, 0.0f);
- t.setWindowCrop(mDividerSurface, mDividerWidthPx, taskBounds.height());
+
+ int dividerSurfacePosition;
+ if (mProperties.mDividerAttributes.getDividerType()
+ == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
+ // When the divider drag handle width is larger than the divider width, the position
+ // of the divider surface is adjusted so that it is large enough to host both the
+ // divider line and the divider drag handle.
+ mDividerSurfaceWidthPx = Math.max(mDividerWidthPx, mHandleWidthPx);
+ dividerSurfacePosition =
+ mProperties.mIsReversedLayout
+ ? mDividerPosition
+ : mDividerPosition + mDividerWidthPx - mDividerSurfaceWidthPx;
+ dividerSurfacePosition = Math.clamp(dividerSurfacePosition, 0,
+ mProperties.mIsVerticalSplit ? taskBounds.width() : taskBounds.height());
} else {
- t.setPosition(mDividerSurface, 0.0f, mDividerPosition);
- t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerWidthPx);
+ mDividerSurfaceWidthPx = mDividerWidthPx;
+ dividerSurfacePosition = mDividerPosition;
}
+
+ if (mProperties.mIsVerticalSplit) {
+ t.setPosition(mDividerSurface, dividerSurfacePosition, 0.0f);
+ t.setWindowCrop(mDividerSurface, mDividerSurfaceWidthPx, taskBounds.height());
+ } else {
+ t.setPosition(mDividerSurface, 0.0f, dividerSurfacePosition);
+ t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerSurfaceWidthPx);
+ }
+
+ // Update divider line position in the surface
+ if (!mProperties.mIsReversedLayout) {
+ final int offset = mDividerPosition - dividerSurfacePosition;
+ mDividerLine.setX(mProperties.mIsVerticalSplit ? offset : 0);
+ mDividerLine.setY(mProperties.mIsVerticalSplit ? 0 : offset);
+ } else {
+ // For reversed layout, the divider line is always at the start of the divider
+ // surface.
+ mDividerLine.setX(0);
+ mDividerLine.setY(0);
+ }
+
if (mIsDragging) {
updateVeils(t);
}
@@ -971,14 +1050,14 @@
final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
final WindowManager.LayoutParams lp = mProperties.mIsVerticalSplit
? new WindowManager.LayoutParams(
- mDividerWidthPx,
+ mDividerSurfaceWidthPx,
taskBounds.height(),
TYPE_APPLICATION_PANEL,
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT)
: new WindowManager.LayoutParams(
taskBounds.width(),
- mDividerWidthPx,
+ mDividerSurfaceWidthPx,
TYPE_APPLICATION_PANEL,
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
@@ -995,12 +1074,19 @@
*/
private void updateDivider(@NonNull SurfaceControl.Transaction t) {
mDividerLayout.removeAllViews();
- if (mProperties.mIsDraggableExpandType) {
+ mDividerLayout.addView(mDividerLine);
+ if (mProperties.mIsDraggableExpandType && !mIsDragging) {
// If a container is fully expanded, the divider overlays on the expanded container.
- mDividerLayout.setBackgroundColor(Color.TRANSPARENT);
+ mDividerLine.setBackgroundColor(Color.TRANSPARENT);
} else {
- mDividerLayout.setBackgroundColor(mProperties.mDividerAttributes.getDividerColor());
+ mDividerLine.setBackgroundColor(mProperties.mDividerAttributes.getDividerColor());
}
+ final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
+ mDividerLine.setLayoutParams(
+ mProperties.mIsVerticalSplit
+ ? new FrameLayout.LayoutParams(mDividerWidthPx, taskBounds.height())
+ : new FrameLayout.LayoutParams(taskBounds.width(), mDividerWidthPx)
+ );
if (mProperties.mDividerAttributes.getDividerType()
== DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
createVeils();
@@ -1027,7 +1113,7 @@
R.dimen.activity_embedding_divider_touch_target_width));
params.gravity = Gravity.CENTER;
button.setLayoutParams(params);
- button.setBackgroundColor(R.color.transparent);
+ button.setBackgroundColor(Color.TRANSPARENT);
final Drawable handle = context.getResources().getDrawable(
R.drawable.activity_embedding_divider_handle, context.getTheme());
@@ -1087,8 +1173,8 @@
}
private void showVeils(@NonNull SurfaceControl.Transaction t) {
- t.setColor(mPrimaryVeil, colorToFloatArray(DEFAULT_PRIMARY_VEIL_COLOR))
- .setColor(mSecondaryVeil, colorToFloatArray(DEFAULT_SECONDARY_VEIL_COLOR))
+ t.setColor(mPrimaryVeil, colorToFloatArray(mProperties.mPrimaryVeilColor))
+ .setColor(mSecondaryVeil, colorToFloatArray(mProperties.mSecondaryVeilColor))
.setLayer(mDividerSurface, DIVIDER_LAYER)
.setLayer(mPrimaryVeil, VEIL_LAYER)
.setLayer(mSecondaryVeil, VEIL_LAYER)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 14388a6..5b0e6b9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -855,7 +855,9 @@
if (!parentInfo.isVisible()) {
// Only making the TaskContainer invisible and drops the other info, and perform the
// update when the next time the Task becomes visible.
- taskContainer.setIsVisible(false);
+ if (taskContainer.isVisible()) {
+ taskContainer.setInvisible();
+ }
return;
}
@@ -3228,10 +3230,8 @@
@NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) {
final DividerPresenter dividerPresenter = mDividerPresenters.get(taskContainer.getTaskId());
final TaskFragmentParentInfo parentInfo = taskContainer.getTaskFragmentParentInfo();
- if (parentInfo != null) {
- dividerPresenter.updateDivider(
- wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer());
- }
+ dividerPresenter.updateDivider(
+ wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer());
}
@Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index a683738..c708da9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -78,16 +78,7 @@
private TaskFragmentContainer mAlwaysOnTopOverlayContainer;
@NonNull
- private final Configuration mConfiguration;
-
- private int mDisplayId;
-
- private boolean mIsVisible;
-
- private boolean mHasDirectActivity;
-
- @Nullable
- private TaskFragmentParentInfo mTaskFragmentParentInfo;
+ private TaskFragmentParentInfo mInfo;
/**
* TaskFragments that the organizer has requested to be closed. They should be removed when
@@ -131,12 +122,14 @@
mTaskId = taskId;
final TaskProperties taskProperties = TaskProperties
.getTaskPropertiesFromActivity(activityInTask);
- mConfiguration = taskProperties.getConfiguration();
- mDisplayId = taskProperties.getDisplayId();
- // Note that it is always called when there's a new Activity is started, which implies
- // the host task is visible and has an activity in the task.
- mIsVisible = true;
- mHasDirectActivity = true;
+ mInfo = new TaskFragmentParentInfo(
+ taskProperties.getConfiguration(),
+ taskProperties.getDisplayId(),
+ // Note that it is always called when there's a new Activity is started, which
+ // implies the host task is visible and has an activity in the task.
+ true /* visible */,
+ true /* hasDirectActivity */,
+ null /* decorSurface */);
}
int getTaskId() {
@@ -144,43 +137,39 @@
}
int getDisplayId() {
- return mDisplayId;
+ return mInfo.getDisplayId();
}
boolean isVisible() {
- return mIsVisible;
+ return mInfo.isVisible();
}
- void setIsVisible(boolean visible) {
- mIsVisible = visible;
+ void setInvisible() {
+ mInfo = new TaskFragmentParentInfo(mInfo.getConfiguration(), mInfo.getDisplayId(),
+ false /* visible */, mInfo.hasDirectActivity(), mInfo.getDecorSurface());
}
boolean hasDirectActivity() {
- return mHasDirectActivity;
+ return mInfo.hasDirectActivity();
}
@NonNull
Rect getBounds() {
- return mConfiguration.windowConfiguration.getBounds();
+ return mInfo.getConfiguration().windowConfiguration.getBounds();
}
@NonNull
TaskProperties getTaskProperties() {
- return new TaskProperties(mDisplayId, mConfiguration);
+ return new TaskProperties(mInfo.getDisplayId(), mInfo.getConfiguration());
}
void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
- // TODO(b/293654166): cache the TaskFragmentParentInfo and remove these fields.
- mConfiguration.setTo(info.getConfiguration());
- mDisplayId = info.getDisplayId();
- mIsVisible = info.isVisible();
- mHasDirectActivity = info.hasDirectActivity();
- mTaskFragmentParentInfo = info;
+ mInfo = info;
}
- @Nullable
+ @NonNull
TaskFragmentParentInfo getTaskFragmentParentInfo() {
- return mTaskFragmentParentInfo;
+ return mInfo;
}
/**
@@ -196,8 +185,8 @@
// If the task properties equals regardless of starting position, don't
// need to update the container.
- return mConfiguration.diffPublicOnly(configuration) != 0
- || mDisplayId != info.getDisplayId();
+ return mInfo.getConfiguration().diffPublicOnly(configuration) != 0
+ || mInfo.getDisplayId() != info.getDisplayId();
}
/**
@@ -224,7 +213,7 @@
}
boolean isInPictureInPicture() {
- return isInPictureInPicture(mConfiguration);
+ return isInPictureInPicture(mInfo.getConfiguration());
}
private static boolean isInPictureInPicture(@NonNull Configuration configuration) {
@@ -237,7 +226,7 @@
@WindowingMode
private int getWindowingMode() {
- return mConfiguration.windowConfiguration.getWindowingMode();
+ return mInfo.getConfiguration().windowConfiguration.getWindowingMode();
}
/** Whether there is any {@link TaskFragmentContainer} below this Task. */
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index 56c3bce..339908a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -16,16 +16,10 @@
package androidx.window.sidecar;
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
-import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
-
+import android.annotation.Nullable;
import android.app.Activity;
-import android.app.ActivityThread;
import android.app.Application;
import android.content.Context;
-import android.graphics.Rect;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
import android.os.IBinder;
@@ -38,7 +32,6 @@
import androidx.window.util.BaseDataProducer;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
/**
@@ -76,64 +69,13 @@
@NonNull
@Override
public SidecarDeviceState getDeviceState() {
- SidecarDeviceState deviceState = new SidecarDeviceState();
- deviceState.posture = deviceStateFromFeature();
- return deviceState;
- }
-
- private int deviceStateFromFeature() {
- for (int i = 0; i < mStoredFeatures.size(); i++) {
- CommonFoldingFeature feature = mStoredFeatures.get(i);
- final int state = feature.getState();
- switch (state) {
- case CommonFoldingFeature.COMMON_STATE_FLAT:
- return SidecarDeviceState.POSTURE_OPENED;
- case CommonFoldingFeature.COMMON_STATE_HALF_OPENED:
- return SidecarDeviceState.POSTURE_HALF_OPENED;
- case CommonFoldingFeature.COMMON_STATE_UNKNOWN:
- return SidecarDeviceState.POSTURE_UNKNOWN;
- }
- }
- return SidecarDeviceState.POSTURE_UNKNOWN;
+ return SidecarHelper.calculateDeviceState(mStoredFeatures);
}
@NonNull
@Override
public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
- Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken);
- SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo();
- if (activity == null) {
- return windowLayoutInfo;
- }
- windowLayoutInfo.displayFeatures = getDisplayFeatures(activity);
- return windowLayoutInfo;
- }
-
- private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
- int displayId = activity.getDisplay().getDisplayId();
- if (displayId != DEFAULT_DISPLAY) {
- return Collections.emptyList();
- }
-
- if (activity.isInMultiWindowMode()) {
- // It is recommended not to report any display features in multi-window mode, since it
- // won't be possible to synchronize the display feature positions with window movement.
- return Collections.emptyList();
- }
-
- List<SidecarDisplayFeature> features = new ArrayList<>();
- final int rotation = activity.getResources().getConfiguration().windowConfiguration
- .getDisplayRotation();
- for (CommonFoldingFeature baseFeature : mStoredFeatures) {
- SidecarDisplayFeature feature = new SidecarDisplayFeature();
- Rect featureRect = baseFeature.getRect();
- rotateRectToDisplayRotation(displayId, rotation, featureRect);
- transformToWindowSpaceRect(activity, featureRect);
- feature.setRect(featureRect);
- feature.setType(baseFeature.getType());
- features.add(feature);
- }
- return Collections.unmodifiableList(features);
+ return SidecarHelper.calculateWindowLayoutInfo(windowToken, mStoredFeatures);
}
@Override
@@ -145,13 +87,14 @@
private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter {
@Override
- public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+ public void onActivityCreated(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState) {
super.onActivityCreated(activity, savedInstanceState);
onDisplayFeaturesChangedForActivity(activity);
}
@Override
- public void onActivityConfigurationChanged(Activity activity) {
+ public void onActivityConfigurationChanged(@NonNull Activity activity) {
super.onActivityConfigurationChanged(activity);
onDisplayFeaturesChangedForActivity(activity);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
new file mode 100644
index 0000000..bb6ab47
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.window.sidecar;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
+import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.ActivityThread;
+import android.graphics.Rect;
+import android.os.IBinder;
+
+import androidx.window.common.CommonFoldingFeature;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A utility class for transforming between Sidecar and Extensions features.
+ */
+class SidecarHelper {
+
+ private SidecarHelper() {}
+
+ /**
+ * Returns the {@link SidecarDeviceState} posture that is calculated for the first fold in
+ * the feature list. Sidecar devices only have one fold so we only pick the first one to
+ * determine the state.
+ * @param featureList the {@link CommonFoldingFeature} that are currently active.
+ * @return the {@link SidecarDeviceState} calculated from the {@link List} of
+ * {@link CommonFoldingFeature}.
+ */
+ @SuppressWarnings("deprecation")
+ private static int deviceStateFromFeatureList(@NonNull List<CommonFoldingFeature> featureList) {
+ for (int i = 0; i < featureList.size(); i++) {
+ final CommonFoldingFeature feature = featureList.get(i);
+ final int state = feature.getState();
+ switch (state) {
+ case CommonFoldingFeature.COMMON_STATE_FLAT:
+ return SidecarDeviceState.POSTURE_OPENED;
+ case CommonFoldingFeature.COMMON_STATE_HALF_OPENED:
+ return SidecarDeviceState.POSTURE_HALF_OPENED;
+ case CommonFoldingFeature.COMMON_STATE_UNKNOWN:
+ return SidecarDeviceState.POSTURE_UNKNOWN;
+ case CommonFoldingFeature.COMMON_STATE_NO_FOLDING_FEATURES:
+ return SidecarDeviceState.POSTURE_UNKNOWN;
+ case CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE:
+ return SidecarDeviceState.POSTURE_UNKNOWN;
+ }
+ }
+ return SidecarDeviceState.POSTURE_UNKNOWN;
+ }
+
+ /**
+ * Returns a {@link SidecarDeviceState} calculated from a {@link List} of
+ * {@link CommonFoldingFeature}s.
+ */
+ @SuppressWarnings("deprecation")
+ static SidecarDeviceState calculateDeviceState(
+ @NonNull List<CommonFoldingFeature> featureList) {
+ final SidecarDeviceState deviceState = new SidecarDeviceState();
+ deviceState.posture = deviceStateFromFeatureList(featureList);
+ return deviceState;
+ }
+
+ @SuppressWarnings("deprecation")
+ private static List<SidecarDisplayFeature> calculateDisplayFeatures(
+ @NonNull Activity activity,
+ @NonNull List<CommonFoldingFeature> featureList
+ ) {
+ final int displayId = activity.getDisplay().getDisplayId();
+ if (displayId != DEFAULT_DISPLAY) {
+ return Collections.emptyList();
+ }
+
+ if (activity.isInMultiWindowMode()) {
+ // It is recommended not to report any display features in multi-window mode, since it
+ // won't be possible to synchronize the display feature positions with window movement.
+ return Collections.emptyList();
+ }
+
+ final List<SidecarDisplayFeature> features = new ArrayList<>();
+ final int rotation = activity.getResources().getConfiguration().windowConfiguration
+ .getDisplayRotation();
+ for (CommonFoldingFeature baseFeature : featureList) {
+ final SidecarDisplayFeature feature = new SidecarDisplayFeature();
+ final Rect featureRect = baseFeature.getRect();
+ rotateRectToDisplayRotation(displayId, rotation, featureRect);
+ transformToWindowSpaceRect(activity, featureRect);
+ feature.setRect(featureRect);
+ feature.setType(baseFeature.getType());
+ features.add(feature);
+ }
+ return Collections.unmodifiableList(features);
+ }
+
+ /**
+ * Returns a {@link SidecarWindowLayoutInfo} calculated from the {@link List} of
+ * {@link CommonFoldingFeature}.
+ */
+ @SuppressWarnings("deprecation")
+ static SidecarWindowLayoutInfo calculateWindowLayoutInfo(@NonNull IBinder windowToken,
+ @NonNull List<CommonFoldingFeature> featureList) {
+ final Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken);
+ final SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo();
+ if (activity == null) {
+ return windowLayoutInfo;
+ }
+ windowLayoutInfo.displayFeatures = calculateDisplayFeatures(activity, featureList);
+ return windowLayoutInfo;
+ }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
index 8aca92e..ad913c9 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
@@ -35,8 +35,11 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.Activity;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
import android.os.Binder;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
@@ -44,6 +47,8 @@
import android.view.Display;
import android.view.MotionEvent;
import android.view.SurfaceControl;
+import android.view.View;
+import android.view.Window;
import android.window.TaskFragmentOperation;
import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
@@ -152,7 +157,10 @@
true /* isVerticalSplit */,
false /* isReversedLayout */,
Display.DEFAULT_DISPLAY,
- false /* isDraggableExpandType */);
+ false /* isDraggableExpandType */,
+ Color.valueOf(Color.BLACK), /* primaryVeilColor */
+ Color.valueOf(Color.GRAY) /* secondaryVeilColor */
+ );
mDividerPresenter = new DividerPresenter(
MOCK_TASK_ID, mDragEventCallback, mock(Executor.class));
@@ -604,6 +612,38 @@
0.0001 /* delta */);
}
+ @Test
+ public void testGetContainerBackgroundColor() {
+ final Color defaultColor = Color.valueOf(Color.RED);
+ final Color activityBackgroundColor = Color.valueOf(Color.BLUE);
+ final TaskFragmentContainer container = mock(TaskFragmentContainer.class);
+ final Activity activity = mock(Activity.class);
+ final Window window = mock(Window.class);
+ final View decorView = mock(View.class);
+ final ColorDrawable backgroundDrawable =
+ new ColorDrawable(activityBackgroundColor.toArgb());
+ when(activity.getWindow()).thenReturn(window);
+ when(window.getDecorView()).thenReturn(decorView);
+ when(decorView.getBackground()).thenReturn(backgroundDrawable);
+
+ // When the top non-finishing activity returns null, the default color should be returned.
+ when(container.getTopNonFinishingActivity()).thenReturn(null);
+ assertEquals(defaultColor,
+ DividerPresenter.getContainerBackgroundColor(container, defaultColor));
+
+ // When the top non-finishing activity is not resumed, the default color should be returned.
+ when(container.getTopNonFinishingActivity()).thenReturn(activity);
+ when(activity.isResumed()).thenReturn(false);
+ assertEquals(defaultColor,
+ DividerPresenter.getContainerBackgroundColor(container, defaultColor));
+
+ // When the top non-finishing activity is resumed, its background color should be returned.
+ when(container.getTopNonFinishingActivity()).thenReturn(activity);
+ when(activity.isResumed()).thenReturn(true);
+ assertEquals(activityBackgroundColor,
+ DividerPresenter.getContainerBackgroundColor(container, defaultColor));
+ }
+
private TaskFragmentContainer createMockTaskFragmentContainer(
@NonNull IBinder token, @NonNull Rect bounds) {
final TaskFragmentContainer container = mock(TaskFragmentContainer.class);
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index 6110133..0764141 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -31,7 +31,6 @@
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner
import com.android.wm.shell.bubbles.DeviceConfig
-import com.android.wm.shell.bubbles.bar.BubbleExpandedViewPinController.Companion.DROP_TARGET_SCALE
import com.android.wm.shell.common.bubbles.BaseBubblePinController
import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION
import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION
@@ -248,15 +247,8 @@
private val dropTargetView: View?
get() = container.findViewById(R.id.bubble_bar_drop_target)
- private fun getExpectedDropTargetBounds(onLeft: Boolean): Rect {
- val rect = Rect()
- positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOveflowExpanded */, rect)
- // Scale the rect to expected size, but keep the center point the same
- val centerX = rect.centerX()
- val centerY = rect.centerY()
- rect.scale(DROP_TARGET_SCALE)
- rect.offset(centerX - rect.centerX(), centerY - rect.centerY())
- return rect
+ private fun getExpectedDropTargetBounds(onLeft: Boolean): Rect = Rect().also {
+ positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOveflowExpanded */, it)
}
private fun runOnMainSync(runnable: Runnable) {
diff --git a/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml b/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml
index 9dcde3b..b928a0b 100644
--- a/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml
@@ -13,12 +13,14 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="rectangle">
- <corners android:radius="@dimen/bubble_bar_expanded_view_corner_radius" />
- <solid android:color="@color/bubble_drop_target_background_color" />
- <stroke
- android:width="1dp"
- android:color="?androidprv:attr/materialColorPrimaryContainer" />
-</shape>
+ android:inset="@dimen/bubble_bar_expanded_view_drop_target_padding">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/bubble_bar_expanded_view_drop_target_corner" />
+ <solid android:color="@color/bubble_drop_target_background_color" />
+ <stroke
+ android:width="1dp"
+ android:color="?androidprv:attr/materialColorPrimaryContainer" />
+ </shape>
+</inset>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index f532f96..8d24c16 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -274,6 +274,9 @@
<dimen name="bubble_bar_expanded_view_corner_radius">16dp</dimen>
<!-- Corner radius for expanded view while it is being dragged -->
<dimen name="bubble_bar_expanded_view_corner_radius_dragged">28dp</dimen>
+ <!-- Corner radius for expanded view drop target -->
+ <dimen name="bubble_bar_expanded_view_drop_target_corner">28dp</dimen>
+ <dimen name="bubble_bar_expanded_view_drop_target_padding">24dp</dimen>
<!-- Width of the box around bottom center of the screen where drag only leads to dismiss -->
<dimen name="bubble_bar_dismiss_zone_width">192dp</dimen>
<!-- Height of the box around bottom center of the screen where drag only leads to dismiss -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 87aac0b..9e6c5fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -170,6 +170,8 @@
* the pointer might need to be updated.
*/
void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer);
+ /** Called when the bubble overflow empty state changes, used to show/hide the overflow. */
+ void bubbleOverflowChanged(boolean hasBubbles);
}
private final Context mContext;
@@ -1169,6 +1171,15 @@
*/
public void startBubbleDrag(String bubbleKey) {
onBubbleDrag(bubbleKey, true /* isBeingDragged */);
+ if (mBubbleStateListener != null) {
+ boolean overflow = BubbleOverflow.KEY.equals(bubbleKey);
+ Rect rect = new Rect();
+ mBubblePositioner.getBubbleBarExpandedViewBounds(mBubblePositioner.isBubbleBarOnLeft(),
+ overflow, rect);
+ BubbleBarUpdate update = new BubbleBarUpdate();
+ update.expandedViewDropTargetSize = new Point(rect.width(), rect.height());
+ mBubbleStateListener.onBubbleStateChange(update);
+ }
}
/**
@@ -1401,7 +1412,7 @@
Bubble b = mBubbleData.getOverflowBubbleWithKey(appBubbleKey);
if (b != null) {
// It's in the overflow, so remove it & reinflate
- mBubbleData.removeOverflowBubble(b);
+ mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_NOTIF_CANCEL);
} else {
// App bubble does not exist, lets add and expand it
b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
@@ -1844,6 +1855,11 @@
}
}
+
+ @Override
+ public void bubbleOverflowChanged(boolean hasBubbles) {
+ // TODO (b/334175587): tell stack view to hide / show the overflow
+ }
};
/** When bubbles are in the bubble bar, this will be used to notify bubble bar views. */
@@ -1876,6 +1892,11 @@
}
@Override
+ public void bubbleOverflowChanged(boolean hasBubbles) {
+ // Nothing to do for our views, handled by launcher / in the bubble bar.
+ }
+
+ @Override
public void suppressionChanged(Bubble bubble, boolean isSuppressed) {
if (mLayerView != null) {
// TODO (b/273316505) handle suppression changes, although might not need to
@@ -1914,7 +1935,7 @@
ProtoLog.d(WM_SHELL_BUBBLES, "mBubbleDataListener#applyUpdate:"
+ " added=%s removed=%b updated=%s orderChanged=%b expansionChanged=%b"
+ " expanded=%b selectionChanged=%b selected=%s"
- + " suppressed=%s unsupressed=%s shouldShowEducation=%b",
+ + " suppressed=%s unsupressed=%s shouldShowEducation=%b showOverflowChanged=%b",
update.addedBubble != null ? update.addedBubble.getKey() : "null",
!update.removedBubbles.isEmpty(),
update.updatedBubble != null ? update.updatedBubble.getKey() : "null",
@@ -1923,13 +1944,17 @@
update.selectedBubble != null ? update.selectedBubble.getKey() : "null",
update.suppressedBubble != null ? update.suppressedBubble.getKey() : "null",
update.unsuppressedBubble != null ? update.unsuppressedBubble.getKey() : "null",
- update.shouldShowEducation);
+ update.shouldShowEducation, update.showOverflowChanged);
ensureBubbleViewsAndWindowCreated();
// Lazy load overflow bubbles from disk
loadOverflowBubblesFromDisk();
+ if (update.showOverflowChanged) {
+ mBubbleViewCallback.bubbleOverflowChanged(!update.overflowBubbles.isEmpty());
+ }
+
// If bubbles in the overflow have a dot, make sure the overflow shows a dot
updateOverflowButtonDot();
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 ae3d0c5..ea30af5 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
@@ -77,6 +77,7 @@
boolean suppressedSummaryChanged;
boolean expanded;
boolean shouldShowEducation;
+ boolean showOverflowChanged;
@Nullable BubbleViewProvider selectedBubble;
@Nullable Bubble addedBubble;
@Nullable Bubble updatedBubble;
@@ -109,7 +110,8 @@
|| suppressedBubble != null
|| unsuppressedBubble != null
|| suppressedSummaryChanged
- || suppressedSummaryGroup != null;
+ || suppressedSummaryGroup != null
+ || showOverflowChanged;
}
void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) {
@@ -157,6 +159,8 @@
bubbleBarUpdate.bubbleKeysInOrder.add(bubbles.get(i).getKey());
}
}
+ bubbleBarUpdate.showOverflowChanged = showOverflowChanged;
+ bubbleBarUpdate.showOverflow = !overflowBubbles.isEmpty();
return bubbleBarUpdate;
}
@@ -410,6 +414,9 @@
if (bubbleToReturn != null) {
// Promoting from overflow
mOverflowBubbles.remove(bubbleToReturn);
+ if (mOverflowBubbles.isEmpty()) {
+ mStateChange.showOverflowChanged = true;
+ }
} else if (mPendingBubbles.containsKey(key)) {
// Update while it was pending
bubbleToReturn = mPendingBubbles.get(key);
@@ -497,19 +504,6 @@
}
/**
- * Explicitly removes a bubble from the overflow, if it exists.
- *
- * @param bubble the bubble to remove.
- */
- public void removeOverflowBubble(Bubble bubble) {
- if (bubble == null) return;
- if (mOverflowBubbles.remove(bubble)) {
- mStateChange.removedOverflowBubble = bubble;
- dispatchPendingChanges();
- }
- }
-
- /**
* Adds a group key indicating that the summary for this group should be suppressed.
*
* @param groupKey the group key of the group whose summary should be suppressed.
@@ -683,7 +677,6 @@
if (indexToRemove == -1) {
if (hasOverflowBubbleWithKey(key)
&& shouldRemoveHiddenBubble) {
-
Bubble b = getOverflowBubbleWithKey(key);
ProtoLog.d(WM_SHELL_BUBBLES, "doRemove - cancel overflow bubble=%s", key);
if (b != null) {
@@ -693,6 +686,7 @@
mOverflowBubbles.remove(b);
mStateChange.bubbleRemoved(b, reason);
mStateChange.removedOverflowBubble = b;
+ mStateChange.showOverflowChanged = mOverflowBubbles.isEmpty();
}
if (hasSuppressedBubbleWithKey(key) && shouldRemoveHiddenBubble) {
Bubble b = getSuppressedBubbleWithKey(key);
@@ -792,6 +786,9 @@
}
ProtoLog.d(WM_SHELL_BUBBLES, "overflowBubble=%s", bubble.getKey());
mLogger.logOverflowAdd(bubble, reason);
+ if (mOverflowBubbles.isEmpty()) {
+ mStateChange.showOverflowChanged = true;
+ }
mOverflowBubbles.remove(bubble);
mOverflowBubbles.add(0, bubble);
mStateChange.addedOverflowBubble = bubble;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 3c788b1..be88b34 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -536,7 +536,8 @@
private OnClickListener mBubbleClickListener = new OnClickListener() {
@Override
public void onClick(View view) {
- mIsDraggingStack = false; // If the touch ended in a click, we're no longer dragging.
+ // If the touch ended in a click, we're no longer dragging.
+ onDraggingEnded();
// Bubble clicks either trigger expansion/collapse or a bubble switch, both of which we
// shouldn't interrupt. These are quick transitions, so it's not worth trying to adjust
@@ -719,8 +720,7 @@
mDismissView.hide();
}
- mIsDraggingStack = false;
- mMagnetizedObject = null;
+ onDraggingEnded();
// Hide the stack after a delay, if needed.
updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
@@ -1096,6 +1096,7 @@
} else {
maybeShowStackEdu();
}
+ onDraggingEnded();
});
animate()
@@ -1153,6 +1154,14 @@
}
/**
+ * Reset state related to dragging.
+ */
+ private void onDraggingEnded() {
+ mIsDraggingStack = false;
+ mMagnetizedObject = null;
+ }
+
+ /**
* Sets whether or not the stack should become temporarily invisible by moving off the side of
* the screen.
*
@@ -2341,8 +2350,8 @@
showScrim(true, null /* runnable */);
updateBubbleShadows(mIsExpanded);
- updateBadges(false /* setBadgeForCollapsedStack */);
mBubbleContainer.setActiveController(mExpandedAnimationController);
+ updateBadges(false /* setBadgeForCollapsedStack */);
updateOverflowVisibility();
updatePointerPosition(false /* forIme */);
mExpandedAnimationController.expandFromStack(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt
index 3b3974d..651bf02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt
@@ -22,7 +22,6 @@
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
-import androidx.annotation.VisibleForTesting
import androidx.core.view.updateLayoutParams
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner
@@ -79,7 +78,11 @@
override fun updateLocation(location: BubbleBarLocation) {
val view = dropTargetView ?: return
- getBounds(location.isOnLeft(view.isLayoutRtl), tempRect)
+ positioner.getBubbleBarExpandedViewBounds(
+ location.isOnLeft(view.isLayoutRtl),
+ false /* isOverflowExpanded */,
+ tempRect
+ )
view.updateLayoutParams<FrameLayout.LayoutParams> {
width = tempRect.width()
height = tempRect.height()
@@ -87,17 +90,4 @@
view.x = tempRect.left.toFloat()
view.y = tempRect.top.toFloat()
}
-
- private fun getBounds(onLeft: Boolean, out: Rect) {
- positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOverflowExpanded */, out)
- val centerX = out.centerX()
- val centerY = out.centerY()
- out.scale(DROP_TARGET_SCALE)
- // Move rect center back to the same position as before scale
- out.offset(centerX - out.centerX(), centerY - out.centerY())
- }
-
- companion object {
- @VisibleForTesting const val DROP_TARGET_SCALE = 0.9f
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 98dccbb..da414cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -389,9 +389,6 @@
public void dispatchDragEvent(DragEvent event) {}
@Override
- public void updatePointerIcon(float x, float y) {}
-
- @Override
public void dispatchWindowShown() {}
@Override
@@ -409,5 +406,10 @@
// ignore
}
}
+
+ @Override
+ public void dumpWindow(ParcelFileDescriptor pfd) {
+
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
index e5f6c37..ec3c601 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Point;
import android.os.Parcel;
import android.os.Parcelable;
@@ -49,6 +50,10 @@
public String unsupressedBubbleKey;
@Nullable
public BubbleBarLocation bubbleBarLocation;
+ @Nullable
+ public Point expandedViewDropTargetSize;
+ public boolean showOverflowChanged;
+ public boolean showOverflow;
// This is only populated if bubbles have been removed.
public List<RemovedBubble> removedBubbles = new ArrayList<>();
@@ -81,12 +86,16 @@
suppressedBubbleKey = parcel.readString();
unsupressedBubbleKey = parcel.readString();
removedBubbles = parcel.readParcelableList(new ArrayList<>(),
- RemovedBubble.class.getClassLoader());
+ RemovedBubble.class.getClassLoader(), RemovedBubble.class);
parcel.readStringList(bubbleKeysInOrder);
currentBubbleList = parcel.readParcelableList(new ArrayList<>(),
- BubbleInfo.class.getClassLoader());
+ BubbleInfo.class.getClassLoader(), BubbleInfo.class);
bubbleBarLocation = parcel.readParcelable(BubbleBarLocation.class.getClassLoader(),
BubbleBarLocation.class);
+ expandedViewDropTargetSize = parcel.readParcelable(Point.class.getClassLoader(),
+ Point.class);
+ showOverflowChanged = parcel.readBoolean();
+ showOverflow = parcel.readBoolean();
}
/**
@@ -102,9 +111,11 @@
|| suppressedBubbleKey != null
|| unsupressedBubbleKey != null
|| !currentBubbleList.isEmpty()
- || bubbleBarLocation != null;
+ || bubbleBarLocation != null
+ || showOverflowChanged;
}
+ @NonNull
@Override
public String toString() {
return "BubbleBarUpdate{"
@@ -121,6 +132,9 @@
+ " bubbles=" + bubbleKeysInOrder
+ " currentBubbleList=" + currentBubbleList
+ " bubbleBarLocation=" + bubbleBarLocation
+ + " expandedViewDropTargetSize=" + expandedViewDropTargetSize
+ + " showOverflowChanged=" + showOverflowChanged
+ + " showOverflow=" + showOverflow
+ " }";
}
@@ -144,6 +158,9 @@
parcel.writeStringList(bubbleKeysInOrder);
parcel.writeParcelableList(currentBubbleList, flags);
parcel.writeParcelable(bubbleBarLocation, flags);
+ parcel.writeParcelable(expandedViewDropTargetSize, flags);
+ parcel.writeBoolean(showOverflowChanged);
+ parcel.writeBoolean(showOverflow);
}
/**
@@ -157,10 +174,11 @@
@NonNull
public static final Creator<BubbleBarUpdate> CREATOR =
- new Creator<BubbleBarUpdate>() {
+ new Creator<>() {
public BubbleBarUpdate createFromParcel(Parcel source) {
return new BubbleBarUpdate(source);
}
+
public BubbleBarUpdate[] newArray(int size) {
return new BubbleBarUpdate[size];
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/OWNERS
new file mode 100644
index 0000000..08c7031
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/OWNERS
@@ -0,0 +1,6 @@
+# WM shell sub-module bubble owner
+madym@google.com
+atsjenk@google.com
+liranb@google.com
+sukeshram@google.com
+mpodolian@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
index 1e30d8f..dba0a98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -16,11 +16,14 @@
package com.android.wm.shell.common.pip
import android.app.ActivityTaskManager
+import android.app.AppGlobals
import android.app.RemoteAction
import android.app.WindowConfiguration
import android.content.ComponentName
import android.content.Context
+import android.content.pm.PackageManager
import android.os.RemoteException
+import android.os.SystemProperties
import android.util.DisplayMetrics
import android.util.Log
import android.util.Pair
@@ -135,7 +138,23 @@
}
}
+ private var isPip2ExperimentEnabled: Boolean? = null
+
+ /**
+ * Returns true if PiP2 implementation should be used. Besides the trunk stable flag,
+ * system property can be used to override this read only flag during development.
+ * It's currently limited to phone form factor, i.e., not enabled on ARC / TV.
+ */
@JvmStatic
- val isPip2ExperimentEnabled: Boolean
- get() = Flags.enablePip2Implementation()
+ fun isPip2ExperimentEnabled(): Boolean {
+ if (isPip2ExperimentEnabled == null) {
+ val isArc = AppGlobals.getPackageManager().hasSystemFeature(
+ "org.chromium.arc", 0)
+ val isTv = AppGlobals.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_LEANBACK, 0)
+ isPip2ExperimentEnabled = SystemProperties.getBoolean("wm_shell.pip2", false) ||
+ (Flags.enablePip2Implementation() && !isArc && !isTv)
+ }
+ return isPip2ExperimentEnabled as Boolean
+ }
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index f195f95..3ab1fad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -81,6 +81,10 @@
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+ if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) {
+ // Don't show the SCM button for freeform tasks
+ mHasSizeCompat &= !taskInfo.isFreeform();
+ }
mCameraCompatControlState =
taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState;
mCompatUIHintsState = compatUIHintsState;
@@ -136,6 +140,10 @@
final boolean prevHasSizeCompat = mHasSizeCompat;
final int prevCameraCompatControlState = mCameraCompatControlState;
mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+ if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) {
+ // Don't show the SCM button for freeform tasks
+ mHasSizeCompat &= !taskInfo.isFreeform();
+ }
mCameraCompatControlState =
taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 9bf9fa7..b41454d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -65,7 +65,7 @@
"persist.wm.debug.desktop_use_window_shadows_focused_window", false);
/**
- * Flag to indicate whether to apply shadows to windows in desktop mode.
+ * Flag to indicate whether to use rounded corners for windows in desktop mode.
*/
private static final boolean USE_ROUNDED_CORNERS = SystemProperties.getBoolean(
"persist.wm.debug.desktop_use_rounded_corners", true);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
new file mode 100644
index 0000000..6da3741
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:JvmName("DesktopModeUtils")
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.pm.ActivityInfo.isFixedOrientationLandscape
+import android.content.pm.ActivityInfo.isFixedOrientationPortrait
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
+import android.graphics.Rect
+import android.os.SystemProperties
+import android.util.Size
+import com.android.wm.shell.common.DisplayLayout
+
+
+val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float = SystemProperties
+ .getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f
+
+val DESKTOP_MODE_LANDSCAPE_APP_PADDING: Int = SystemProperties
+ .getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25)
+
+
+/**
+ * Calculates the initial bounds required for an application to fill a scale of the display bounds
+ * without any letterboxing. This is done by taking into account the applications fullscreen size,
+ * aspect ratio, orientation and resizability to calculate an area this is compatible with the
+ * applications previous configuration.
+ */
+fun calculateInitialBounds(
+ displayLayout: DisplayLayout,
+ taskInfo: RunningTaskInfo,
+ scale: Float = DESKTOP_MODE_INITIAL_BOUNDS_SCALE
+): Rect {
+ val screenBounds = Rect(0, 0, displayLayout.width(), displayLayout.height())
+ val appAspectRatio = calculateAspectRatio(taskInfo)
+ val idealSize = calculateIdealSize(screenBounds, scale)
+ // If no top activity exists, apps fullscreen bounds and aspect ratio cannot be calculated.
+ // Instead default to the desired initial bounds.
+ val topActivityInfo = taskInfo.topActivityInfo
+ ?: return positionInScreen(idealSize, screenBounds)
+
+ val initialSize: Size = when (taskInfo.configuration.orientation) {
+ ORIENTATION_LANDSCAPE -> {
+ if (taskInfo.isResizeable) {
+ if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
+ // Respect apps fullscreen width
+ Size(taskInfo.appCompatTaskInfo.topActivityLetterboxWidth, idealSize.height)
+ } else {
+ idealSize
+ }
+ } else {
+ maximumSizeMaintainingAspectRatio(taskInfo, idealSize,
+ appAspectRatio)
+ }
+ }
+ ORIENTATION_PORTRAIT -> {
+ val customPortraitWidthForLandscapeApp = screenBounds.width() -
+ (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2)
+ if (taskInfo.isResizeable) {
+ if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
+ // Respect apps fullscreen height and apply custom app width
+ Size(customPortraitWidthForLandscapeApp,
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight)
+ } else {
+ idealSize
+ }
+ } else {
+ if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
+ // Apply custom app width and calculate maximum size
+ maximumSizeMaintainingAspectRatio(
+ taskInfo,
+ Size(customPortraitWidthForLandscapeApp, idealSize.height),
+ appAspectRatio)
+ } else {
+ maximumSizeMaintainingAspectRatio(taskInfo, idealSize,
+ appAspectRatio)
+ }
+ }
+ }
+ else -> {
+ idealSize
+ }
+ }
+
+ return positionInScreen(initialSize, screenBounds)
+}
+
+/**
+ * Calculates the largest size that can fit in a given area while maintaining a specific aspect
+ * ratio.
+ */
+private fun maximumSizeMaintainingAspectRatio(
+ taskInfo: RunningTaskInfo,
+ targetArea: Size,
+ aspectRatio: Float
+): Size {
+ val targetHeight = targetArea.height
+ val targetWidth = targetArea.width
+ val finalHeight: Int
+ val finalWidth: Int
+ if (isFixedOrientationPortrait(taskInfo.topActivityInfo!!.screenOrientation)) {
+ val tempWidth = (targetHeight / aspectRatio).toInt()
+ if (tempWidth <= targetWidth) {
+ finalHeight = targetHeight
+ finalWidth = tempWidth
+ } else {
+ finalWidth = targetWidth
+ finalHeight = (finalWidth * aspectRatio).toInt()
+ }
+ } else {
+ val tempWidth = (targetHeight * aspectRatio).toInt()
+ if (tempWidth <= targetWidth) {
+ finalHeight = targetHeight
+ finalWidth = tempWidth
+ } else {
+ finalWidth = targetWidth
+ finalHeight = (finalWidth / aspectRatio).toInt()
+ }
+ }
+ return Size(finalWidth, finalHeight)
+}
+
+/**
+ * Calculates the aspect ratio of an activity from its fullscreen bounds.
+ */
+private fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
+ if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
+ val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth
+ val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight
+ return maxOf(appLetterboxWidth, appLetterboxHeight) /
+ minOf(appLetterboxWidth, appLetterboxHeight).toFloat()
+ }
+ val appBounds = taskInfo.configuration.windowConfiguration.appBounds ?: return 1f
+ return maxOf(appBounds.height(), appBounds.width()) /
+ minOf(appBounds.height(), appBounds.width()).toFloat()
+}
+
+/**
+ * Calculates the desired initial bounds for applications in desktop windowing. This is done as a
+ * scale of the screen bounds.
+ */
+private fun calculateIdealSize(screenBounds: Rect, scale: Float): Size {
+ val width = (screenBounds.width() * scale).toInt()
+ val height = (screenBounds.height() * scale).toInt()
+ return Size(width, height)
+}
+
+/**
+ * Adjusts bounds to be positioned in the middle of the screen.
+ */
+private fun positionInScreen(desiredSize: Size, screenBounds: Rect): Rect {
+ // TODO(b/325240051): Position apps with bottom heavy offset
+ val heightOffset = (screenBounds.height() - desiredSize.height) / 2
+ val widthOffset = (screenBounds.width() - desiredSize.width) / 2
+ return Rect(widthOffset, heightOffset,
+ desiredSize.width + widthOffset, desiredSize.height + heightOffset)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index f7bfb86..b0d5923 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -47,6 +47,7 @@
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
+import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -85,7 +86,6 @@
import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
-import com.android.wm.shell.windowdecor.extension.isFreeform
import com.android.wm.shell.windowdecor.extension.isFullscreen
import java.io.PrintWriter
import java.util.Optional
@@ -203,6 +203,11 @@
dragAndDropController.addListener(this)
}
+ @VisibleForTesting
+ fun getVisualIndicator(): DesktopModeVisualIndicator? {
+ return visualIndicator
+ }
+
fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
@@ -605,8 +610,9 @@
}
/**
- * Quick-resizes a desktop task, toggling between the stable bounds and the last saved bounds
- * if available or the default bounds otherwise.
+ * Quick-resizes a desktop task, toggling between a fullscreen state (represented by the
+ * stable bounds) and a free floating state (either the last saved bounds if available or the
+ * default bounds otherwise).
*/
fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
@@ -623,7 +629,11 @@
if (taskBoundsBeforeMaximize != null) {
destinationBounds.set(taskBoundsBeforeMaximize)
} else {
- destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout))
+ if (Flags.enableWindowingDynamicInitialBounds()){
+ destinationBounds.set(calculateInitialBounds(displayLayout, taskInfo))
+ } else {
+ destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout))
+ }
}
} else {
// Save current bounds so that task can be restored back to original bounds if necessary
@@ -1011,6 +1021,7 @@
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
) {
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) {
@@ -1019,6 +1030,9 @@
} else {
WINDOWING_MODE_FREEFORM
}
+ if (Flags.enableWindowingDynamicInitialBounds()) {
+ wct.setBounds(taskInfo.token, calculateInitialBounds(displayLayout, taskInfo))
+ }
wct.setWindowingMode(taskInfo.token, targetWindowingMode)
wct.reorder(taskInfo.token, true /* onTop */)
if (isDesktopDensityOverrideSet()) {
@@ -1239,13 +1253,17 @@
* @param y height of drag, to be checked against status bar height.
*/
fun onDragPositioningEndThroughStatusBar(inputCoordinates: PointF, taskInfo: RunningTaskInfo) {
- val indicator = visualIndicator ?: return
+ val indicator = getVisualIndicator() ?: return
val indicatorType = indicator
.updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
when (indicatorType) {
DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
- finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
+ if (Flags.enableWindowingDynamicInitialBounds()) {
+ finalizeDragToDesktop(taskInfo, calculateInitialBounds(displayLayout, taskInfo))
+ } else {
+ finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
+ }
}
DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index ecb53dc..59d6969 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -16,11 +16,11 @@
package com.android.wm.shell.draganddrop;
-import static android.app.StatusBarManager.DISABLE2_NONE;
import static android.app.StatusBarManager.DISABLE_NONE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
@@ -516,20 +516,18 @@
}
private void animateFullscreenContainer(boolean visible) {
- int flags = visible ? HIDE_STATUS_BAR_FLAGS : DISABLE_NONE;
- StatusBarManager.DisableInfo disableInfo = new StatusBarManager.DisableInfo(flags,
- DISABLE2_NONE);
- mStatusBarManager.requestDisabledComponent(disableInfo, "animateFullscreenContainer");
+ mStatusBarManager.disable(visible
+ ? HIDE_STATUS_BAR_FLAGS
+ : DISABLE_NONE);
// We're only using the first drop zone if there is one fullscreen target
mDropZoneView1.setShowingMargin(visible);
mDropZoneView1.setShowingHighlight(visible);
}
private void animateSplitContainers(boolean visible, Runnable animCompleteCallback) {
- int flags = visible ? HIDE_STATUS_BAR_FLAGS : DISABLE_NONE;
- StatusBarManager.DisableInfo disableInfo = new StatusBarManager.DisableInfo(flags,
- DISABLE2_NONE);
- mStatusBarManager.requestDisabledComponent(disableInfo, "animateSplitContainers");
+ mStatusBarManager.disable(visible
+ ? HIDE_STATUS_BAR_FLAGS
+ : DISABLE_NONE);
mDropZoneView1.setShowingMargin(visible);
mDropZoneView2.setShowingMargin(visible);
Animator animator = mDropZoneView1.getAnimator();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index ad29d15..19af3d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -52,7 +52,7 @@
WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
- Consts.TAG_WM_SHELL),
+ Consts.TAG_WM_DESKTOP_MODE),
WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
@@ -120,6 +120,7 @@
private static final String TAG_WM_SHELL = "WindowManagerShell";
private static final String TAG_WM_STARTING_WINDOW = "ShellStartingWindow";
private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen";
+ private static final String TAG_WM_DESKTOP_MODE = "ShellDesktopMode";
private static final boolean ENABLE_DEBUG = true;
private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 4d02ec2..968b27b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -426,7 +426,8 @@
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Converting mixed transition into a keyguard transition");
// Consume the original mixed transition
- onTransitionConsumed(transition, false, null);
+ mActiveTransitions.remove(mixed);
+ mixed.onTransitionConsumed(transition, false, null);
return true;
} else {
// Keyguard handler cannot handle it, process through original mixed
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index f3ef7c1..dfdb58a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -278,9 +278,11 @@
public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
if (visible && stage != STAGE_TYPE_UNDEFINED) {
DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
- if (decor != null && DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopTasksController.moveToSplit(decor.mTaskInfo);
+ if (decor == null || !DesktopModeStatus.canEnterDesktopMode(mContext)
+ || decor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ return;
}
+ mDesktopTasksController.moveToSplit(decor.mTaskInfo);
}
}
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index f7516da..4c347ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -110,6 +110,8 @@
private ResizeVeil mResizeVeil;
private Bitmap mAppIconBitmap;
+ private Bitmap mResizeVeilBitmap;
+
private CharSequence mAppName;
private ExclusionRegionListener mExclusionRegionListener;
@@ -468,11 +470,15 @@
PackageManager pm = mContext.getApplicationContext().getPackageManager();
final IconProvider provider = new IconProvider(mContext);
final Drawable appIconDrawable = provider.getIcon(activityInfo);
- final Resources resources = mContext.getResources();
- final BaseIconFactory factory = new BaseIconFactory(mContext,
- resources.getDisplayMetrics().densityDpi,
- resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius));
- mAppIconBitmap = factory.createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+ final BaseIconFactory headerIconFactory = createIconFactory(mContext,
+ R.dimen.desktop_mode_caption_icon_radius);
+ mAppIconBitmap = headerIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+
+ final BaseIconFactory resizeVeilIconFactory = createIconFactory(mContext,
+ R.dimen.desktop_mode_resize_veil_icon_size);
+ mResizeVeilBitmap = resizeVeilIconFactory
+ .createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+
final ApplicationInfo applicationInfo = activityInfo.applicationInfo;
mAppName = pm.getApplicationLabel(applicationInfo);
} finally {
@@ -480,6 +486,13 @@
}
}
+ private BaseIconFactory createIconFactory(Context context, int dimensions) {
+ final Resources resources = context.getResources();
+ final int densityDpi = resources.getDisplayMetrics().densityDpi;
+ final int iconSize = resources.getDimensionPixelSize(dimensions);
+ return new BaseIconFactory(context, densityDpi, iconSize);
+ }
+
private void closeDragResizeListener() {
if (mDragResizeListener == null) {
return;
@@ -495,7 +508,7 @@
private void createResizeVeilIfNeeded() {
if (mResizeVeil != null) return;
loadAppInfoIfNeeded();
- mResizeVeil = new ResizeVeil(mContext, mDisplayController, mAppIconBitmap, mTaskInfo,
+ mResizeVeil = new ResizeVeil(mContext, mDisplayController, mResizeVeilBitmap, mTaskInfo,
mTaskSurface, mSurfaceControlTransactionSupplier);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 9624d46..5379ca6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -24,7 +24,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
-import static com.android.input.flags.Flags.enablePointerChoreographer;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
@@ -54,6 +54,7 @@
import android.view.WindowManagerGlobal;
import android.window.InputTransferToken;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
@@ -399,12 +400,17 @@
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
int ctrlType = mDragResizeWindowGeometry.calculateCtrlType(isTouch, x, y);
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "%s: Handling action down, update ctrlType to %d", TAG, ctrlType);
mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType,
rawX, rawY);
// Increase the input sink region to cover the whole screen; this is to
// prevent input and focus from going to other tasks during a drag resize.
updateInputSinkRegionForDrag(mDragStartTaskBounds);
result = true;
+ } else {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "%s: Handling action down, but ignore event", TAG);
}
break;
}
@@ -499,12 +505,10 @@
// where views in the task can receive input events because we can't set touch regions
// of input sinks to have rounded corners.
if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) {
- if (enablePointerChoreographer()) {
- mInputManager.setPointerIcon(PointerIcon.getSystemIcon(mContext, cursorType),
- displayId, deviceId, pointerId, mInputChannel.getToken());
- } else {
- mInputManager.setPointerIconType(cursorType);
- }
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: update pointer icon from %d to %d",
+ TAG, mLastCursorType, cursorType);
+ mInputManager.setPointerIcon(PointerIcon.getSystemIcon(mContext, cursorType),
+ displayId, deviceId, pointerId, mInputChannel.getToken());
mLastCursorType = cursorType;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index eafb569..4f513f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -33,6 +33,9 @@
import android.util.Size;
import android.view.MotionEvent;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
import com.android.wm.shell.R;
import java.util.Objects;
@@ -41,6 +44,11 @@
* Geometry for a drag resize region for a particular window.
*/
final class DragResizeWindowGeometry {
+ // TODO(b/337264971) clean up when no longer needed
+ @VisibleForTesting static final boolean DEBUG = true;
+ // The additional width to apply to edge resize bounds just for logging when a touch is
+ // close.
+ @VisibleForTesting static final int EDGE_DEBUG_BUFFER = 15;
private final int mTaskCornerRadius;
private final Size mTaskSize;
// The size of the handle applied to the edges of the window, for the user to drag resize.
@@ -51,10 +59,9 @@
// The task corners to permit drag resizing with a fine input, such as stylus or cursor.
private final @NonNull TaskCorners mFineTaskCorners;
// The bounds for each edge drag region, which can resize the task in one direction.
- private final @NonNull Rect mTopEdgeBounds;
- private final @NonNull Rect mLeftEdgeBounds;
- private final @NonNull Rect mRightEdgeBounds;
- private final @NonNull Rect mBottomEdgeBounds;
+ private final @NonNull TaskEdges mTaskEdges;
+ // Extra-large edge bounds for logging to help debug when an edge resize is ignored.
+ private final @Nullable TaskEdges mDebugTaskEdges;
DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
@@ -66,26 +73,12 @@
mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize);
// Save touch areas for each edge.
- mTopEdgeBounds = new Rect(
- -mResizeHandleThickness,
- -mResizeHandleThickness,
- mTaskSize.getWidth() + mResizeHandleThickness,
- 0);
- mLeftEdgeBounds = new Rect(
- -mResizeHandleThickness,
- 0,
- 0,
- mTaskSize.getHeight());
- mRightEdgeBounds = new Rect(
- mTaskSize.getWidth(),
- 0,
- mTaskSize.getWidth() + mResizeHandleThickness,
- mTaskSize.getHeight());
- mBottomEdgeBounds = new Rect(
- -mResizeHandleThickness,
- mTaskSize.getHeight(),
- mTaskSize.getWidth() + mResizeHandleThickness,
- mTaskSize.getHeight() + mResizeHandleThickness);
+ mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness);
+ if (DEBUG) {
+ mDebugTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness + EDGE_DEBUG_BUFFER);
+ } else {
+ mDebugTaskEdges = null;
+ }
}
/**
@@ -127,10 +120,13 @@
*/
void union(@NonNull Region region) {
// Apply the edge resize regions.
- region.union(mTopEdgeBounds);
- region.union(mLeftEdgeBounds);
- region.union(mRightEdgeBounds);
- region.union(mBottomEdgeBounds);
+ if (inDebugMode()) {
+ // Use the larger edge sizes if we are debugging, to be able to log if we ignored a
+ // touch due to the size of the edge region.
+ mDebugTaskEdges.union(region);
+ } else {
+ mTaskEdges.union(region);
+ }
if (enableWindowingEdgeDragResize()) {
// Apply the corners as well for the larger corners, to ensure we capture all possible
@@ -216,6 +212,10 @@
@DragPositioningCallback.CtrlType
private int calculateEdgeResizeCtrlType(float x, float y) {
+ if (inDebugMode() && (mDebugTaskEdges.contains((int) x, (int) y)
+ && !mTaskEdges.contains((int) x, (int) y))) {
+ return CTRL_TYPE_UNDEFINED;
+ }
int ctrlType = CTRL_TYPE_UNDEFINED;
// mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
// sides will use the bounds specified in setGeometry and not go into task bounds.
@@ -306,10 +306,9 @@
&& this.mResizeHandleThickness == other.mResizeHandleThickness
&& this.mFineTaskCorners.equals(other.mFineTaskCorners)
&& this.mLargeTaskCorners.equals(other.mLargeTaskCorners)
- && this.mTopEdgeBounds.equals(other.mTopEdgeBounds)
- && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds)
- && this.mRightEdgeBounds.equals(other.mRightEdgeBounds)
- && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds);
+ && (inDebugMode()
+ ? this.mDebugTaskEdges.equals(other.mDebugTaskEdges)
+ : this.mTaskEdges.equals(other.mTaskEdges));
}
@Override
@@ -320,10 +319,11 @@
mResizeHandleThickness,
mFineTaskCorners,
mLargeTaskCorners,
- mTopEdgeBounds,
- mLeftEdgeBounds,
- mRightEdgeBounds,
- mBottomEdgeBounds);
+ (inDebugMode() ? mDebugTaskEdges : mTaskEdges));
+ }
+
+ private boolean inDebugMode() {
+ return DEBUG && mDebugTaskEdges != null;
}
/**
@@ -431,4 +431,92 @@
mRightBottomCornerBounds);
}
}
+
+ /**
+ * Representation of the drag resize regions at the edges of the window.
+ */
+ private static class TaskEdges {
+ private final @NonNull Rect mTopEdgeBounds;
+ private final @NonNull Rect mLeftEdgeBounds;
+ private final @NonNull Rect mRightEdgeBounds;
+ private final @NonNull Rect mBottomEdgeBounds;
+ private final @NonNull Region mRegion;
+
+ private TaskEdges(@NonNull Size taskSize, int resizeHandleThickness) {
+ // Save touch areas for each edge.
+ mTopEdgeBounds = new Rect(
+ -resizeHandleThickness,
+ -resizeHandleThickness,
+ taskSize.getWidth() + resizeHandleThickness,
+ 0);
+ mLeftEdgeBounds = new Rect(
+ -resizeHandleThickness,
+ 0,
+ 0,
+ taskSize.getHeight());
+ mRightEdgeBounds = new Rect(
+ taskSize.getWidth(),
+ 0,
+ taskSize.getWidth() + resizeHandleThickness,
+ taskSize.getHeight());
+ mBottomEdgeBounds = new Rect(
+ -resizeHandleThickness,
+ taskSize.getHeight(),
+ taskSize.getWidth() + resizeHandleThickness,
+ taskSize.getHeight() + resizeHandleThickness);
+
+ mRegion = new Region();
+ mRegion.union(mTopEdgeBounds);
+ mRegion.union(mLeftEdgeBounds);
+ mRegion.union(mRightEdgeBounds);
+ mRegion.union(mBottomEdgeBounds);
+ }
+
+ /**
+ * Returns {@code true} if the edges contain the given point.
+ */
+ private boolean contains(int x, int y) {
+ return mRegion.contains(x, y);
+ }
+
+ /**
+ * Updates the region to include all four corners.
+ */
+ private void union(Region region) {
+ region.union(mTopEdgeBounds);
+ region.union(mLeftEdgeBounds);
+ region.union(mRightEdgeBounds);
+ region.union(mBottomEdgeBounds);
+ }
+
+ @Override
+ public String toString() {
+ return "TaskEdges for the"
+ + " top " + mTopEdgeBounds
+ + " left " + mLeftEdgeBounds
+ + " right " + mRightEdgeBounds
+ + " bottom " + mBottomEdgeBounds;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) return false;
+ if (this == obj) return true;
+ if (!(obj instanceof TaskEdges other)) return false;
+
+ return this.mTopEdgeBounds.equals(other.mTopEdgeBounds)
+ && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds)
+ && this.mRightEdgeBounds.equals(other.mRightEdgeBounds)
+ && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mTopEdgeBounds,
+ mLeftEdgeBounds,
+ mRightEdgeBounds,
+ mBottomEdgeBounds);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
index a2293d5..ec20471 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.windowdecor.extension
import android.app.TaskInfo
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
@@ -36,6 +35,3 @@
val TaskInfo.isFullscreen: Boolean
get() = windowingMode == WINDOWING_MODE_FULLSCREEN
-
-val TaskInfo.isFreeform: Boolean
- get() = windowingMode == WINDOWING_MODE_FREEFORM
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 6be411d..0f43377 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
@@ -1193,23 +1193,6 @@
}
@Test
- public void test_removeOverflowBubble() {
- sendUpdatedEntryAtTime(mEntryA1, 2000);
- mBubbleData.setListener(mListener);
-
- mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
- verifyUpdateReceived();
- assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
-
- mBubbleData.removeOverflowBubble(mBubbleA1);
- verifyUpdateReceived();
-
- BubbleData.Update update = mUpdateCaptor.getValue();
- assertThat(update.removedOverflowBubble).isEqualTo(mBubbleA1);
- assertOverflowChangedTo(ImmutableList.of());
- }
-
- @Test
public void test_getInitialStateForBubbleBar_includesInitialBubblesAndPosition() {
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
@@ -1235,6 +1218,51 @@
assertExpandedChangedTo(true);
}
+ @Test
+ public void testShowOverflowChanged_hasOverflowBubbles() {
+ assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ verifyUpdateReceived();
+ assertThat(mUpdateCaptor.getValue().showOverflowChanged).isTrue();
+ assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
+ }
+
+ @Test
+ public void testShowOverflowChanged_false_hasOverflowBubbles() {
+ assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 1000);
+ mBubbleData.setListener(mListener);
+
+ // First overflowed causes change event
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ verifyUpdateReceived();
+ assertThat(mUpdateCaptor.getValue().showOverflowChanged).isTrue();
+ assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
+
+ // Second overflow does not
+ mBubbleData.dismissBubbleWithKey(mEntryA2.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ verifyUpdateReceived();
+ assertThat(mUpdateCaptor.getValue().showOverflowChanged).isFalse();
+ }
+
+ @Test
+ public void testShowOverflowChanged_noOverflowBubbles() {
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
+
+ verifyUpdateReceived();
+ assertThat(mUpdateCaptor.getValue().showOverflowChanged).isTrue();
+ assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
+ }
+
private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index df8a222..3f76c4f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -24,6 +24,12 @@
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
@@ -101,7 +107,9 @@
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
import org.mockito.quality.Strictness
@@ -141,6 +149,7 @@
@Mock lateinit var dragAndDropController: DragAndDropController
@Mock lateinit var multiInstanceHelper: MultiInstanceHelper
@Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver
+ @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -154,6 +163,15 @@
// Mock running tasks are registered here so we can get the list from mock shell task organizer
private val runningTasks = mutableListOf<RunningTaskInfo>()
+ private val DISPLAY_DIMENSION_SHORT = 1600
+ private val DISPLAY_DIMENSION_LONG = 2560
+ private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 200, 2240, 1400)
+ private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 320, 1400, 2240)
+ private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 680, 1575, 1880)
+ private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 200, 1880, 1400)
+ private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 699, 1575, 1861)
+ private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 200, 1730, 1400)
+
@Before
fun setUp() {
mockitoSession = mockitoSession().strictness(Strictness.LENIENT)
@@ -161,7 +179,7 @@
whenever(DesktopModeStatus.isEnabled()).thenReturn(true)
doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
- shellInit = Mockito.spy(ShellInit(testExecutor))
+ shellInit = spy(ShellInit(testExecutor))
desktopModeTaskRepository = DesktopModeTaskRepository()
desktopTasksLimiter =
DesktopTasksLimiter(transitions, desktopModeTaskRepository, shellTaskOrganizer)
@@ -464,6 +482,135 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask()
+ setUpLandscapeDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+ setUpLandscapeDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ shouldLetterbox = true)
+ setUpLandscapeDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(isResizable = false,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+ setUpLandscapeDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(isResizable = false,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true)
+ setUpLandscapeDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
+ setUpPortraitDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+ setUpPortraitDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+ setUpPortraitDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(isResizable = false,
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+ setUpPortraitDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(isResizable = false,
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+ setUpPortraitDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
val task = setUpFullscreenTask()
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -1225,6 +1372,185 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask()
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ shouldLetterbox = true)
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(isResizable = false,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(isResizable = false,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true)
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(isResizable = false,
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(isResizable = false,
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() {
val task = setUpFreeformTask()
val mockSurface = mock(SurfaceControl::class.java)
@@ -1276,8 +1602,7 @@
controller.toggleDesktopTaskSize(task)
// Assert bounds set to stable bounds
val wct = getLatestToggleResizeDesktopTaskWct()
- assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds)
- .isEqualTo(STABLE_BOUNDS)
+ assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
}
@Test
@@ -1304,8 +1629,7 @@
// Assert bounds set to last bounds before maximize
val wct = getLatestToggleResizeDesktopTaskWct()
- assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds)
- .isEqualTo(boundsBeforeMaximize)
+ assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
}
@Test
@@ -1346,14 +1670,65 @@
return task
}
- private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+ private fun setUpFullscreenTask(
+ displayId: Int = DEFAULT_DISPLAY,
+ isResizable: Boolean = true,
+ windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
+ deviceOrientation: Int = ORIENTATION_LANDSCAPE,
+ screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED,
+ shouldLetterbox: Boolean = false
+ ): RunningTaskInfo {
val task = createFullscreenTask(displayId)
+ val activityInfo = ActivityInfo()
+ activityInfo.screenOrientation = screenOrientation
+ with(task) {
+ topActivityInfo = activityInfo
+ isResizeable = isResizable
+ configuration.orientation = deviceOrientation
+ configuration.windowConfiguration.windowingMode = windowingMode
+
+ if (shouldLetterbox) {
+ if (deviceOrientation == ORIENTATION_LANDSCAPE &&
+ screenOrientation == SCREEN_ORIENTATION_PORTRAIT) {
+ // Letterbox to portrait size
+ appCompatTaskInfo.topActivityBoundsLetterboxed = true
+ appCompatTaskInfo.topActivityLetterboxWidth = 1200
+ appCompatTaskInfo.topActivityLetterboxHeight = 1600
+ } else if (deviceOrientation == ORIENTATION_PORTRAIT &&
+ screenOrientation == SCREEN_ORIENTATION_LANDSCAPE) {
+ // Letterbox to landscape size
+ appCompatTaskInfo.topActivityBoundsLetterboxed = true
+ appCompatTaskInfo.topActivityLetterboxWidth = 1600
+ appCompatTaskInfo.topActivityLetterboxHeight = 1200
+ }
+ } else {
+ appCompatTaskInfo.topActivityBoundsLetterboxed = false
+ }
+
+ if (deviceOrientation == ORIENTATION_LANDSCAPE) {
+ configuration.windowConfiguration.appBounds = Rect(0, 0,
+ DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT)
+ } else {
+ configuration.windowConfiguration.appBounds = Rect(0, 0,
+ DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG)
+ }
+ }
whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
runningTasks.add(task)
return task
}
+ private fun setUpLandscapeDisplay() {
+ whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_LONG)
+ whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_SHORT)
+ }
+
+ private fun setUpPortraitDisplay() {
+ whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_SHORT)
+ whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_LONG)
+ }
+
private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createSplitScreenTask(displayId)
whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
@@ -1418,6 +1793,17 @@
return arg.value
}
+ private fun getLatestDragToDesktopWct(): WindowContainerTransaction {
+ val arg: ArgumentCaptor<WindowContainerTransaction> =
+ ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ if (ENABLE_SHELL_TRANSITIONS) {
+ verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg))
+ } else {
+ verify(shellTaskOrganizer).applyTransaction(capture(arg))
+ }
+ return arg.value
+ }
+
private fun getLatestExitDesktopWct(): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
if (ENABLE_SHELL_TRANSITIONS) {
@@ -1429,6 +1815,10 @@
return arg.value
}
+ private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
+ wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds
+
+
private fun verifyWCTNotExecuted() {
if (ENABLE_SHELL_TRANSITIONS) {
verify(transitions, never()).startTransition(anyInt(), any(), isNull())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index 82e5a1c..5464508 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -56,6 +56,8 @@
private static final Size TASK_SIZE = new Size(500, 1000);
private static final int TASK_CORNER_RADIUS = 10;
private static final int EDGE_RESIZE_THICKNESS = 15;
+ private static final int EDGE_RESIZE_DEBUG_THICKNESS = EDGE_RESIZE_THICKNESS
+ + (DragResizeWindowGeometry.DEBUG ? DragResizeWindowGeometry.EDGE_DEBUG_BUFFER : 0);
private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
@@ -90,13 +92,14 @@
EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE),
new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
- .addEqualityGroup(
+ .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+ EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5),
new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
- LARGE_CORNER_SIZE + 5),
+ EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5))
+ .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+ EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE),
new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
- LARGE_CORNER_SIZE + 5))
+ EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE))
.testEquals();
}
@@ -122,21 +125,21 @@
private static void verifyHorizontalEdge(@NonNull Region region, @NonNull Point point) {
assertThat(region.contains(point.x, point.y)).isTrue();
// Horizontally along the edge is still contained.
- assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue();
- assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue();
+ assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
+ assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
// Vertically along the edge is not contained.
- assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse();
- assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse();
+ assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
+ assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
}
private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) {
assertThat(region.contains(point.x, point.y)).isTrue();
// Horizontally along the edge is not contained.
- assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse();
- assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse();
+ assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
+ assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
// Vertically along the edge is contained.
- assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue();
- assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue();
+ assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
+ assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
}
/**
@@ -148,7 +151,10 @@
public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() {
Region region = new Region();
GEOMETRY.union(region);
- final int cornerRadius = LARGE_CORNER_SIZE / 2;
+ // Make sure we're choosing a point outside of any debug region buffer.
+ final int cornerRadius = DragResizeWindowGeometry.DEBUG
+ ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
+ : LARGE_CORNER_SIZE / 2;
new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
}
@@ -162,7 +168,9 @@
public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() {
Region region = new Region();
GEOMETRY.union(region);
- final int cornerRadius = FINE_CORNER_SIZE / 2;
+ final int cornerRadius = DragResizeWindowGeometry.DEBUG
+ ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
+ : LARGE_CORNER_SIZE / 2;
new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
}
diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
index 1a5b938..31c9db7 100644
--- a/libs/hwui/ColorFilter.h
+++ b/libs/hwui/ColorFilter.h
@@ -23,17 +23,42 @@
#include "GraphicsJNI.h"
#include "SkColorFilter.h"
-#include "SkiaWrapper.h"
namespace android {
namespace uirenderer {
-class ColorFilter : public SkiaWrapper<SkColorFilter> {
+class ColorFilter : public VirtualLightRefBase {
public:
static ColorFilter* fromJava(jlong handle) { return reinterpret_cast<ColorFilter*>(handle); }
+ sk_sp<SkColorFilter> getInstance() {
+ if (mInstance != nullptr && shouldDiscardInstance()) {
+ mInstance = nullptr;
+ }
+
+ if (mInstance == nullptr) {
+ mInstance = createInstance();
+ if (mInstance) {
+ mInstance = mInstance->makeWithWorkingColorSpace(SkColorSpace::MakeSRGB());
+ }
+ mGenerationId++;
+ }
+ return mInstance;
+ }
+
+ virtual bool shouldDiscardInstance() const { return false; }
+
+ void discardInstance() { mInstance = nullptr; }
+
+ [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; }
+
protected:
ColorFilter() = default;
+ virtual sk_sp<SkColorFilter> createInstance() = 0;
+
+private:
+ sk_sp<SkColorFilter> mInstance = nullptr;
+ int32_t mGenerationId = 0;
};
class BlendModeColorFilter : public ColorFilter {
diff --git a/libs/hwui/SkiaWrapper.h b/libs/hwui/SkiaWrapper.h
deleted file mode 100644
index bd0e35a..0000000
--- a/libs/hwui/SkiaWrapper.h
+++ /dev/null
@@ -1,56 +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.
- */
-
-#ifndef SKIA_WRAPPER_H_
-#define SKIA_WRAPPER_H_
-
-#include <SkRefCnt.h>
-#include <utils/RefBase.h>
-
-namespace android::uirenderer {
-
-template <typename T>
-class SkiaWrapper : public VirtualLightRefBase {
-public:
- sk_sp<T> getInstance() {
- if (mInstance != nullptr && shouldDiscardInstance()) {
- mInstance = nullptr;
- }
-
- if (mInstance == nullptr) {
- mInstance = createInstance();
- mGenerationId++;
- }
- return mInstance;
- }
-
- virtual bool shouldDiscardInstance() const { return false; }
-
- void discardInstance() { mInstance = nullptr; }
-
- [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; }
-
-protected:
- virtual sk_sp<T> createInstance() = 0;
-
-private:
- sk_sp<T> mInstance = nullptr;
- int32_t mGenerationId = 0;
-};
-
-} // namespace android::uirenderer
-
-#endif // SKIA_WRAPPER_H_
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index 6a46544..5cf5a1d 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -117,7 +117,7 @@
return {mLocked.pointerX, mLocked.pointerY};
}
-int32_t MouseCursorController::getDisplayId() const {
+ui::LogicalDisplayId MouseCursorController::getDisplayId() const {
std::scoped_lock lock(mLock);
return mLocked.viewport.displayId;
}
@@ -467,10 +467,10 @@
std::function<bool(nsecs_t)> func = std::bind(&MouseCursorController::doAnimations, this, _1);
/*
- * Using -1 for displayId here to avoid removing the callback
+ * Using ui::ADISPLAY_ID_NONE for displayId here to avoid removing the callback
* if a TouchSpotController with the same display is removed.
*/
- mContext.addAnimationCallback(-1, func);
+ mContext.addAnimationCallback(ui::ADISPLAY_ID_NONE, func);
}
} // namespace android
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 00dc085..dc7e8ca 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -47,7 +47,7 @@
void move(float deltaX, float deltaY);
void setPosition(float x, float y);
FloatPoint getPosition() const;
- int32_t getDisplayId() const;
+ ui::LogicalDisplayId getDisplayId() const;
void fade(PointerControllerInterface::Transition transition);
void unfade(PointerControllerInterface::Transition transition);
void setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources);
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index f97992f..cca1b07 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -142,7 +142,7 @@
}
void PointerController::move(float deltaX, float deltaY) {
- const int32_t displayId = mCursorController.getDisplayId();
+ const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
vec2 transformed;
{
std::scoped_lock lock(getLock());
@@ -153,7 +153,7 @@
}
void PointerController::setPosition(float x, float y) {
- const int32_t displayId = mCursorController.getDisplayId();
+ const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
vec2 transformed;
{
std::scoped_lock lock(getLock());
@@ -164,7 +164,7 @@
}
FloatPoint PointerController::getPosition() const {
- const int32_t displayId = mCursorController.getDisplayId();
+ const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
const auto p = mCursorController.getPosition();
{
std::scoped_lock lock(getLock());
@@ -173,7 +173,7 @@
}
}
-int32_t PointerController::getDisplayId() const {
+ui::LogicalDisplayId PointerController::getDisplayId() const {
return mCursorController.getDisplayId();
}
@@ -202,7 +202,7 @@
}
void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
- BitSet32 spotIdBits, int32_t displayId) {
+ BitSet32 spotIdBits, ui::LogicalDisplayId displayId) {
std::scoped_lock lock(getLock());
std::array<PointerCoords, MAX_POINTERS> outSpotCoords{};
const ui::Transform& transform = getTransformForDisplayLocked(displayId);
@@ -286,7 +286,7 @@
mCursorController.setCustomPointerIcon(icon);
}
-void PointerController::setSkipScreenshot(int32_t displayId, bool skip) {
+void PointerController::setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) {
std::scoped_lock lock(getLock());
if (skip) {
mLocked.displaysToSkipScreenshot.insert(displayId);
@@ -300,14 +300,14 @@
}
void PointerController::onDisplayViewportsUpdated(const std::vector<DisplayViewport>& viewports) {
- std::unordered_set<int32_t> displayIdSet;
+ std::unordered_set<ui::LogicalDisplayId> displayIdSet;
for (const DisplayViewport& viewport : viewports) {
displayIdSet.insert(viewport.displayId);
}
std::scoped_lock lock(getLock());
for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) {
- int32_t displayId = it->first;
+ ui::LogicalDisplayId displayId = it->first;
if (!displayIdSet.count(displayId)) {
/*
* Ensures that an in-progress animation won't dereference
@@ -326,7 +326,8 @@
mLocked.mDisplayInfos = displayInfo;
}
-const ui::Transform& PointerController::getTransformForDisplayLocked(int displayId) const {
+const ui::Transform& PointerController::getTransformForDisplayLocked(
+ ui::LogicalDisplayId displayId) const {
const auto& di = mLocked.mDisplayInfos;
auto it = std::find_if(di.begin(), di.end(), [displayId](const gui::DisplayInfo& info) {
return info.displayId == displayId;
@@ -339,7 +340,8 @@
std::scoped_lock lock(getLock());
dump += StringPrintf(INDENT2 "Presentation: %s\n",
ftl::enum_string(mLocked.presentation).c_str());
- dump += StringPrintf(INDENT2 "Pointer Display ID: %" PRIu32 "\n", mLocked.pointerDisplayId);
+ dump += StringPrintf(INDENT2 "Pointer Display ID: %s\n",
+ mLocked.pointerDisplayId.toString().c_str());
dump += StringPrintf(INDENT2 "Viewports:\n");
for (const auto& info : mLocked.mDisplayInfos) {
info.dump(dump, INDENT3);
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index eaf34d5..70e5c24 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -55,18 +55,18 @@
void move(float deltaX, float deltaY) override;
void setPosition(float x, float y) override;
FloatPoint getPosition() const override;
- int32_t getDisplayId() const override;
+ ui::LogicalDisplayId getDisplayId() const override;
void fade(Transition transition) override;
void unfade(Transition transition) override;
void setDisplayViewport(const DisplayViewport& viewport) override;
void setPresentation(Presentation presentation) override;
void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
- BitSet32 spotIdBits, int32_t displayId) override;
+ BitSet32 spotIdBits, ui::LogicalDisplayId displayId) override;
void clearSpots() override;
void updatePointerIcon(PointerIconStyle iconId) override;
void setCustomPointerIcon(const SpriteIcon& icon) override;
- void setSkipScreenshot(int32_t displayId, bool skip) override;
+ void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) override;
virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout);
void doInactivityTimeout();
@@ -109,11 +109,11 @@
struct Locked {
Presentation presentation;
- int32_t pointerDisplayId = ADISPLAY_ID_NONE;
+ ui::LogicalDisplayId pointerDisplayId = ui::ADISPLAY_ID_NONE;
std::vector<gui::DisplayInfo> mDisplayInfos;
- std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
- std::unordered_set<int32_t /* displayId */> displaysToSkipScreenshot;
+ std::unordered_map<ui::LogicalDisplayId, TouchSpotController> spotControllers;
+ std::unordered_set<ui::LogicalDisplayId> displaysToSkipScreenshot;
} mLocked GUARDED_BY(getLock());
class DisplayInfoListener : public gui::WindowInfosListener {
@@ -132,7 +132,8 @@
sp<DisplayInfoListener> mDisplayInfoListener;
const WindowListenerUnregisterConsumer mUnregisterWindowInfosListener;
- const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(getLock());
+ const ui::Transform& getTransformForDisplayLocked(ui::LogicalDisplayId displayId) const
+ REQUIRES(getLock());
void clearSpotsLocked() REQUIRES(getLock());
};
@@ -148,7 +149,7 @@
void setPresentation(Presentation) override {
LOG_ALWAYS_FATAL("Should not be called");
}
- void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override {
+ void setSpots(const PointerCoords*, const uint32_t*, BitSet32, ui::LogicalDisplayId) override {
LOG_ALWAYS_FATAL("Should not be called");
}
void clearSpots() override {
@@ -176,7 +177,7 @@
FloatPoint getPosition() const override {
LOG_ALWAYS_FATAL("Should not be called");
}
- int32_t getDisplayId() const override {
+ ui::LogicalDisplayId getDisplayId() const override {
LOG_ALWAYS_FATAL("Should not be called");
}
void fade(Transition) override {
@@ -212,7 +213,7 @@
void setPresentation(Presentation) override {
LOG_ALWAYS_FATAL("Should not be called");
}
- void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override {
+ void setSpots(const PointerCoords*, const uint32_t*, BitSet32, ui::LogicalDisplayId) override {
LOG_ALWAYS_FATAL("Should not be called");
}
void clearSpots() override {
diff --git a/libs/input/PointerControllerContext.cpp b/libs/input/PointerControllerContext.cpp
index 15c3517..747eb8e 100644
--- a/libs/input/PointerControllerContext.cpp
+++ b/libs/input/PointerControllerContext.cpp
@@ -138,12 +138,12 @@
return 1; // keep the callback
}
-void PointerControllerContext::addAnimationCallback(int32_t displayId,
+void PointerControllerContext::addAnimationCallback(ui::LogicalDisplayId displayId,
std::function<bool(nsecs_t)> callback) {
mAnimator.addCallback(displayId, callback);
}
-void PointerControllerContext::removeAnimationCallback(int32_t displayId) {
+void PointerControllerContext::removeAnimationCallback(ui::LogicalDisplayId displayId) {
mAnimator.removeCallback(displayId);
}
@@ -161,14 +161,14 @@
}
}
-void PointerControllerContext::PointerAnimator::addCallback(int32_t displayId,
+void PointerControllerContext::PointerAnimator::addCallback(ui::LogicalDisplayId displayId,
std::function<bool(nsecs_t)> callback) {
std::scoped_lock lock(mLock);
mLocked.callbacks[displayId] = callback;
startAnimationLocked();
}
-void PointerControllerContext::PointerAnimator::removeCallback(int32_t displayId) {
+void PointerControllerContext::PointerAnimator::removeCallback(ui::LogicalDisplayId displayId) {
std::scoped_lock lock(mLock);
auto it = mLocked.callbacks.find(displayId);
if (it == mLocked.callbacks.end()) {
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index e893c49..d422148 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -72,12 +72,13 @@
virtual ~PointerControllerPolicyInterface() {}
public:
- virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) = 0;
- virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) = 0;
+ virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) = 0;
+ virtual void loadPointerResources(PointerResources* outResources,
+ ui::LogicalDisplayId displayId) = 0;
virtual void loadAdditionalMouseResources(
std::map<PointerIconStyle, SpriteIcon>* outResources,
std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
- int32_t displayId) = 0;
+ ui::LogicalDisplayId displayId) = 0;
virtual PointerIconStyle getDefaultPointerIconId() = 0;
virtual PointerIconStyle getDefaultStylusIconId() = 0;
virtual PointerIconStyle getCustomPointerIconId() = 0;
@@ -102,7 +103,7 @@
nsecs_t getAnimationTime();
- void clearSpotsByDisplay(int32_t displayId);
+ void clearSpotsByDisplay(ui::LogicalDisplayId displayId);
void setHandlerController(std::shared_ptr<PointerController> controller);
void setCallbackController(std::shared_ptr<PointerController> controller);
@@ -112,8 +113,9 @@
void handleDisplayEvents();
- void addAnimationCallback(int32_t displayId, std::function<bool(nsecs_t)> callback);
- void removeAnimationCallback(int32_t displayId);
+ void addAnimationCallback(ui::LogicalDisplayId displayId,
+ std::function<bool(nsecs_t)> callback);
+ void removeAnimationCallback(ui::LogicalDisplayId displayId);
class MessageHandler : public virtual android::MessageHandler {
public:
@@ -136,8 +138,8 @@
public:
PointerAnimator(PointerControllerContext& context);
- void addCallback(int32_t displayId, std::function<bool(nsecs_t)> callback);
- void removeCallback(int32_t displayId);
+ void addCallback(ui::LogicalDisplayId displayId, std::function<bool(nsecs_t)> callback);
+ void removeCallback(ui::LogicalDisplayId displayId);
void handleVsyncEvents();
nsecs_t getAnimationTimeLocked();
@@ -148,7 +150,7 @@
bool animationPending{false};
nsecs_t animationTime{systemTime(SYSTEM_TIME_MONOTONIC)};
- std::unordered_map<int32_t, std::function<bool(nsecs_t)>> callbacks;
+ std::unordered_map<ui::LogicalDisplayId, std::function<bool(nsecs_t)>> callbacks;
} mLocked GUARDED_BY(mLock);
DisplayEventReceiver mDisplayEventReceiver;
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 0baa929..af49939 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -19,9 +19,9 @@
#include "SpriteController.h"
-#include <log/log.h>
-#include <utils/String8.h>
+#include <android-base/logging.h>
#include <gui/Surface.h>
+#include <utils/String8.h>
namespace android {
@@ -340,13 +340,14 @@
}
}
-sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height, int32_t displayId,
+sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height,
+ ui::LogicalDisplayId displayId,
bool hideOnMirrored) {
ensureSurfaceComposerClient();
const sp<SurfaceControl> parent = mParentSurfaceProvider(displayId);
if (parent == nullptr) {
- ALOGE("Failed to get the parent surface for pointers on display %d", displayId);
+ LOG(ERROR) << "Failed to get the parent surface for pointers on display " << displayId;
}
int32_t createFlags = ISurfaceComposerClient::eHidden | ISurfaceComposerClient::eCursorWindow;
@@ -475,7 +476,7 @@
}
}
-void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) {
+void SpriteController::SpriteImpl::setDisplayId(ui::LogicalDisplayId displayId) {
AutoMutex _l(mController.mLock);
if (mLocked.state.displayId != displayId) {
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index fdb15506..070c90c 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -95,7 +95,7 @@
virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
/* Sets the id of the display where the sprite should be shown. */
- virtual void setDisplayId(int32_t displayId) = 0;
+ virtual void setDisplayId(ui::LogicalDisplayId displayId) = 0;
/* Sets the flag to hide sprite on mirrored displays.
* This will add ISurfaceComposerClient::eSkipScreenshot flag to the sprite. */
@@ -115,7 +115,7 @@
*/
class SpriteController {
public:
- using ParentSurfaceProvider = std::function<sp<SurfaceControl>(int /*displayId*/)>;
+ using ParentSurfaceProvider = std::function<sp<SurfaceControl>(ui::LogicalDisplayId)>;
SpriteController(const sp<Looper>& looper, int32_t overlayLayer, ParentSurfaceProvider parent);
SpriteController(const SpriteController&) = delete;
SpriteController& operator=(const SpriteController&) = delete;
@@ -174,7 +174,7 @@
int32_t layer{0};
float alpha{1.0f};
SpriteTransformationMatrix transformationMatrix;
- int32_t displayId{ADISPLAY_ID_DEFAULT};
+ ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_DEFAULT};
sp<SurfaceControl> surfaceControl;
int32_t surfaceWidth{0};
@@ -208,7 +208,7 @@
virtual void setLayer(int32_t layer);
virtual void setAlpha(float alpha);
virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
- virtual void setDisplayId(int32_t displayId);
+ virtual void setDisplayId(ui::LogicalDisplayId displayId);
virtual void setSkipScreenshot(bool skip);
inline const SpriteState& getStateLocked() const {
@@ -273,7 +273,7 @@
void doDisposeSurfaces();
void ensureSurfaceComposerClient();
- sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, int32_t displayId,
+ sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, ui::LogicalDisplayId displayId,
bool hideOnMirrored);
};
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index 530d541..7462481 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -40,7 +40,7 @@
// --- Spot ---
void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float newX, float newY,
- int32_t displayId, bool skipScreenshot) {
+ ui::LogicalDisplayId displayId, bool skipScreenshot) {
sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
sprite->setAlpha(alpha);
sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
@@ -69,7 +69,8 @@
// --- TouchSpotController ---
-TouchSpotController::TouchSpotController(int32_t displayId, PointerControllerContext& context)
+TouchSpotController::TouchSpotController(ui::LogicalDisplayId displayId,
+ PointerControllerContext& context)
: mDisplayId(displayId), mContext(context) {
mContext.getPolicy()->loadPointerResources(&mResources, mDisplayId);
}
@@ -94,7 +95,7 @@
const PointerCoords& c = spotCoords[spotIdToIndex[id]];
ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id,
c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y),
- c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId);
+ c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId.id);
}
#endif
@@ -274,7 +275,7 @@
out += prefix;
out += "SpotController:\n";
out += prefix;
- StringAppendF(&out, INDENT "DisplayId: %" PRId32 "\n", mDisplayId);
+ StringAppendF(&out, INDENT "DisplayId: %s\n", mDisplayId.toString().c_str());
std::scoped_lock lock(mLock);
out += prefix;
StringAppendF(&out, INDENT "Animating: %s\n", toString(mLocked.animating));
diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h
index 608653c..ac37fa4 100644
--- a/libs/input/TouchSpotController.h
+++ b/libs/input/TouchSpotController.h
@@ -29,7 +29,7 @@
*/
class TouchSpotController {
public:
- TouchSpotController(int32_t displayId, PointerControllerContext& context);
+ TouchSpotController(ui::LogicalDisplayId displayId, PointerControllerContext& context);
~TouchSpotController();
void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
BitSet32 spotIdBits, bool skipScreenshot);
@@ -59,7 +59,7 @@
y(0.0f),
mLastIcon(nullptr) {}
- void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId,
+ void updateSprite(const SpriteIcon* icon, float x, float y, ui::LogicalDisplayId displayId,
bool skipScreenshot);
void dump(std::string& out, const char* prefix = "") const;
@@ -67,7 +67,7 @@
const SpriteIcon* mLastIcon;
};
- int32_t mDisplayId;
+ ui::LogicalDisplayId mDisplayId;
mutable std::mutex mLock;
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 3bc0e24..7a13380 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -52,12 +52,13 @@
class MockPointerControllerPolicyInterface : public PointerControllerPolicyInterface {
public:
- virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) override;
- virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) override;
+ virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) override;
+ virtual void loadPointerResources(PointerResources* outResources,
+ ui::LogicalDisplayId displayId) override;
virtual void loadAdditionalMouseResources(
std::map<PointerIconStyle, SpriteIcon>* outResources,
std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
- int32_t displayId) override;
+ ui::LogicalDisplayId displayId) override;
virtual PointerIconStyle getDefaultPointerIconId() override;
virtual PointerIconStyle getDefaultStylusIconId() override;
virtual PointerIconStyle getCustomPointerIconId() override;
@@ -73,13 +74,13 @@
bool additionalMouseResourcesLoaded{false};
};
-void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) {
+void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId) {
loadPointerIconForType(icon, CURSOR_TYPE_DEFAULT);
pointerIconLoaded = true;
}
void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources* outResources,
- int32_t) {
+ ui::LogicalDisplayId) {
loadPointerIconForType(&outResources->spotHover, CURSOR_TYPE_HOVER);
loadPointerIconForType(&outResources->spotTouch, CURSOR_TYPE_TOUCH);
loadPointerIconForType(&outResources->spotAnchor, CURSOR_TYPE_ANCHOR);
@@ -88,7 +89,7 @@
void MockPointerControllerPolicyInterface::loadAdditionalMouseResources(
std::map<PointerIconStyle, SpriteIcon>* outResources,
- std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t) {
+ std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, ui::LogicalDisplayId) {
SpriteIcon icon;
PointerAnimation anim;
@@ -165,7 +166,7 @@
PointerControllerTest();
~PointerControllerTest();
- void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT);
+ void ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_DEFAULT);
sp<MockSprite> mPointerSprite;
sp<MockPointerControllerPolicyInterface> mPolicy;
@@ -204,7 +205,7 @@
mThread.join();
}
-void PointerControllerTest::ensureDisplayViewportIsSet(int32_t displayId) {
+void PointerControllerTest::ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId) {
DisplayViewport viewport;
viewport.displayId = displayId;
viewport.logicalRight = 1600;
@@ -334,23 +335,23 @@
// Update spots to sync state with sprite
mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
- ADISPLAY_ID_DEFAULT);
+ ui::ADISPLAY_ID_DEFAULT);
testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
// Marking the display to skip screenshot should update sprite as well
- mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, true);
+ mPointerController->setSkipScreenshot(ui::ADISPLAY_ID_DEFAULT, true);
EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true));
// Update spots to sync state with sprite
mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
- ADISPLAY_ID_DEFAULT);
+ ui::ADISPLAY_ID_DEFAULT);
testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
// Reset flag and verify again
- mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, false);
+ mPointerController->setSkipScreenshot(ui::ADISPLAY_ID_DEFAULT, false);
EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false));
mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
- ADISPLAY_ID_DEFAULT);
+ ui::ADISPLAY_ID_DEFAULT);
testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
}
diff --git a/libs/input/tests/mocks/MockSprite.h b/libs/input/tests/mocks/MockSprite.h
index 0867221..21628fb 100644
--- a/libs/input/tests/mocks/MockSprite.h
+++ b/libs/input/tests/mocks/MockSprite.h
@@ -33,7 +33,7 @@
MOCK_METHOD(void, setLayer, (int32_t), (override));
MOCK_METHOD(void, setAlpha, (float), (override));
MOCK_METHOD(void, setTransformationMatrix, (const SpriteTransformationMatrix&), (override));
- MOCK_METHOD(void, setDisplayId, (int32_t), (override));
+ MOCK_METHOD(void, setDisplayId, (ui::LogicalDisplayId), (override));
MOCK_METHOD(void, setSkipScreenshot, (bool), (override));
};
diff --git a/libs/input/tests/mocks/MockSpriteController.h b/libs/input/tests/mocks/MockSpriteController.h
index 62f1d65..9ef6b7c 100644
--- a/libs/input/tests/mocks/MockSpriteController.h
+++ b/libs/input/tests/mocks/MockSpriteController.h
@@ -27,7 +27,7 @@
public:
MockSpriteController(sp<Looper> looper)
- : SpriteController(looper, 0, [](int) { return nullptr; }) {}
+ : SpriteController(looper, 0, [](ui::LogicalDisplayId) { return nullptr; }) {}
~MockSpriteController() {}
MOCK_METHOD(sp<Sprite>, createSprite, (), (override));
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 50ebdd5..0da32bd 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -18,6 +18,7 @@
import android.app.PendingIntent
import android.app.assist.AssistStructure
+import android.content.ComponentName
import android.content.Context
import android.credentials.CredentialManager
import android.credentials.GetCredentialRequest
@@ -34,6 +35,10 @@
import android.os.OutcomeReceiver
import android.os.ResultReceiver
import android.service.autofill.AutofillService
+import com.android.credentialmanager.model.get.ProviderInfo
+import androidx.core.graphics.drawable.toBitmap
+import com.android.credentialmanager.model.get.ActionEntryInfo
+import com.android.credentialmanager.model.EntryInfo
import android.service.autofill.Dataset
import android.service.autofill.Field
import android.service.autofill.FillCallback
@@ -63,7 +68,8 @@
import com.android.credentialmanager.getflow.toProviderDisplayInfo
import com.android.credentialmanager.ktx.credentialEntry
import com.android.credentialmanager.model.CredentialType
-import com.android.credentialmanager.model.get.CredentialEntryInfo
+import java.util.ArrayList
+import java.util.Objects
import java.util.concurrent.Executors
import org.json.JSONException
import org.json.JSONObject
@@ -121,8 +127,11 @@
val responseClientState = Bundle()
responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, false)
- val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
- requestId, resultReceiver, responseClientState)
+ val uniqueAutofillIdsForRequest: MutableSet<AutofillId> = mutableSetOf()
+ val getCredRequest: GetCredentialRequest? = getCredManRequest(
+ structure, sessionId,
+ requestId, resultReceiver, responseClientState, uniqueAutofillIdsForRequest
+ )
// TODO(b/324635774): Use callback for validating. If the request is coming
// directly from the view, there should be a corresponding callback, otherwise
// we should fail fast,
@@ -132,14 +141,17 @@
return
}
val credentialManager: CredentialManager =
- getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
+ getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
val outcome = object : OutcomeReceiver<GetCandidateCredentialsResponse,
GetCandidateCredentialsException> {
override fun onResult(result: GetCandidateCredentialsResponse) {
Log.i(TAG, "getCandidateCredentials onResult")
- val fillResponse = convertToFillResponse(result, request,
- responseClientState, GetFlowUtils.extractTypePriorityMap(getCredRequest))
+ val fillResponse = convertToFillResponse(
+ result, request,
+ responseClientState, GetFlowUtils.extractTypePriorityMap(getCredRequest),
+ uniqueAutofillIdsForRequest
+ )
if (fillResponse != null) {
callback.onSuccess(fillResponse)
} else {
@@ -195,58 +207,131 @@
private fun convertToFillResponse(
getCredResponse: GetCandidateCredentialsResponse,
- filLRequest: FillRequest,
+ fillRequest: FillRequest,
responseClientState: Bundle,
typePriorityMap: Map<String, Int>,
+ uniqueAutofillIdsForRequest: MutableSet<AutofillId>
): FillResponse? {
val candidateProviders = getCredResponse.candidateProviderDataList
if (candidateProviders.isEmpty()) {
return null
}
-
+ val primaryProviderComponentName = getCredResponse.primaryProviderComponentName
val entryIconMap: Map<String, Icon> = getEntryToIconMap(candidateProviders)
val autofillIdToProvidersMap: Map<AutofillId, ArrayList<GetCredentialProviderData>> =
- mapAutofillIdToProviders(candidateProviders)
+ mapAutofillIdToProviders(
+ uniqueAutofillIdsForRequest,
+ candidateProviders,
+ primaryProviderComponentName
+ )
val fillResponseBuilder = FillResponse.Builder()
fillResponseBuilder.setFlags(FillResponse.FLAG_CREDENTIAL_MANAGER_RESPONSE)
- var validFillResponse = false
autofillIdToProvidersMap.forEach { (autofillId, providers) ->
- validFillResponse = processProvidersForAutofillId(
- filLRequest, autofillId, providers, entryIconMap, fillResponseBuilder,
- getCredResponse.intent, typePriorityMap)
- .or(validFillResponse)
+ var credentialDatasetAdded = addCredentialDatasetsForAutofillId(fillRequest,
+ autofillId, providers, entryIconMap, fillResponseBuilder, typePriorityMap)
+ if (!credentialDatasetAdded && primaryProviderComponentName != null) {
+ val providerList = GetFlowUtils.toProviderList(
+ providers,
+ this@CredentialAutofillService
+ )
+ val primaryProviderInfo =
+ providerList.find { provider -> primaryProviderComponentName
+ .flattenToString().equals(provider.id) }
+ if (primaryProviderInfo != null) {
+ addActionDatasetsForAutofillId(
+ fillRequest,
+ autofillId,
+ primaryProviderInfo,
+ fillResponseBuilder
+ )
+ }
+ }
}
- if (!validFillResponse) {
- return null
+ for (autofillId in uniqueAutofillIdsForRequest) {
+ addMoreOptionsDataset(
+ fillRequest,
+ autofillId,
+ fillResponseBuilder,
+ getCredResponse.intent.putExtra(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, ArrayList(candidateProviders)
+ )
+ )
}
fillResponseBuilder.setClientState(responseClientState)
return fillResponseBuilder.build()
}
- private fun processProvidersForAutofillId(
- filLRequest: FillRequest,
- autofillId: AutofillId,
- providerDataList: ArrayList<GetCredentialProviderData>,
- entryIconMap: Map<String, Icon>,
- fillResponseBuilder: FillResponse.Builder,
- bottomSheetIntent: Intent,
- typePriorityMap: Map<String, Int>,
+ private fun addActionDatasetsForAutofillId(
+ fillRequest: FillRequest,
+ autofillId: AutofillId,
+ primaryProvider: ProviderInfo,
+ fillResponseBuilder: FillResponse.Builder,
+ ): Boolean {
+ var index = 0
+ var datasetAdded = false
+ primaryProvider.actionEntryList.forEach { actionEntry ->
+ if (index >= maxDatasetDisplayLimit(primaryProvider.actionEntryList.size)) {
+ return@forEach
+ }
+ val pendingIntent = actionEntry.pendingIntent
+ if (pendingIntent == null) {
+ Log.e(TAG, "Pending intent for action chip is null")
+ return@forEach
+ }
+
+ val icon: Icon? = Icon.createWithBitmap(actionEntry.icon.toBitmap())
+ if (icon == null) {
+ Log.e(TAG, "Icon for action chip is null")
+ return@forEach
+ }
+
+ val presentations = constructPresentations(
+ fillRequest,
+ index,
+ actionEntry,
+ pendingIntent,
+ icon,
+ actionEntry.title,
+ actionEntry.subTitle,
+ primaryProvider.actionEntryList.size
+ )
+
+ fillResponseBuilder.addDataset(
+ Dataset.Builder()
+ .setField(
+ autofillId,
+ Field.Builder().setPresentations(presentations).build()
+ )
+ .setAuthentication(pendingIntent.intentSender)
+ .build()
+ )
+ datasetAdded = true
+
+ index++
+ }
+
+ return datasetAdded
+ }
+
+ private fun addCredentialDatasetsForAutofillId(
+ fillRequest: FillRequest,
+ autofillId: AutofillId,
+ providerDataList: List<GetCredentialProviderData>,
+ entryIconMap: Map<String, Icon>,
+ fillResponseBuilder: FillResponse.Builder,
+ typePriorityMap: Map<String, Int>,
): Boolean {
val providerList = GetFlowUtils.toProviderList(
providerDataList,
- this@CredentialAutofillService)
+ this@CredentialAutofillService
+ )
if (providerList.isEmpty()) {
return false
}
val providerDisplayInfo: ProviderDisplayInfo =
- toProviderDisplayInfo(providerList, typePriorityMap)
+ toProviderDisplayInfo(providerList, typePriorityMap)
var totalEntryCount = providerDisplayInfo.sortedUserNameToCredentialEntryList.size
- val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest
- val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
- val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
- val maxDatasetDisplayLimit = this.resources.getInteger(
- com.android.credentialmanager.R.integer.autofill_max_visible_datasets)
- .coerceAtMost(totalEntryCount)
+
var i = 0
var datasetAdded = false
@@ -260,8 +345,6 @@
}
}
}
- bottomSheetIntent.putExtra(
- ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList)
providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{
val primaryEntry = it.sortedCredentialEntryList.first()
val pendingIntent = primaryEntry.pendingIntent
@@ -271,7 +354,7 @@
Log.e(TAG, "PendingIntent was missing from the entry.")
return@usernameLoop
}
- if (i >= maxDatasetDisplayLimit) {
+ if (i >= maxDatasetDisplayLimit(totalEntryCount)) {
return@usernameLoop
}
val icon: Icon = if (primaryEntry.icon == null) {
@@ -280,116 +363,172 @@
getDefaultIcon()
} else {
entryIconMap[primaryEntry.entryKey + primaryEntry.entrySubkey]
- ?: getDefaultIcon()
+ ?: getDefaultIcon()
}
- // Create inline presentation
- var inlinePresentation: InlinePresentation? = null
- if (inlinePresentationSpecs != null && i < maxDatasetDisplayLimit) {
- val spec: InlinePresentationSpec? = if (i < inlinePresentationSpecsCount) {
- inlinePresentationSpecs[i]
- } else {
- inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
- }
- if (spec != null) {
- inlinePresentation = createInlinePresentation(primaryEntry, pendingIntent, icon,
- InlinePresentationsFactory.modifyInlinePresentationSpec
- (this@CredentialAutofillService, spec),
- duplicateDisplayNamesForPasskeys)
- }
+ val displayName = primaryEntry.displayName
+ val title: String = if (primaryEntry.credentialType == CredentialType.PASSKEY &&
+ displayName != null
+ ) {
+ displayName
+ } else {
+ primaryEntry.userName
}
- var dropdownPresentation: RemoteViews? = null
- if (i < maxDatasetDisplayLimit) {
- dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
- this, icon, primaryEntry, /*isFirstEntry= */ i == 0,
- /*isLastEntry= */ (totalEntryCount - i == 1))
+ val subtitle = if (primaryEntry.credentialType ==
+ CredentialType.PASSKEY && duplicateDisplayNamesForPasskeys[title] == true
+ ) {
+ primaryEntry.userName
+ } else {
+ null
}
-
- val dataSetBuilder = Dataset.Builder()
- val presentationBuilder = Presentations.Builder()
- if (dropdownPresentation != null) {
- presentationBuilder.setMenuPresentation(dropdownPresentation)
- }
- if (inlinePresentation != null) {
- presentationBuilder.setInlinePresentation(inlinePresentation)
- }
-
+ val presentations =
+ constructPresentations(
+ fillRequest, i, primaryEntry, pendingIntent,
+ icon, title, subtitle, totalEntryCount
+ )
fillResponseBuilder.addDataset(
- dataSetBuilder
- .setField(
- autofillId,
- Field.Builder().setPresentations(
- presentationBuilder.build())
- .build())
- .setAuthentication(pendingIntent.intentSender)
- .setCredentialFillInIntent(fillInIntent)
- .build())
+ Dataset.Builder()
+ .setField(
+ autofillId,
+ Field.Builder().setPresentations(
+ presentations
+ )
+ .build()
+ )
+ .setAuthentication(pendingIntent.intentSender)
+ .setCredentialFillInIntent(fillInIntent)
+ .build()
+ )
datasetAdded = true
i++
}
- val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs,
- inlinePresentationSpecsCount)
- if (datasetAdded) {
- addDropdownMoreOptionsPresentation(bottomSheetIntent, autofillId, fillResponseBuilder)
- if (pinnedSpec != null) {
- addPinnedInlineSuggestion(pinnedSpec, autofillId,
- fillResponseBuilder, bottomSheetIntent)
- }
- }
return datasetAdded
}
- private fun createInlinePresentation(
- primaryEntry: CredentialEntryInfo,
- pendingIntent: PendingIntent,
- icon: Icon,
- spec: InlinePresentationSpec,
- duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>
- ): InlinePresentation {
- val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY &&
- primaryEntry.displayName != null) {
- primaryEntry.displayName!!
- } else {
- primaryEntry.userName
+ private fun addMoreOptionsDataset(
+ fillRequest: FillRequest,
+ autofillId: AutofillId,
+ fillResponseBuilder: FillResponse.Builder,
+ bottomSheetIntent: Intent
+ ) {
+ val inlineSuggestionsRequest = fillRequest.inlineSuggestionsRequest
+ val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
+ val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
+ val pinnedSpec = getLastInlinePresentationSpec(
+ inlinePresentationSpecs,
+ inlinePresentationSpecsCount
+ )
+ addDropdownMoreOptionsPresentation(bottomSheetIntent, autofillId, fillResponseBuilder)
+ if (pinnedSpec != null) {
+ addPinnedInlineSuggestion(
+ pinnedSpec, autofillId,
+ fillResponseBuilder, bottomSheetIntent
+ )
}
+ }
+
+ private fun constructPresentations(
+ fillRequest: FillRequest,
+ index: Int,
+ entry: EntryInfo,
+ pendingIntent: PendingIntent,
+ icon: Icon,
+ title: String,
+ subtitle: String?,
+ totalEntryCount: Int
+ ): Presentations {
+ val inlineSuggestionsRequest = fillRequest.inlineSuggestionsRequest
+ val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
+ val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
+
+ // Create inline presentation
+ var inlinePresentation: InlinePresentation? = null
+ if (inlinePresentationSpecs != null && index < maxDatasetDisplayLimit(totalEntryCount)) {
+ val spec: InlinePresentationSpec? = if (index < inlinePresentationSpecsCount) {
+ inlinePresentationSpecs[index]
+ } else {
+ inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+ }
+ if (spec != null) {
+ inlinePresentation = createInlinePresentation(
+ pendingIntent, icon,
+ InlinePresentationsFactory.modifyInlinePresentationSpec
+ (this@CredentialAutofillService, spec),
+ title, subtitle, entry is ActionEntryInfo
+ )
+ }
+ }
+ var dropdownPresentation: RemoteViews? = null
+ if (index < maxDatasetDisplayLimit(totalEntryCount)) {
+ dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
+ this, icon, entry, /*isFirstEntry= */ index == 0,
+ /*isLastEntry= */ (totalEntryCount - index == 1)
+ )
+ }
+
+ val presentationBuilder = Presentations.Builder()
+ if (dropdownPresentation != null) {
+ presentationBuilder.setMenuPresentation(dropdownPresentation)
+ }
+ if (inlinePresentation != null) {
+ presentationBuilder.setInlinePresentation(inlinePresentation)
+ }
+ return presentationBuilder.build()
+ }
+
+ private fun maxDatasetDisplayLimit(totalEntryCount: Int) = this.resources.getInteger(
+ com.android.credentialmanager.R.integer.autofill_max_visible_datasets
+ ).coerceAtMost(totalEntryCount)
+
+ private fun createInlinePresentation(
+ pendingIntent: PendingIntent,
+ icon: Icon,
+ spec: InlinePresentationSpec,
+ title: String,
+ subtitle: String?,
+ isActionEntry: Boolean
+ ): InlinePresentation {
val sliceBuilder = InlineSuggestionUi
- .newContentBuilder(pendingIntent)
- .setTitle(displayName)
+ .newContentBuilder(pendingIntent)
+ .setTitle(title)
icon.setTintBlendMode(BlendMode.DST)
sliceBuilder.setStartIcon(icon)
- if (primaryEntry.credentialType ==
- CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
- sliceBuilder.setSubtitle(primaryEntry.userName)
+ if (subtitle != null && !isActionEntry) {
+ sliceBuilder.setSubtitle(subtitle)
}
return InlinePresentation(
- sliceBuilder.build().slice, spec, /* pinned= */ false)
+ sliceBuilder.build().slice, spec, /* pinned= */ false
+ )
}
private fun addDropdownMoreOptionsPresentation(
- bottomSheetIntent: Intent,
- autofillId: AutofillId,
- fillResponseBuilder: FillResponse.Builder
+ bottomSheetIntent: Intent,
+ autofillId: AutofillId,
+ fillResponseBuilder: FillResponse.Builder
) {
val presentationBuilder = Presentations.Builder()
- .setMenuPresentation(
- RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
+ .setMenuPresentation(
+ RemoteViewsFactory.createMoreSignInOptionsPresentation(this)
+ )
val pendingIntent = setUpBottomSheetPendingIntent(bottomSheetIntent)
fillResponseBuilder.addDataset(
- Dataset.Builder()
- .setId(AutofillManager.PINNED_DATASET_ID)
- .setField(
- autofillId,
- Field.Builder().setPresentations(
- presentationBuilder.build())
- .build())
- .setAuthentication(pendingIntent.intentSender)
+ Dataset.Builder()
+ .setId(AutofillManager.PINNED_DATASET_ID)
+ .setField(
+ autofillId,
+ Field.Builder().setPresentations(
+ presentationBuilder.build()
+ )
.build()
+ )
+ .setAuthentication(pendingIntent.intentSender)
+ .build()
)
}
private fun getLastInlinePresentationSpec(
- inlinePresentationSpecs: List<InlinePresentationSpec>?,
- inlinePresentationSpecsCount: Int
+ inlinePresentationSpecs: List<InlinePresentationSpec>?,
+ inlinePresentationSpecsCount: Int
): InlinePresentationSpec? {
if (inlinePresentationSpecs != null) {
return inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
@@ -398,40 +537,47 @@
}
private fun addPinnedInlineSuggestion(
- spec: InlinePresentationSpec,
- autofillId: AutofillId,
- fillResponseBuilder: FillResponse.Builder,
- bottomSheetIntent: Intent
+ spec: InlinePresentationSpec,
+ autofillId: AutofillId,
+ fillResponseBuilder: FillResponse.Builder,
+ bottomSheetIntent: Intent
) {
val pendingIntent = setUpBottomSheetPendingIntent(bottomSheetIntent)
val dataSetBuilder = Dataset.Builder()
val sliceBuilder = InlineSuggestionUi
- .newContentBuilder(pendingIntent)
- .setStartIcon(Icon.createWithResource(this,
- com.android.credentialmanager.R.drawable.more_horiz_24px))
+ .newContentBuilder(pendingIntent)
+ .setStartIcon(
+ Icon.createWithResource(
+ this,
+ com.android.credentialmanager.R.drawable.more_horiz_24px
+ )
+ )
val presentationBuilder = Presentations.Builder()
- .setInlinePresentation(InlinePresentation(
- sliceBuilder.build().slice, spec, /* pinned= */ true))
+ .setInlinePresentation(
+ InlinePresentation(
+ sliceBuilder.build().slice, spec, /* pinned= */ true
+ )
+ )
fillResponseBuilder.addDataset(
- dataSetBuilder
- .setId(AutofillManager.PINNED_DATASET_ID)
- .setField(
- autofillId,
- Field.Builder().setPresentations(
- presentationBuilder.build()
- ).build()
- )
- .setAuthentication(pendingIntent.intentSender)
- .build()
+ dataSetBuilder
+ .setId(AutofillManager.PINNED_DATASET_ID)
+ .setField(
+ autofillId,
+ Field.Builder().setPresentations(
+ presentationBuilder.build()
+ ).build()
+ )
+ .setAuthentication(pendingIntent.intentSender)
+ .build()
)
}
private fun setUpBottomSheetPendingIntent(intent: Intent): PendingIntent {
intent.setAction(java.util.UUID.randomUUID().toString())
return PendingIntent.getActivity(this, /*requestCode=*/0, intent,
- PendingIntent.FLAG_MUTABLE, /*options=*/null)
+ PendingIntent.FLAG_MUTABLE, /*options=*/null)
}
/**
@@ -465,17 +611,33 @@
* }
*/
private fun mapAutofillIdToProviders(
- providerList: List<GetCredentialProviderData>
+ uniqueAutofillIdsForRequest: Set<AutofillId>,
+ providerList: List<GetCredentialProviderData>,
+ primaryProviderComponentName: ComponentName?
): Map<AutofillId, ArrayList<GetCredentialProviderData>> {
val autofillIdToProviders: MutableMap<AutofillId, ArrayList<GetCredentialProviderData>> =
mutableMapOf()
+ var primaryProvider: GetCredentialProviderData? = null
providerList.forEach { provider ->
+ if (primaryProviderComponentName != null && Objects.equals(ComponentName
+ .unflattenFromString(provider
+ .providerFlattenedComponentName), primaryProviderComponentName)) {
+ primaryProvider = provider
+ }
val autofillIdToCredentialEntries:
MutableMap<AutofillId, ArrayList<Entry>> =
mapAutofillIdToCredentialEntries(provider.credentialEntries)
autofillIdToCredentialEntries.forEach { (autofillId, entries) ->
autofillIdToProviders.getOrPut(autofillId) { ArrayList() }
- .add(copyProviderInfo(provider, entries))
+ .add(copyProviderInfo(provider, entries))
+ }
+ }
+ // adds primary provider action entries for autofill IDs without credential entries
+ uniqueAutofillIdsForRequest.forEach { autofillId ->
+ if (!autofillIdToProviders.containsKey(autofillId) && primaryProvider != null) {
+ autofillIdToProviders.put(
+ autofillId,
+ ArrayList(listOf(copyProviderInfoForActionsOnly(primaryProvider!!))))
}
}
return autofillIdToProviders
@@ -526,19 +688,35 @@
)
}
+ private fun copyProviderInfoForActionsOnly(
+ providerInfo: GetCredentialProviderData,
+ ): GetCredentialProviderData {
+ return GetCredentialProviderData(
+ providerInfo.providerFlattenedComponentName,
+ emptyList(),
+ providerInfo.actionChips,
+ emptyList(),
+ null
+ )
+ }
+
override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
TODO("Not yet implemented")
}
private fun getCredManRequest(
- structure: AssistStructure,
- sessionId: Int,
- requestId: Int,
- resultReceiver: ResultReceiver,
- responseClientState: Bundle
+ structure: AssistStructure,
+ sessionId: Int,
+ requestId: Int,
+ resultReceiver: ResultReceiver,
+ responseClientState: Bundle,
+ uniqueAutofillIdsForRequest: MutableSet<AutofillId>
): GetCredentialRequest? {
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
- traverseStructureForRequest(structure, credentialOptions, responseClientState, sessionId)
+ traverseStructureForRequest(
+ structure, credentialOptions, responseClientState,
+ sessionId, uniqueAutofillIdsForRequest
+ )
if (credentialOptions.isNotEmpty()) {
val dataBundle = Bundle()
@@ -558,7 +736,8 @@
structure: AssistStructure,
cmRequests: MutableList<CredentialOption>,
responseClientState: Bundle,
- sessionId: Int
+ sessionId: Int,
+ uniqueAutofillIdsForRequest: MutableSet<AutofillId>
) {
val traversedViewNodes: MutableSet<AutofillId> = mutableSetOf()
val credentialOptionsFromHints: MutableMap<String, CredentialOption> = mutableMapOf()
@@ -570,7 +749,7 @@
windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
traverseNodeForRequest(
windowNode.rootViewNode, cmRequests, responseClientState, traversedViewNodes,
- credentialOptionsFromHints, sessionId)
+ credentialOptionsFromHints, sessionId, uniqueAutofillIdsForRequest)
}
}
@@ -580,7 +759,8 @@
responseClientState: Bundle,
traversedViewNodes: MutableSet<AutofillId>,
credentialOptionsFromHints: MutableMap<String, CredentialOption>,
- sessionId: Int
+ sessionId: Int,
+ uniqueAutofillIdsForRequest: MutableSet<AutofillId>
) {
viewNode.autofillId?.let {
val domain = viewNode.webDomain
@@ -590,7 +770,9 @@
WEBVIEW_REQUESTED_CREDENTIAL_KEY, true)
}
cmRequests.addAll(getCredentialOptionsFromViewNode(viewNode, it, responseClientState,
- traversedViewNodes, credentialOptionsFromHints, sessionId))
+ traversedViewNodes, credentialOptionsFromHints, sessionId,
+ uniqueAutofillIdsForRequest)
+ )
traversedViewNodes.add(it)
}
@@ -600,8 +782,10 @@
}
children.forEach { childNode: AssistStructure.ViewNode ->
- traverseNodeForRequest(childNode, cmRequests, responseClientState, traversedViewNodes,
- credentialOptionsFromHints, sessionId)
+ traverseNodeForRequest(
+ childNode, cmRequests, responseClientState, traversedViewNodes,
+ credentialOptionsFromHints, sessionId, uniqueAutofillIdsForRequest
+ )
}
}
@@ -611,7 +795,8 @@
responseClientState: Bundle,
traversedViewNodes: MutableSet<AutofillId>,
credentialOptionsFromHints: MutableMap<String, CredentialOption>,
- sessionId: Int
+ sessionId: Int,
+ uniqueAutofillIdsForRequest: MutableSet<AutofillId>
): MutableList<CredentialOption> {
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
if (Flags.autofillCredmanDevIntegration() && viewNode.pendingCredentialRequest != null) {
@@ -641,8 +826,9 @@
CredentialProviderService.EXTRA_AUTOFILL_ID,
associatedAutofillIds
)
+ uniqueAutofillIdsForRequest.addAll(associatedAutofillIds)
}
- }
+ }
}
// TODO(b/325502552): clean up cred option logic in autofill hint
val credentialHints: MutableList<String> = mutableListOf()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index 7bb08d2..98e1690 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -21,8 +21,9 @@
import com.android.credentialmanager.common.Constants
import android.widget.RemoteViews
import androidx.core.content.ContextCompat
+import com.android.credentialmanager.model.get.ActionEntryInfo
import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.model.CredentialType
+import com.android.credentialmanager.model.EntryInfo
import android.graphics.drawable.Icon
class RemoteViewsFactory {
@@ -39,37 +40,47 @@
fun createDropdownPresentation(
context: Context,
icon: Icon,
- credentialEntryInfo: CredentialEntryInfo,
+ entryInfo: EntryInfo,
isFirstEntry: Boolean,
isLastEntry: Boolean,
): RemoteViews {
var layoutId: Int = com.android.credentialmanager.R.layout
.credman_dropdown_presentation_layout
val remoteViews = RemoteViews(context.packageName, layoutId)
- val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
- remoteViews.setTextViewText(android.R.id.text1, displayName)
- val secondaryText = getSecondaryText(credentialEntryInfo)
- if (secondaryText.isNullOrBlank()) {
- Log.w(Constants.LOG_TAG, "Secondary text for dropdown is null")
- } else {
- remoteViews.setTextViewText(android.R.id.text2, secondaryText)
+ if (entryInfo is CredentialEntryInfo) {
+ val displayName = entryInfo.displayName ?: entryInfo.userName
+ remoteViews.setTextViewText(android.R.id.text1, displayName)
+ val secondaryText = getSecondaryText(entryInfo)
+ if (secondaryText.isNullOrBlank()) {
+ Log.w(Constants.LOG_TAG, "Secondary text for dropdown credential entry is null")
+ } else {
+ remoteViews.setTextViewText(android.R.id.text2, secondaryText)
+ }
+ remoteViews.setContentDescription(
+ android.R.id.icon1, entryInfo
+ .providerDisplayName
+ )
+ } else if (entryInfo is ActionEntryInfo) {
+ remoteViews.setTextViewText(android.R.id.text1, entryInfo.title)
+ remoteViews.setTextViewText(android.R.id.text2, entryInfo.subTitle)
}
- remoteViews.setImageViewIcon(android.R.id.icon1, icon);
+ remoteViews.setImageViewIcon(android.R.id.icon1, icon)
remoteViews.setBoolean(
- android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true);
+ android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true
+ )
remoteViews.setInt(
android.R.id.icon1,
SET_MAX_HEIGHT_METHOD_NAME,
context.resources.getDimensionPixelSize(
- com.android.credentialmanager.R.dimen.autofill_icon_size));
- remoteViews.setContentDescription(android.R.id.icon1, credentialEntryInfo
- .providerDisplayName);
+ com.android.credentialmanager.R.dimen.autofill_icon_size
+ )
+ )
val drawableId =
if (isFirstEntry)
com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one else
com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_middle
remoteViews.setInt(
- android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId);
+ android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId)
if (isFirstEntry) remoteViews.setViewPadding(
com.android.credentialmanager.R.id.credential_card,
/* left=*/0,
@@ -94,8 +105,8 @@
* providerDisplayName. Both credential type and provider display name should not be empty.
*/
private fun getSecondaryText(credentialEntryInfo: CredentialEntryInfo): String? {
- return listOf(if (credentialEntryInfo.displayName != null
- && (credentialEntryInfo.displayName != credentialEntryInfo.userName))
+ return listOf(if (credentialEntryInfo.displayName != null &&
+ (credentialEntryInfo.displayName != credentialEntryInfo.userName))
(credentialEntryInfo.userName) else null,
credentialEntryInfo.credentialTypeDisplayName,
credentialEntryInfo.providerDisplayName).filterNot { it.isNullOrBlank() }
@@ -113,16 +124,16 @@
com.android.credentialmanager
.R.string.dropdown_presentation_more_sign_in_options_text))
remoteViews.setBoolean(
- android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true);
+ android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true)
remoteViews.setInt(
android.R.id.icon1,
SET_MAX_HEIGHT_METHOD_NAME,
context.resources.getDimensionPixelSize(
- com.android.credentialmanager.R.dimen.autofill_icon_size));
+ com.android.credentialmanager.R.dimen.autofill_icon_size))
val drawableId =
com.android.credentialmanager.R.drawable.more_options_list_item
remoteViews.setInt(
- android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId);
+ android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId)
return remoteViews
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 8e78861..19f5a99 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -292,12 +292,12 @@
providerDisplayInfo.remoteEntry == null &&
providerDisplayInfo.authenticationEntryList.all { it.isUnlockedAndEmpty })
GetScreenState.UNLOCKED_AUTH_ENTRIES_ONLY
+ else if (isRequestForAllOptions)
+ GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY
else if (providerDisplayInfo.sortedUserNameToCredentialEntryList.isEmpty() &&
providerDisplayInfo.authenticationEntryList.isEmpty() &&
providerDisplayInfo.remoteEntry != null)
GetScreenState.REMOTE_ONLY
- else if (isRequestForAllOptions)
- GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY
else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo)))
GetScreenState.BIOMETRIC_SELECTION
else GetScreenState.PRIMARY_SELECTION
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index d969d1c..2e9b7b4 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -446,6 +446,9 @@
// Returns InstallUserActionRequired stage if install details could be successfully
// computed, else it returns InstallAborted.
val confirmationSnippet: InstallStage = generateConfirmationSnippet()
+ if (confirmationSnippet.stageCode == InstallStage.STAGE_ABORTED) {
+ return confirmationSnippet
+ }
val existingUpdateOwner: CharSequence? = getExistingUpdateOwner(newPackageInfo!!)
return if (sessionId == SessionInfo.INVALID_ID &&
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
index 7c76ea1..221e8f5 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
@@ -38,7 +38,7 @@
<color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
<!-- Dialog background color. -->
- <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+ <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainer</color>
<color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
index 2a6499a..dc2d3dc 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
@@ -38,7 +38,7 @@
<color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
<!-- Dialog background color. -->
- <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+ <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainer</color>
<!-- Material next track outline color-->
<color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 3dffb27..8917412 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -10,8 +10,10 @@
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -37,12 +39,9 @@
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.settingslib.widget.AdaptiveOutlineDrawable;
-import com.google.common.collect.ImmutableSet;
-
import java.io.IOException;
import java.util.List;
import java.util.Locale;
-import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -56,8 +55,6 @@
public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
- private static final Set<String> EXCLUSIVE_MANAGERS =
- ImmutableSet.of("com.google.android.gms.dck");
private static ErrorListener sErrorListener;
@@ -740,14 +737,13 @@
/**
* Returns the BluetoothDevice's exclusive manager ({@link
- * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the given
- * set, otherwise null.
+ * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists, otherwise null.
*/
@Nullable
- private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) {
- byte[] exclusiveManagerNameBytes =
+ private static String getExclusiveManager(BluetoothDevice bluetoothDevice) {
+ byte[] exclusiveManagerBytes =
bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER);
- if (exclusiveManagerNameBytes == null) {
+ if (exclusiveManagerBytes == null) {
Log.d(
TAG,
"Bluetooth device "
@@ -755,47 +751,46 @@
+ " doesn't have exclusive manager");
return null;
}
- String exclusiveManagerName = new String(exclusiveManagerNameBytes);
- return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName : null;
+ return new String(exclusiveManagerBytes);
}
- /** Checks if given package is installed */
- private static boolean isPackageInstalled(Context context, String packageName) {
+ /** Checks if given package is installed and enabled */
+ private static boolean isPackageInstalledAndEnabled(Context context, String packageName) {
PackageManager packageManager = context.getPackageManager();
try {
- packageManager.getPackageInfo(packageName, 0);
- return true;
+ ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, 0);
+ return appInfo.enabled;
} catch (PackageManager.NameNotFoundException e) {
- Log.d(TAG, "Package " + packageName + " is not installed");
+ Log.d(TAG, "Package " + packageName + " is not installed/enabled");
}
return false;
}
/**
* A BluetoothDevice is exclusively managed if 1) it has field {@link
- * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app name is
- * in the allowlist. 3) the exclusive manager app is installed.
+ * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app is
+ * installed and enabled.
*/
public static boolean isExclusivelyManagedBluetoothDevice(
@NonNull Context context, @NonNull BluetoothDevice bluetoothDevice) {
- String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice);
+ String exclusiveManagerName = getExclusiveManager(bluetoothDevice);
if (exclusiveManagerName == null) {
return false;
}
- if (!isPackageInstalled(context, exclusiveManagerName)) {
+
+ ComponentName exclusiveManagerComponent =
+ ComponentName.unflattenFromString(exclusiveManagerName);
+ String exclusiveManagerPackage = exclusiveManagerComponent != null
+ ? exclusiveManagerComponent.getPackageName() : exclusiveManagerName;
+
+ if (!isPackageInstalledAndEnabled(context, exclusiveManagerPackage)) {
return false;
} else {
- Log.d(TAG, "Found exclusively managed app " + exclusiveManagerName);
+ Log.d(TAG, "Found exclusively managed app " + exclusiveManagerPackage);
return true;
}
}
- /** Return the allowlist for exclusive manager names. */
- @NonNull
- public static Set<String> getExclusiveManagers() {
- return EXCLUSIVE_MANAGERS;
- }
-
/**
* Get CSIP group id for {@link CachedBluetoothDevice}.
*
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 30bec77..c2a83b1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -68,6 +68,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -1442,14 +1443,8 @@
int stringRes = R.string.bluetooth_pairing;
//when profile is connected, information would be available
if (profileConnected) {
- // Update Meta data for connected device
- if (BluetoothUtils.getBooleanMetaData(
- mDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
- leftBattery = BluetoothUtils.getIntMetaData(mDevice,
- BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY);
- rightBattery = BluetoothUtils.getIntMetaData(mDevice,
- BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY);
- }
+ leftBattery = getLeftBatteryLevel();
+ rightBattery = getRightBatteryLevel();
// Set default string with battery level in device connected situation.
if (isTwsBatteryAvailable(leftBattery, rightBattery)) {
@@ -1485,7 +1480,7 @@
boolean isActiveLeAudioHearingAid = mIsActiveDeviceLeAudio
&& isConnectedHapClientDevice();
if (isActiveAshaHearingAid || isActiveLeAudioHearingAid) {
- return getHearingDeviceSummary(leftBattery, rightBattery, shortSummary);
+ stringRes = getHearingDeviceSummaryRes(leftBattery, rightBattery, shortSummary);
}
}
}
@@ -1498,6 +1493,8 @@
boolean summaryIncludesBatteryLevel = stringRes == R.string.bluetooth_battery_level
|| stringRes == R.string.bluetooth_active_battery_level
|| stringRes == R.string.bluetooth_active_battery_level_untethered
+ || stringRes == R.string.bluetooth_active_battery_level_untethered_left
+ || stringRes == R.string.bluetooth_active_battery_level_untethered_right
|| stringRes == R.string.bluetooth_battery_level_untethered;
if (isTvSummary && summaryIncludesBatteryLevel && Flags.enableTvMediaOutputDialog()) {
return getTvBatterySummary(
@@ -1510,6 +1507,14 @@
if (isTwsBatteryAvailable(leftBattery, rightBattery)) {
return mContext.getString(stringRes, Utils.formatPercentage(leftBattery),
Utils.formatPercentage(rightBattery));
+ } else if (leftBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN
+ && !BluetoothUtils.getBooleanMetaData(mDevice,
+ BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+ return mContext.getString(stringRes, Utils.formatPercentage(leftBattery));
+ } else if (rightBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN
+ && !BluetoothUtils.getBooleanMetaData(mDevice,
+ BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+ return mContext.getString(stringRes, Utils.formatPercentage(rightBattery));
} else {
return mContext.getString(stringRes, batteryLevelPercentageString);
}
@@ -1553,60 +1558,34 @@
return spannableBuilder;
}
- private CharSequence getHearingDeviceSummary(int leftBattery, int rightBattery,
+ private int getHearingDeviceSummaryRes(int leftBattery, int rightBattery,
boolean shortSummary) {
+ boolean isLeftDeviceConnected = getConnectedHearingAidSide(
+ HearingAidInfo.DeviceSide.SIDE_LEFT).isPresent();
+ boolean isRightDeviceConnected = getConnectedHearingAidSide(
+ HearingAidInfo.DeviceSide.SIDE_RIGHT).isPresent();
+ boolean shouldShowLeftBattery =
+ !shortSummary && (leftBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ boolean shouldShowRightBattery =
+ !shortSummary && (rightBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
- CachedBluetoothDevice memberDevice = getMemberDevice().stream().filter(
- CachedBluetoothDevice::isConnected).findFirst().orElse(null);
- if (memberDevice == null && mSubDevice != null && mSubDevice.isConnected()) {
- memberDevice = mSubDevice;
+ if (isLeftDeviceConnected && isRightDeviceConnected) {
+ return (shouldShowLeftBattery && shouldShowRightBattery)
+ ? R.string.bluetooth_active_battery_level_untethered
+ : R.string.bluetooth_hearing_aid_left_and_right_active;
+ }
+ if (isLeftDeviceConnected) {
+ return shouldShowLeftBattery
+ ? R.string.bluetooth_active_battery_level_untethered_left
+ : R.string.bluetooth_hearing_aid_left_active;
+ }
+ if (isRightDeviceConnected) {
+ return shouldShowRightBattery
+ ? R.string.bluetooth_active_battery_level_untethered_right
+ : R.string.bluetooth_hearing_aid_right_active;
}
- CachedBluetoothDevice leftDevice = null;
- CachedBluetoothDevice rightDevice = null;
- final int deviceSide = getDeviceSide();
- if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT) {
- leftDevice = this;
- rightDevice = memberDevice;
- } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_RIGHT) {
- leftDevice = memberDevice;
- rightDevice = this;
- } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) {
- leftDevice = this;
- rightDevice = this;
- }
-
- if (leftBattery < 0 && leftDevice != null) {
- leftBattery = leftDevice.getBatteryLevel();
- }
- if (rightBattery < 0 && rightDevice != null) {
- rightBattery = rightDevice.getBatteryLevel();
- }
-
- if (leftDevice != null && rightDevice != null) {
- if (leftBattery >= 0 && rightBattery >= 0 && !shortSummary) {
- return mContext.getString(R.string.bluetooth_active_battery_level_untethered,
- Utils.formatPercentage(leftBattery), Utils.formatPercentage(rightBattery));
- } else {
- return mContext.getString(R.string.bluetooth_hearing_aid_left_and_right_active);
- }
- } else if (leftDevice != null) {
- if (leftBattery >= 0 && !shortSummary) {
- return mContext.getString(R.string.bluetooth_active_battery_level_untethered_left,
- Utils.formatPercentage(leftBattery));
- } else {
- return mContext.getString(R.string.bluetooth_hearing_aid_left_active);
- }
- } else if (rightDevice != null) {
- if (rightBattery >= 0 && !shortSummary) {
- return mContext.getString(R.string.bluetooth_active_battery_level_untethered_right,
- Utils.formatPercentage(rightBattery));
- } else {
- return mContext.getString(R.string.bluetooth_hearing_aid_right_active);
- }
- }
-
- return mContext.getString(R.string.bluetooth_active_no_battery_level);
+ return R.string.bluetooth_active_no_battery_level;
}
private void addBatterySpan(SpannableStringBuilder builder,
@@ -1632,6 +1611,56 @@
return leftBattery >= 0 && rightBattery >= 0;
}
+ private Optional<CachedBluetoothDevice> getConnectedHearingAidSide(
+ @HearingAidInfo.DeviceSide int side) {
+ return Stream.concat(Stream.of(this, mSubDevice), mMemberDevices.stream())
+ .filter(Objects::nonNull)
+ .filter(device -> device.getDeviceSide() == side
+ || device.getDeviceSide() == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT)
+ .filter(device -> device.getDevice().isConnected())
+ // For hearing aids, we should expect only one device assign to one side, but if
+ // it happens, we don't care which one.
+ .findAny();
+ }
+
+ private int getLeftBatteryLevel() {
+ int leftBattery = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+ if (BluetoothUtils.getBooleanMetaData(mDevice,
+ BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+ leftBattery = BluetoothUtils.getIntMetaData(mDevice,
+ BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY);
+ }
+
+ // Retrieve hearing aids (ASHA, HAP) individual side battery level
+ if (leftBattery == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+ leftBattery = getConnectedHearingAidSide(HearingAidInfo.DeviceSide.SIDE_LEFT)
+ .map(CachedBluetoothDevice::getBatteryLevel)
+ .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+ .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ }
+
+ return leftBattery;
+ }
+
+ private int getRightBatteryLevel() {
+ int rightBattery = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+ if (BluetoothUtils.getBooleanMetaData(
+ mDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+ rightBattery = BluetoothUtils.getIntMetaData(mDevice,
+ BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY);
+ }
+
+ // Retrieve hearing aids (ASHA, HAP) individual side battery level
+ if (rightBattery == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+ rightBattery = getConnectedHearingAidSide(HearingAidInfo.DeviceSide.SIDE_RIGHT)
+ .map(CachedBluetoothDevice::getBatteryLevel)
+ .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+ .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ }
+
+ return rightBattery;
+ }
+
private boolean isProfileConnectedFail() {
Log.d(TAG, "anonymizedAddress=" + mDevice.getAnonymizedAddress()
+ " mIsA2dpProfileConnectedFail=" + mIsA2dpProfileConnectedFail
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index f197f9e..7a2818d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -28,7 +28,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
-import android.content.pm.PackageInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
@@ -80,7 +80,8 @@
private static final String CONTROL_METADATA =
"<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + STRING_METADATA
+ "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
- private static final String FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name";
+ private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager";
+ private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component";
@Before
public void setUp() {
@@ -399,7 +400,7 @@
}
@Test
- public void isExclusivelyManagedBluetoothDevice_isNotExclusivelyManaged_returnFalse() {
+ public void isExclusivelyManaged_hasNoManager_returnFalse() {
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
null);
@@ -408,45 +409,85 @@
}
@Test
- public void isExclusivelyManagedBluetoothDevice_isNotInAllowList_returnFalse() {
+ public void isExclusivelyManaged_hasPackageName_packageNotInstalled_returnFalse()
+ throws Exception {
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- FAKE_EXCLUSIVE_MANAGER_NAME.getBytes());
+ TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager)
+ .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
mBluetoothDevice)).isEqualTo(false);
}
@Test
- public void isExclusivelyManagedBluetoothDevice_packageNotInstalled_returnFalse()
+ public void isExclusivelyManaged_hasComponentName_packageNotInstalled_returnFalse()
throws Exception {
- final String exclusiveManagerName =
- BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
- FAKE_EXCLUSIVE_MANAGER_NAME);
-
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- exclusiveManagerName.getBytes());
+ TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getPackageInfo(
- exclusiveManagerName, 0);
+ doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager)
+ .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(false);
+ mBluetoothDevice)).isEqualTo(false);
}
@Test
- public void isExclusivelyManagedBluetoothDevice_isExclusivelyManaged_returnTrue()
- throws Exception {
- final String exclusiveManagerName =
- BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
- FAKE_EXCLUSIVE_MANAGER_NAME);
-
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- exclusiveManagerName.getBytes());
+ public void isExclusivelyManaged_hasPackageName_packageNotEnabled_returnFalse()
+ throws Exception {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.enabled = false;
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0);
+ doReturn(appInfo).when(mPackageManager).getApplicationInfo(
+ TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+ TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(true);
+ mBluetoothDevice)).isEqualTo(false);
+ }
+
+ @Test
+ public void isExclusivelyManaged_hasComponentName_packageNotEnabled_returnFalse()
+ throws Exception {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.enabled = false;
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ doReturn(appInfo).when(mPackageManager).getApplicationInfo(
+ TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+ TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
+
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+ mBluetoothDevice)).isEqualTo(false);
+ }
+
+ @Test
+ public void isExclusivelyManaged_hasPackageName_packageInstalledAndEnabled_returnTrue()
+ throws Exception {
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+ TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo(
+ TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+ mBluetoothDevice)).isEqualTo(true);
+ }
+
+ @Test
+ public void isExclusivelyManaged_hasComponentName_packageInstalledAndEnabled_returnTrue()
+ throws Exception {
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+ TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo(
+ TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+ mBluetoothDevice)).isEqualTo(true);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index b4bd482..b9bf9ca 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -753,33 +753,71 @@
}
@Test
- public void getConnectionSummary_testHearingAidBatteryWithoutInCall_returnActiveBattery() {
+ public void getConnectionSummary_testHearingAidLeftEarBatteryNotInCall_returnActiveBattery() {
// Arrange:
- // 1. Profile: {HEARING_AID, Connected, Active}
+ // 1. Profile: {HEARING_AID, Connected, Active, Left ear}
// 2. Battery Level: 10
// 3. Audio Manager: Normal (Without In Call)
updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
mBatteryLevel = 10;
// Act & Assert:
- // Get "Active, 10% battery" result with Battery Level 10.
- assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 10% battery");
+ // Get "Active. L: 10% battery." result with Battery Level 10.
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active. L: 10% battery.");
}
@Test
- public void getTvConnectionSummary_testHearingAidBatteryWithoutInCall_returnBattery() {
+ public void getTvConnectionSummary_testHearingAidLeftEarBatteryWithoutInCall_returnBattery() {
// Arrange:
- // 1. Profile: {HEARING_AID, Connected, Active}
+ // 1. Profile: {HEARING_AID, Connected, Active, Left ear}
// 2. Battery Level: 10
// 3. Audio Manager: Normal (Without In Call)
updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
mBatteryLevel = 10;
// Act & Assert:
- // Get "Active, 10% battery" result with Battery Level 10.
- assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo("Battery 10%");
+ // Get "Left: 10% battery" result with Battery Level 10.
+ assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo(
+ "Left: 10% battery");
+ }
+
+ @Test
+ public void getConnectionSummary_testHearingAidLeftEarBatteryInCall_returnActiveBattery() {
+ // Arrange:
+ // 1. Profile: {HEARING_AID, Connected, Active, Left ear}
+ // 2. Battery Level: 10
+ // 3. Audio Manager: In Call
+ updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
+ mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
+ mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ mBatteryLevel = 10;
+
+ // Act & Assert:
+ // Get "Active. L: 10% battery." result with Battery Level 10.
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active. L: 10% battery.");
+ }
+
+ @Test
+ public void getTvConnectionSummary_testHearingAidLeftEarBatteryInCall_returnBattery() {
+ // Arrange:
+ // 1. Profile: {HEARING_AID, Connected, Active, Left ear}
+ // 2. Battery Level: 10
+ // 3. Audio Manager: In Call
+ updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
+ mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
+ mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ mBatteryLevel = 10;
+
+ // Act & Assert:
+ // Get "Left: 10% battery" result with Battery Level 10.
+ assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo(
+ "Left: 10% battery");
}
@Test
@@ -851,35 +889,45 @@
}
@Test
- public void getConnectionSummary_testHearingAidBatteryInCall_returnActiveBattery() {
+ public void getConnectionSummary_testHearingAidBothEarBattery_returnActiveBattery() {
// Arrange:
- // 1. Profile: {HEARING_AID, Connected, Active}
+ // 1. Profile: {HEARING_AID, Connected, Active, Both ear}
// 2. Battery Level: 10
// 3. Audio Manager: In Call
+ mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+ mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
+ updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.setSubDevice(mSubCachedDevice);
mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
mBatteryLevel = 10;
// Act & Assert:
- // Get "Active, 10% battery" result with Battery Level 10.
- assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 10% battery");
+ // Get "Active. L: 10%, R: 10% battery." result with Battery Level 10.
+ assertThat(mCachedDevice.getConnectionSummary().toString())
+ .isEqualTo("Active. L: 10%, R: 10% battery.");
}
@Test
- public void getTvConnectionSummary_testHearingAidBatteryInCall_returnBattery() {
+ public void getTvConnectionSummary_testHearingAidBothEarBattery_returnActiveBattery() {
// Arrange:
- // 1. Profile: {HEARING_AID, Connected, Active}
+ // 1. Profile: {HEARING_AID, Connected, Active, Both ear}
// 2. Battery Level: 10
// 3. Audio Manager: In Call
+ mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+ mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
+ updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.setSubDevice(mSubCachedDevice);
mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
mBatteryLevel = 10;
// Act & Assert:
- // Get "Active, 10% battery" result with Battery Level 10.
- assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo("Battery 10%");
+ // Get "Left: 10% battery Right: 10% battery" result with Battery Level 10.
+ assertThat(mCachedDevice.getTvConnectionSummary().toString())
+ .isEqualTo("Left: 10% battery Right: 10% battery");
}
@Test
@@ -1151,7 +1199,7 @@
updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
- mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
+ mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
"true".getBytes());
when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
@@ -1160,7 +1208,7 @@
TWS_BATTERY_RIGHT.getBytes());
assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
- "Active, L: 15% battery, R: 25% battery");
+ "Active. L: 15%, R: 25% battery.");
}
@Test
@@ -1169,7 +1217,7 @@
updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
- mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
+ mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
"true".getBytes());
when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
@@ -1178,7 +1226,7 @@
TWS_BATTERY_RIGHT.getBytes());
assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo(
- "Left 15% Right 25%");
+ "Left: 15% battery Right: 25% battery");
}
@Test
@@ -1741,16 +1789,6 @@
BluetoothProfile.STATE_CONNECTED);
}
- private void updateProfileStatus(LocalBluetoothProfile profile, int status) {
- doReturn(status).when(profile).getConnectionStatus(mDevice);
- mCachedDevice.onProfileStateChanged(profile, status);
- }
-
- private void updateSubDeviceProfileStatus(LocalBluetoothProfile profile, int status) {
- doReturn(status).when(profile).getConnectionStatus(mSubDevice);
- mSubCachedDevice.onProfileStateChanged(profile, status);
- }
-
@Test
public void getSubDevice_setSubDevice() {
mCachedDevice.setSubDevice(mSubCachedDevice);
@@ -2030,6 +2068,29 @@
assertThat(mCachedDevice.getConnectionSummary(false)).isNull();
}
+ private void updateProfileStatus(LocalBluetoothProfile profile, int status) {
+ doReturn(status).when(profile).getConnectionStatus(mDevice);
+ mCachedDevice.onProfileStateChanged(profile, status);
+ updateConnectionStatus(mCachedDevice);
+ }
+
+ private void updateSubDeviceProfileStatus(LocalBluetoothProfile profile, int status) {
+ doReturn(status).when(profile).getConnectionStatus(mSubDevice);
+ mSubCachedDevice.onProfileStateChanged(profile, status);
+ updateConnectionStatus(mSubCachedDevice);
+ }
+
+ private void updateConnectionStatus(CachedBluetoothDevice cachedBluetoothDevice) {
+ for (LocalBluetoothProfile profile : cachedBluetoothDevice.getProfiles()) {
+ int status = cachedBluetoothDevice.getProfileConnectionState(profile);
+ if (status == BluetoothProfile.STATE_CONNECTED) {
+ when(cachedBluetoothDevice.getDevice().isConnected()).thenReturn(true);
+ return;
+ }
+ }
+ when(cachedBluetoothDevice.getDevice().isConnected()).thenReturn(false);
+ }
+
private HearingAidInfo getLeftAshaHearingAidInfo() {
return new HearingAidInfo.Builder()
.setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT)
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 461b6b3..70ce202 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2949,9 +2949,6 @@
dumpSetting(s, p,
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
SystemSettingsProto.Screen.AUTO_BRIGHTNESS_ADJ);
- dumpSetting(s, p,
- Settings.System.SCREEN_BRIGHTNESS_FLOAT,
- SystemSettingsProto.Screen.BRIGHTNESS_FLOAT);
p.end(screenToken);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index c891dfc..92167ee 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -935,7 +935,6 @@
Settings.System.VOLUME_VOICE, // deprecated since API 2?
Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
- Settings.System.SCREEN_BRIGHTNESS_FLOAT,
Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE,
Settings.System.WEAR_TTS_PREWARM_ENABLED,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index fce7a00f..c04ec4f 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -569,7 +569,7 @@
"androidx.test.uiautomator_uiautomator",
"androidx.core_core-animation-testing",
"mockito-target-extended-minus-junit4",
- "mockito-kotlin2",
+ "mockito-kotlin-nodeps",
"androidx.test.ext.junit",
"androidx.test.ext.truth",
"kotlin-test",
@@ -656,6 +656,7 @@
"androidx.core_core-animation-testing",
"androidx.test.ext.junit",
"inline-mockito-robolectric-prebuilt",
+ "mockito-kotlin-nodeps",
"platform-parametric-runner-lib",
"SystemUICustomizationTestUtils",
"kotlin-test",
@@ -740,6 +741,7 @@
"androidx.core_core-animation-testing",
"androidx.test.ext.junit",
"kosmos",
+ "mockito-kotlin-nodeps",
],
libs: [
"android.test.runner",
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 14ebc39..755fe2a 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -4,6 +4,16 @@
# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
flag {
+ name: "delay_show_magnification_button"
+ namespace: "accessibility"
+ description: "Delays the showing of magnification mode switch button."
+ bug: "338259519"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "floating_menu_animated_tuck"
namespace: "accessibility"
description: "Sets up animations for tucking/untucking and adjusts clipbounds."
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 21881f6..626e219 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -550,6 +550,13 @@
}
flag {
+ name: "enable_contextual_tip_for_mute_volume"
+ namespace: "systemui"
+ description: "Enables the contextual tip for muting the volume."
+ bug: "337737048"
+}
+
+flag {
name: "disable_contextual_tips_frequency_check"
description: "Disables frequency capping check for contextual tips."
namespace: "systemui"
@@ -850,6 +857,16 @@
}
flag {
+ name: "restart_dream_on_unocclude"
+ namespace: "systemui"
+ description: "re-enters dreaming upon unocclude when dreaming when originally occluding"
+ bug: "338051457"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "communal_bouncer_do_not_modify_plugin_open"
namespace: "systemui"
description: "do not modify notification shade when handling bouncer expansion."
@@ -858,3 +875,20 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "app_clips_backlinks"
+ namespace: "systemui"
+ description: "Enables Backlinks improvement feature in App Clips"
+ bug: "300307759"
+}
+
+flag {
+ name: "qs_custom_tile_click_guaranteed_bug_fix"
+ namespace: "systemui"
+ description: "Guarantee that clicks on a tile always happen by postponing onStopListening until after the click."
+ bug: "339290820"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 1e60b98..d4660fa 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -44,6 +44,7 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.Flags.activityTransitionUseLargestWindow
+import java.util.concurrent.Executor
import kotlin.math.roundToInt
private const val TAG = "ActivityTransitionAnimator"
@@ -52,14 +53,19 @@
* A class that allows activities to be started in a seamless way from a view that is transforming
* nicely into the starting window.
*/
-class ActivityTransitionAnimator(
+class ActivityTransitionAnimator
+@JvmOverloads
+constructor(
+ /** The executor that runs on the main thread. */
+ private val mainExecutor: Executor,
+
/** The animator used when animating a View into an app. */
- private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
+ private val transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),
/** The animator used when animating a Dialog into an app. */
// TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to
// TIMINGS.contentBeforeFadeOutDuration.
- private val dialogToAppAnimator: TransitionAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR,
+ private val dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor),
/**
* Whether we should disable the WindowManager timeout. This should be set to true in tests
@@ -100,10 +106,6 @@
// TODO(b/288507023): Remove this flag.
@JvmField val DEBUG_TRANSITION_ANIMATION = Build.IS_DEBUGGABLE
- private val DEFAULT_TRANSITION_ANIMATOR = TransitionAnimator(TIMINGS, INTERPOLATORS)
- private val DEFAULT_DIALOG_TO_APP_ANIMATOR =
- TransitionAnimator(DIALOG_TIMINGS, INTERPOLATORS)
-
/** Durations & interpolators for the navigation bar fading in & out. */
private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
@@ -121,6 +123,14 @@
* cancelled by WM.
*/
private const val LONG_TRANSITION_TIMEOUT = 5_000L
+
+ private fun defaultTransitionAnimator(mainExecutor: Executor): TransitionAnimator {
+ return TransitionAnimator(mainExecutor, TIMINGS, INTERPOLATORS)
+ }
+
+ private fun defaultDialogToAppAnimator(mainExecutor: Executor): TransitionAnimator {
+ return TransitionAnimator(mainExecutor, DIALOG_TIMINGS, INTERPOLATORS)
+ }
}
/**
@@ -257,9 +267,7 @@
private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
if (Looper.myLooper() != Looper.getMainLooper()) {
- this.transitionContainer.context.mainExecutor.execute {
- callOnIntentStartedOnMainThread(willAnimate)
- }
+ mainExecutor.execute { callOnIntentStartedOnMainThread(willAnimate) }
} else {
if (DEBUG_TRANSITION_ANIMATION) {
Log.d(
@@ -479,12 +487,10 @@
controller: Controller,
callback: Callback,
/** The animator to use to animate the window transition. */
- transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
+ transitionAnimator: TransitionAnimator,
/** Listener for animation lifecycle events. */
listener: Listener? = null
) : IRemoteAnimationRunner.Stub() {
- private val context = controller.transitionContainer.context
-
// This is being passed across IPC boundaries and cycles (through PendingIntentRecords,
// etc.) are possible. So we need to make sure we drop any references that might
// transitively cause leaks when we're done with animation.
@@ -493,11 +499,12 @@
init {
delegate =
AnimationDelegate(
+ mainExecutor,
controller,
callback,
DelegatingAnimationCompletionListener(listener, this::dispose),
transitionAnimator,
- disableWmTimeout
+ disableWmTimeout,
)
}
@@ -510,7 +517,7 @@
finishedCallback: IRemoteAnimationFinishedCallback?
) {
val delegate = delegate
- context.mainExecutor.execute {
+ mainExecutor.execute {
if (delegate == null) {
Log.i(TAG, "onAnimationStart called after completion")
// Animation started too late and timed out already. We need to still
@@ -525,7 +532,7 @@
@BinderThread
override fun onAnimationCancelled() {
val delegate = delegate
- context.mainExecutor.execute {
+ mainExecutor.execute {
delegate ?: Log.wtf(TAG, "onAnimationCancelled called after completion")
delegate?.onAnimationCancelled()
}
@@ -535,19 +542,21 @@
fun dispose() {
// Drop references to animation controller once we're done with the animation
// to avoid leaking.
- context.mainExecutor.execute { delegate = null }
+ mainExecutor.execute { delegate = null }
}
}
class AnimationDelegate
@JvmOverloads
constructor(
+ private val mainExecutor: Executor,
private val controller: Controller,
private val callback: Callback,
/** Listener for animation lifecycle events. */
private val listener: Listener? = null,
/** The animator to use to animate the window transition. */
- private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
+ private val transitionAnimator: TransitionAnimator =
+ defaultTransitionAnimator(mainExecutor),
/**
* Whether we should disable the WindowManager timeout. This should be set to true in tests
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
index b89ebfc..f5d01d7 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
@@ -37,6 +37,7 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.util.maybeForceFullscreen
import com.android.systemui.util.registerAnimationOnBackInvoked
+import java.util.concurrent.Executor
import kotlin.math.roundToInt
private const val TAG = "DialogTransitionAnimator"
@@ -55,10 +56,16 @@
class DialogTransitionAnimator
@JvmOverloads
constructor(
+ private val mainExecutor: Executor,
private val callback: Callback,
private val interactionJankMonitor: InteractionJankMonitor,
private val featureFlags: AnimationFeatureFlags,
- private val transitionAnimator: TransitionAnimator = TransitionAnimator(TIMINGS, INTERPOLATORS),
+ private val transitionAnimator: TransitionAnimator =
+ TransitionAnimator(
+ mainExecutor,
+ TIMINGS,
+ INTERPOLATORS,
+ ),
private val isForTesting: Boolean = false,
) {
private companion object {
@@ -937,24 +944,9 @@
}
override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
- // onLaunchAnimationEnd is called by an Animator at the end of the animation,
- // on a Choreographer animation tick. The following calls will move the animated
- // content from the dialog overlay back to its original position, and this
- // change must be reflected in the next frame given that we then sync the next
- // frame of both the content and dialog ViewRoots. However, in case that content
- // is rendered by Compose, whose compositions are also scheduled on a
- // Choreographer frame, any state change made *right now* won't be reflected in
- // the next frame given that a Choreographer frame can't schedule another and
- // have it happen in the same frame. So we post the forwarded calls to
- // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring
- // that the move of the content back to its original window will be reflected in
- // the next frame right after [onLaunchAnimationEnd] is called.
- dialog.context.mainExecutor.execute {
- startController.onTransitionAnimationEnd(isExpandingFullyAbove)
- endController.onTransitionAnimationEnd(isExpandingFullyAbove)
-
- onLaunchAnimationEnd()
- }
+ startController.onTransitionAnimationEnd(isExpandingFullyAbove)
+ endController.onTransitionAnimationEnd(isExpandingFullyAbove)
+ onLaunchAnimationEnd()
}
override fun onTransitionAnimationProgress(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index 679c969..cc55df1 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -31,12 +31,17 @@
import androidx.annotation.VisibleForTesting
import com.android.app.animation.Interpolators.LINEAR
import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
+import java.util.concurrent.Executor
import kotlin.math.roundToInt
private const val TAG = "TransitionAnimator"
/** A base class to animate a window (activity or dialog) launch to or return from a view . */
-class TransitionAnimator(private val timings: Timings, private val interpolators: Interpolators) {
+class TransitionAnimator(
+ private val mainExecutor: Executor,
+ private val timings: Timings,
+ private val interpolators: Interpolators,
+) {
companion object {
internal const val DEBUG = false
private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
@@ -351,11 +356,27 @@
if (DEBUG) {
Log.d(TAG, "Animation ended")
}
- controller.onTransitionAnimationEnd(isExpandingFullyAbove)
- transitionContainerOverlay.remove(windowBackgroundLayer)
- if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
- openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+ // onAnimationEnd is called at the end of the animation, on a Choreographer
+ // animation tick. During dialog launches, the following calls will move the
+ // animated content from the dialog overlay back to its original position, and
+ // this change must be reflected in the next frame given that we then sync the
+ // next frame of both the content and dialog ViewRoots. During SysUI activity
+ // launches, we will instantly collapse the shade at the end of the transition.
+ // However, if those are rendered by Compose, whose compositions are also
+ // scheduled on a Choreographer frame, any state change made *right now* won't
+ // be reflected in the next frame given that a Choreographer frame can't
+ // schedule another and have it happen in the same frame. So we post the
+ // forwarded calls to [Controller.onLaunchAnimationEnd] in the main executor,
+ // leaving this Choreographer frame, ensuring that any state change applied by
+ // onTransitionAnimationEnd() will be reflected in the same frame.
+ mainExecutor.execute {
+ controller.onTransitionAnimationEnd(isExpandingFullyAbove)
+ transitionContainerOverlay.remove(windowBackgroundLayer)
+
+ if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
+ openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutout.kt
index 3eb1b14..604b517 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutout.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutout.kt
@@ -35,6 +35,7 @@
val viewDisplayCutoutKeyguardStatusBarView: ViewDisplayCutout? = null,
) {
fun width() = abs(right.value - left.value).dp
+ fun height() = abs(bottom.value - top.value).dp
}
enum class CutoutLocation {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 79b57ca7..6fe5cef 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -59,7 +59,8 @@
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.TouchApp
import androidx.compose.material.icons.outlined.Widgets
@@ -277,7 +278,6 @@
if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
Toolbar(
- isDraggingToRemove = isDraggingToRemove,
setToolbarSize = { toolbarSize = it },
setRemoveButtonCoordinates = { removeButtonCoordinates = it },
onEditDone = onEditDone,
@@ -577,7 +577,6 @@
*/
@Composable
private fun Toolbar(
- isDraggingToRemove: Boolean,
removeEnabled: Boolean,
onRemoveClicked: () -> Unit,
setToolbarSize: (toolbarSize: IntSize) -> Unit,
@@ -591,7 +590,7 @@
label = "RemoveButtonAlphaAnimation"
)
- Row(
+ Box(
modifier =
Modifier.fillMaxWidth()
.padding(
@@ -600,65 +599,54 @@
end = Dimensions.ToolbarPaddingHorizontal,
)
.onSizeChanged { setToolbarSize(it) },
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically
) {
val spacerModifier = Modifier.width(ButtonDefaults.IconSpacing)
- Button(
- onClick = onOpenWidgetPicker,
- colors = filledButtonColors(),
- contentPadding = Dimensions.ButtonPadding
- ) {
- Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text))
- Spacer(spacerModifier)
- Text(
- text = stringResource(R.string.hub_mode_add_widget_button_text),
- )
+
+ if (!removeEnabled) {
+ Button(
+ modifier = Modifier.align(Alignment.CenterStart),
+ onClick = onOpenWidgetPicker,
+ colors = filledButtonColors(),
+ contentPadding = Dimensions.ButtonPadding
+ ) {
+ Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text))
+ Spacer(spacerModifier)
+ Text(
+ text = stringResource(R.string.hub_mode_add_widget_button_text),
+ )
+ }
}
- val colors = LocalAndroidColorScheme.current
- if (isDraggingToRemove) {
+ if (removeEnabled) {
Button(
- // Button is disabled to make it non-clickable
- enabled = false,
- onClick = {},
- colors =
- ButtonDefaults.buttonColors(
- disabledContainerColor = colors.primary,
- disabledContentColor = colors.onPrimary,
- ),
- contentPadding = Dimensions.ButtonPadding,
- modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) }
- ) {
- RemoveButtonContent(spacerModifier)
- }
- } else {
- OutlinedButton(
- enabled = removeEnabled,
onClick = onRemoveClicked,
- colors =
- ButtonDefaults.outlinedButtonColors(
- contentColor = colors.primary,
- disabledContentColor = colors.primary
- ),
- border = BorderStroke(width = 1.0.dp, color = colors.primary),
+ colors = filledButtonColors(),
contentPadding = Dimensions.ButtonPadding,
modifier =
Modifier.graphicsLayer { alpha = removeButtonAlpha }
.onGloballyPositioned { setRemoveButtonCoordinates(it) }
+ .align(Alignment.Center)
) {
RemoveButtonContent(spacerModifier)
}
}
- Button(
- onClick = onEditDone,
- colors = filledButtonColors(),
- contentPadding = Dimensions.ButtonPadding
- ) {
- Text(
- text = stringResource(R.string.hub_mode_editing_exit_button_text),
- )
+ if (!removeEnabled) {
+ Button(
+ modifier = Modifier.align(Alignment.CenterEnd),
+ onClick = onEditDone,
+ colors = filledButtonColors(),
+ contentPadding = Dimensions.ButtonPadding
+ ) {
+ Icon(
+ Icons.Default.Check,
+ stringResource(id = R.string.hub_mode_editing_exit_button_text)
+ )
+ Spacer(spacerModifier)
+ Text(
+ text = stringResource(R.string.hub_mode_editing_exit_button_text),
+ )
+ }
}
}
}
@@ -762,7 +750,7 @@
@Composable
private fun RemoveButtonContent(spacerModifier: Modifier) {
- Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_remove_widget))
+ Icon(Icons.Default.Close, stringResource(R.string.button_to_remove_widget))
Spacer(spacerModifier)
Text(
text = stringResource(R.string.button_to_remove_widget),
@@ -804,6 +792,8 @@
is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier)
is CommunalContentModel.WidgetContent.DisabledWidget ->
DisabledWidgetPlaceholder(model, viewModel, modifier)
+ is CommunalContentModel.WidgetContent.PendingWidget ->
+ PendingWidgetPlaceholder(model, modifier)
is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier)
is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
is CommunalContentModel.Tutorial -> TutorialContent(modifier)
@@ -929,36 +919,36 @@
Modifier.semantics {
contentDescription = accessibilityLabel
onClick(label = clickActionLabel, action = null)
- val deleteAction =
- CustomAccessibilityAction(removeWidgetActionLabel) {
- contentListState.onRemove(index)
- contentListState.onSaveList()
- true
- }
- val selectWidgetAction =
- CustomAccessibilityAction(clickActionLabel) {
- val currentWidgetKey =
- index?.let {
- keyAtIndexIfEditable(contentListState.list, index)
- }
- viewModel.setSelectedKey(currentWidgetKey)
- true
- }
-
- val actions = mutableListOf(deleteAction, selectWidgetAction)
-
- if (selectedIndex != null && selectedIndex != index) {
- actions.add(
- CustomAccessibilityAction(placeWidgetActionLabel) {
- contentListState.onMove(selectedIndex!!, index)
- contentListState.onSaveList()
- viewModel.setSelectedKey(null)
- true
+ val deleteAction =
+ CustomAccessibilityAction(removeWidgetActionLabel) {
+ contentListState.onRemove(index)
+ contentListState.onSaveList()
+ true
+ }
+ val selectWidgetAction =
+ CustomAccessibilityAction(clickActionLabel) {
+ val currentWidgetKey =
+ index?.let {
+ keyAtIndexIfEditable(contentListState.list, index)
}
- )
+ viewModel.setSelectedKey(currentWidgetKey)
+ true
}
- customActions = actions
+ val actions = mutableListOf(deleteAction, selectWidgetAction)
+
+ if (selectedIndex != null && selectedIndex != index) {
+ actions.add(
+ CustomAccessibilityAction(placeWidgetActionLabel) {
+ contentListState.onMove(selectedIndex!!, index)
+ contentListState.onSaveList()
+ viewModel.setSelectedKey(null)
+ true
+ }
+ )
+ }
+
+ customActions = actions
}
}
) {
@@ -1074,13 +1064,43 @@
Image(
painter = rememberDrawablePainter(icon.loadDrawable(context)),
contentDescription = stringResource(R.string.icon_description_for_disabled_widget),
- modifier = Modifier.size(48.dp),
+ modifier = Modifier.size(Dimensions.IconSize),
colorFilter = ColorFilter.colorMatrix(Colors.DisabledColorFilter),
)
}
}
@Composable
+fun PendingWidgetPlaceholder(
+ model: CommunalContentModel.WidgetContent.PendingWidget,
+ modifier: Modifier = Modifier,
+) {
+ val context = LocalContext.current
+ val icon: Icon =
+ if (model.icon != null) {
+ Icon.createWithBitmap(model.icon)
+ } else {
+ Icon.createWithResource(context, android.R.drawable.sym_def_app_icon)
+ }
+
+ Column(
+ modifier =
+ modifier.background(
+ MaterialTheme.colorScheme.surfaceVariant,
+ RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
+ ),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Image(
+ painter = rememberDrawablePainter(icon.loadDrawable(context)),
+ contentDescription = stringResource(R.string.icon_description_for_pending_widget),
+ modifier = Modifier.size(Dimensions.IconSize),
+ )
+ }
+}
+
+@Composable
private fun SmartspaceContent(
model: CommunalContentModel.Smartspace,
modifier: Modifier = Modifier,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackContentHeight.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackContentHeight.kt
new file mode 100644
index 0000000..9f829cc
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackContentHeight.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notifications.ui.composable
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.invalidateMeasurement
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntOffset
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
+
+/**
+ * Modify element, which updates the height to be the same as the Notification stack height returned
+ * by the legacy Notification stack scroll view in [NotificationScrollView.intrinsicStackHeight].
+ *
+ * @param view Notification stack scroll view
+ * @param padding extra padding in pixels to be added to the received content height.
+ */
+fun Modifier.notificationStackHeight(view: NotificationScrollView, padding: Int = 0) =
+ this then StackLayoutElement(view, padding)
+
+private data class StackLayoutElement(
+ val view: NotificationScrollView,
+ val padding: Int,
+) : ModifierNodeElement<StackLayoutNode>() {
+
+ override fun create(): StackLayoutNode = StackLayoutNode(view, padding)
+
+ override fun update(node: StackLayoutNode) {
+ check(view == node.view) { "Trying to reuse the node with a new View." }
+ if (node.padding != padding) {
+ node.padding = padding
+ node.invalidateMeasureIfAttached()
+ }
+ }
+}
+
+private class StackLayoutNode(val view: NotificationScrollView, var padding: Int) :
+ LayoutModifierNode, Modifier.Node() {
+
+ private val stackHeightChangedListener = Runnable { invalidateMeasureIfAttached() }
+
+ override fun onAttach() {
+ super.onAttach()
+ view.addStackHeightChangedListener(stackHeightChangedListener)
+ }
+
+ override fun onDetach() {
+ super.onDetach()
+ view.removeStackHeightChangedListener(stackHeightChangedListener)
+ }
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ val contentHeight = padding + view.intrinsicStackHeight
+ val placeable =
+ measurable.measure(
+ constraints.copy(minHeight = contentHeight, maxHeight = contentHeight)
+ )
+
+ return layout(placeable.width, placeable.height) { placeable.place(IntOffset.Zero) }
+ }
+
+ override fun toString(): String {
+ return "StackLayoutNode(view=$view padding:$padding)"
+ }
+
+ fun invalidateMeasureIfAttached() {
+ if (isAttached) {
+ this.invalidateMeasurement()
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 3ce0feb..7c8cc194 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -40,6 +40,8 @@
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
@@ -75,6 +77,7 @@
import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
@@ -158,6 +161,7 @@
@Composable
fun SceneScope.NotificationScrollingStack(
shadeSession: SaveableSession,
+ stackScrollView: NotificationScrollView,
viewModel: NotificationsPlaceholderViewModel,
maxScrimTop: () -> Float,
shouldPunchHoleBehindScrim: Boolean,
@@ -179,7 +183,12 @@
with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
val screenHeight = LocalRawScreenHeight.current
- val stackHeight = viewModel.stackHeight.collectAsState()
+ /**
+ * The height in px of the contents of notification stack. Depending on the number of
+ * notifications, this can exceed the space available on screen to show notifications, at which
+ * point the notification stack should become scrollable.
+ */
+ val stackHeight = remember { mutableIntStateOf(0) }
val scrimRounding = viewModel.shadeScrimRounding.collectAsState(ShadeScrimRounding())
@@ -212,7 +221,7 @@
// if contentHeight drops below minimum visible scrim height while scrim is
// expanded, reset scrim offset.
LaunchedEffect(stackHeight, scrimOffset) {
- snapshotFlow { stackHeight.value < minVisibleScrimHeight() && scrimOffset.value < 0f }
+ snapshotFlow { stackHeight.intValue < minVisibleScrimHeight() && scrimOffset.value < 0f }
.collect { shouldCollapse -> if (shouldCollapse) scrimOffset.snapTo(0f) }
}
@@ -324,7 +333,7 @@
},
minScrimOffset = minScrimOffset,
maxScrimOffset = 0f,
- contentHeight = { stackHeight.value },
+ contentHeight = { stackHeight.intValue.toFloat() },
minVisibleScrimHeight = minVisibleScrimHeight,
isCurrentGestureOverscroll = {
isCurrentGestureOverscroll.value
@@ -334,7 +343,11 @@
)
.verticalScroll(scrollState)
.fillMaxWidth()
- .height { (stackHeight.value + navBarHeight).roundToInt() },
+ .notificationStackHeight(
+ view = stackScrollView,
+ padding = navBarHeight.toInt()
+ )
+ .onSizeChanged { size -> stackHeight.intValue = size.height },
)
}
HeadsUpNotificationSpace(viewModel = viewModel)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 5f84dd4..2f241ce 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -181,6 +181,7 @@
val horizontalPadding = dimensionResource(R.dimen.qs_content_horizontal_padding)
Row(
modifier
+ .sysuiResTag("qs_footer_actions")
.fillMaxWidth()
.graphicsLayer { this.alpha = alpha }
.then(backgroundModifier)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index abce742..61ce83a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -36,6 +36,7 @@
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.displayCutoutPadding
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -68,6 +69,8 @@
import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
+import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
@@ -87,10 +90,12 @@
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.shade.ui.composable.ShadeHeader
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import dagger.Lazy
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
@@ -105,6 +110,7 @@
constructor(
@Application private val applicationScope: CoroutineScope,
private val shadeSession: SaveableSession,
+ private val notificationStackScrollView: Lazy<NotificationScrollView>,
private val viewModel: QuickSettingsSceneViewModel,
private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
private val tintedIconManagerFactory: TintedIconManager.Factory,
@@ -127,6 +133,7 @@
modifier: Modifier,
) {
QuickSettingsScene(
+ notificationStackScrollView = notificationStackScrollView.get(),
viewModel = viewModel,
notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
createTintedIconManager = tintedIconManagerFactory::create,
@@ -142,6 +149,7 @@
@Composable
private fun SceneScope.QuickSettingsScene(
+ notificationStackScrollView: NotificationScrollView,
viewModel: QuickSettingsSceneViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
@@ -152,6 +160,8 @@
modifier: Modifier = Modifier,
shadeSession: SaveableSession,
) {
+ val cutoutLocation = LocalDisplayCutout.current.location
+
val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState()
val contentAlpha by
animateFloatAsState(
@@ -183,6 +193,9 @@
// scene (and not the one under it) during a scene transition.
Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
}
+ .thenIf(cutoutLocation != CutoutLocation.CENTER) {
+ Modifier.displayCutoutPadding()
+ },
) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState()
@@ -280,7 +293,8 @@
}
Column(
- modifier = shadeHeaderAndQuickSettingsModifier,
+ modifier =
+ shadeHeaderAndQuickSettingsModifier.sysuiResTag("expanded_qs_scroll_view"),
) {
when (LocalWindowSizeClass.current.widthSizeClass) {
WindowWidthSizeClass.Compact ->
@@ -320,7 +334,6 @@
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
- modifier = Modifier.padding(horizontal = 16.dp),
)
}
Spacer(modifier = Modifier.height(16.dp))
@@ -329,7 +342,7 @@
viewModel.qsSceneAdapter,
{ viewModel.qsSceneAdapter.qsHeight },
isSplitShade = false,
- modifier = Modifier.sysuiResTag("expanded_qs_scroll_view")
+ modifier = Modifier.sysuiResTag("quick_settings_panel")
)
val isMediaVisible by viewModel.isMediaVisible.collectAsState()
@@ -352,6 +365,7 @@
)
}
NotificationScrollingStack(
+ stackScrollView = notificationStackScrollView,
viewModel = notificationsPlaceholderViewModel,
shadeSession = shadeSession,
maxScrimTop = { screenHeight },
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index 7eaebc2..ff9c5a5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -5,10 +5,13 @@
import com.android.systemui.bouncer.ui.composable.Bouncer
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.CollapseShadeInstantly
+import com.android.systemui.scene.shared.model.TransitionKeys.GoneToSplitShade
import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition
import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.goneToSplitShadeTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToBouncerTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToCommunalTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTransition
@@ -38,6 +41,13 @@
from(
Scenes.Gone,
to = Scenes.Shade,
+ key = GoneToSplitShade,
+ ) {
+ goneToSplitShadeTransition()
+ }
+ from(
+ Scenes.Gone,
+ to = Scenes.Shade,
key = SlightlyFasterShadeCollapse,
) {
goneToShadeTransition(durationScale = 0.9)
@@ -68,5 +78,9 @@
Notifications.Elements.NotificationScrim,
y = { Shade.Dimensions.ScrimOverscrollLimit }
)
+ translate(
+ Shade.Elements.SplitShadeStartColumn,
+ y = { Shade.Dimensions.ScrimOverscrollLimit }
+ )
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
new file mode 100644
index 0000000..4dc36d6
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.compose.animation.scene.UserActionDistanceScope
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.shade.ui.composable.Shade
+import com.android.systemui.shade.ui.composable.ShadeHeader
+import kotlin.time.Duration.Companion.milliseconds
+
+fun TransitionBuilder.goneToSplitShadeTransition(
+ durationScale: Double = 1.0,
+) {
+ spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+ swipeSpec =
+ spring(
+ stiffness = Spring.StiffnessMediumLow,
+ visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
+ )
+ distance =
+ object : UserActionDistance {
+ override fun UserActionDistanceScope.absoluteDistance(
+ fromSceneSize: IntSize,
+ orientation: Orientation,
+ ): Float {
+ return fromSceneSize.height.toFloat() * 2 / 3f
+ }
+ }
+
+ fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) }
+
+ fractionRange(start = .33f) {
+ fade(ShadeHeader.Elements.Clock)
+ fade(ShadeHeader.Elements.CollapsedContentStart)
+ fade(ShadeHeader.Elements.CollapsedContentEnd)
+ fade(ShadeHeader.Elements.PrivacyChip)
+ fade(QuickSettings.Elements.SplitShadeQuickSettings)
+ fade(QuickSettings.Elements.FooterActions)
+ fade(Notifications.Elements.NotificationScrim)
+ }
+}
+
+private val DefaultDuration = 500.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 0bd38a1..709a416 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -50,6 +50,7 @@
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.max
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexScenePicker
@@ -63,6 +64,7 @@
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
+import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.res.R
@@ -77,6 +79,7 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel
import com.android.systemui.statusbar.policy.Clock
+import kotlin.math.max
object ShadeHeader {
object Elements {
@@ -121,7 +124,11 @@
}
val cutoutWidth = LocalDisplayCutout.current.width()
+ val cutoutHeight = LocalDisplayCutout.current.height()
+ val cutoutTop = LocalDisplayCutout.current.top
val cutoutLocation = LocalDisplayCutout.current.location
+ val horizontalPadding =
+ max(LocalScreenCornerRadius.current / 2f, Shade.Dimensions.HorizontalPadding)
val useExpandedFormat by
remember(cutoutLocation) {
@@ -140,7 +147,7 @@
contents =
listOf(
{
- Row {
+ Row(modifier = Modifier.padding(horizontal = horizontalPadding)) {
Clock(
scale = 1f,
viewModel = viewModel,
@@ -157,7 +164,12 @@
},
{
if (isPrivacyChipVisible) {
- Box(modifier = Modifier.height(CollapsedHeight).fillMaxWidth()) {
+ Box(
+ modifier =
+ Modifier.height(CollapsedHeight)
+ .fillMaxWidth()
+ .padding(horizontal = horizontalPadding)
+ ) {
PrivacyChip(
viewModel = viewModel,
modifier = Modifier.align(Alignment.CenterEnd),
@@ -166,9 +178,13 @@
} else {
Row(
horizontalArrangement = Arrangement.End,
- modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentEnd)
+ modifier =
+ Modifier.element(ShadeHeader.Elements.CollapsedContentEnd)
+ .padding(horizontal = horizontalPadding)
) {
- SystemIconContainer {
+ SystemIconContainer(
+ modifier = Modifier.align(Alignment.CenterVertically)
+ ) {
when (LocalWindowSizeClass.current.widthSizeClass) {
WindowWidthSizeClass.Medium,
WindowWidthSizeClass.Expanded ->
@@ -206,7 +222,7 @@
val screenWidth = constraints.maxWidth
val cutoutWidthPx = cutoutWidth.roundToPx()
- val height = CollapsedHeight.roundToPx()
+ val height = max(cutoutHeight + (cutoutTop * 2), CollapsedHeight).roundToPx()
val childConstraints = Constraints.fixed((screenWidth - cutoutWidthPx) / 2, height)
val startMeasurable = measurables[0][0]
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index bbf8413..869f741 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -31,6 +31,7 @@
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.displayCutoutPadding
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -67,6 +68,9 @@
import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
+import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
+import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
@@ -84,9 +88,11 @@
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import dagger.Lazy
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
@@ -97,6 +103,7 @@
val MediaCarousel = ElementKey("ShadeMediaCarousel")
val BackgroundScrim =
ElementKey("ShadeBackgroundScrim", scenePicker = LowestZIndexScenePicker)
+ val SplitShadeStartColumn = ElementKey("SplitShadeStartColumn")
}
object Dimensions {
@@ -121,6 +128,7 @@
@Inject
constructor(
private val shadeSession: SaveableSession,
+ private val notificationStackScrollView: Lazy<NotificationScrollView>,
private val viewModel: ShadeSceneViewModel,
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
@@ -139,6 +147,7 @@
modifier: Modifier,
) =
ShadeScene(
+ notificationStackScrollView.get(),
viewModel = viewModel,
createTintedIconManager = tintedIconManagerFactory::create,
createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
@@ -158,6 +167,7 @@
@Composable
private fun SceneScope.ShadeScene(
+ notificationStackScrollView: NotificationScrollView,
viewModel: ShadeSceneViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -171,6 +181,7 @@
when (shadeMode) {
is ShadeMode.Single ->
SingleShade(
+ notificationStackScrollView = notificationStackScrollView,
viewModel = viewModel,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
@@ -182,6 +193,7 @@
)
is ShadeMode.Split ->
SplitShade(
+ notificationStackScrollView = notificationStackScrollView,
viewModel = viewModel,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
@@ -197,6 +209,7 @@
@Composable
private fun SceneScope.SingleShade(
+ notificationStackScrollView: NotificationScrollView,
viewModel: ShadeSceneViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -206,6 +219,8 @@
modifier: Modifier = Modifier,
shadeSession: SaveableSession,
) {
+ val cutoutLocation = LocalDisplayCutout.current.location
+
val maxNotifScrimTop = remember { mutableStateOf(0f) }
val tileSquishiness by
animateSceneFloatAsState(
@@ -242,19 +257,21 @@
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier =
- Modifier.fillMaxWidth().thenIf(isClickable) {
- Modifier.clickable(onClick = { viewModel.onContentClicked() })
- }
+ Modifier.fillMaxWidth()
+ .thenIf(isClickable) {
+ Modifier.clickable(
+ onClick = { viewModel.onContentClicked() }
+ )
+ }
+ .thenIf(cutoutLocation != CutoutLocation.CENTER) {
+ Modifier.displayCutoutPadding()
+ },
) {
CollapsedShadeHeader(
viewModel = viewModel.shadeHeaderViewModel,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
- modifier =
- Modifier.padding(
- horizontal = Shade.Dimensions.HorizontalPadding
- )
)
Box(Modifier.element(QuickSettings.Elements.QuickQuickSettings)) {
QuickSettings(
@@ -278,6 +295,7 @@
{
NotificationScrollingStack(
shadeSession = shadeSession,
+ stackScrollView = notificationStackScrollView,
viewModel = viewModel.notifications,
maxScrimTop = { maxNotifScrimTop.value },
shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
@@ -304,6 +322,7 @@
@Composable
private fun SceneScope.SplitShade(
+ notificationStackScrollView: NotificationScrollView,
viewModel: ShadeSceneViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -313,6 +332,8 @@
modifier: Modifier = Modifier,
shadeSession: SaveableSession,
) {
+ val screenCornerRadius = LocalScreenCornerRadius.current
+
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState()
val customizingAnimationDuration by
@@ -321,7 +342,11 @@
val footerActionsViewModel =
remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
val tileSquishiness by
- animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
+ animateSceneFloatAsState(
+ value = 1f,
+ key = QuickSettings.SharedValues.TilesSquishiness,
+ canOverflow = false,
+ )
val unfoldTranslationXForStartSide by
viewModel
.unfoldTranslationX(
@@ -391,8 +416,7 @@
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
modifier =
- Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
- .then(brightnessMirrorShowingModifier)
+ Modifier.then(brightnessMirrorShowingModifier)
.padding(
horizontal = { unfoldTranslationXForStartSide.roundToInt() },
)
@@ -401,9 +425,9 @@
Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
Box(
modifier =
- Modifier.weight(1f).graphicsLayer {
- translationX = unfoldTranslationXForStartSide
- },
+ Modifier.element(Shade.Elements.SplitShadeStartColumn)
+ .weight(1f)
+ .graphicsLayer { translationX = unfoldTranslationXForStartSide },
) {
BrightnessMirror(
viewModel = viewModel.brightnessMirrorViewModel,
@@ -464,13 +488,14 @@
NotificationScrollingStack(
shadeSession = shadeSession,
+ stackScrollView = notificationStackScrollView,
viewModel = viewModel.notifications,
maxScrimTop = { 0f },
shouldPunchHoleBehindScrim = false,
modifier =
Modifier.weight(1f)
.fillMaxHeight()
- .padding(bottom = navBarBottomHeight)
+ .padding(end = screenCornerRadius / 2f, bottom = navBarBottomHeight)
.then(brightnessMirrorShowingModifier)
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index a46f4e5..cb3867f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -34,12 +34,15 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.progressBarRangeInfo
import androidx.compose.ui.semantics.setProgress
+import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformSlider
import com.android.compose.PlatformSliderColors
@@ -60,14 +63,31 @@
PlatformSlider(
modifier =
modifier.clearAndSetSemantics {
- if (!state.isEnabled) disabled()
- contentDescription =
- state.disabledMessage?.let { "${state.label}, $it" } ?: state.label
-
- // provide a not animated value to the a11y because it fails to announce the
- // settled value when it changes rapidly.
if (state.isEnabled) {
- progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange)
+ contentDescription = state.label
+ state.a11yClickDescription?.let {
+ customActions =
+ listOf(
+ CustomAccessibilityAction(
+ it,
+ ) {
+ onIconTapped()
+ true
+ }
+ )
+ }
+
+ state.a11yStateDescription?.let { stateDescription = it }
+ ?: run {
+ // provide a not animated value to the a11y because it fails to announce
+ // the settled value when it changes rapidly.
+ progressBarRangeInfo =
+ ProgressBarRangeInfo(state.value, state.valueRange)
+ }
+ } else {
+ disabled()
+ contentDescription =
+ state.disabledMessage?.let { "${state.label}, $it" } ?: state.label
}
setProgress { targetValue ->
val targetDirection =
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index f539a23..bdeab79 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -28,16 +28,16 @@
import android.util.AttributeSet
import android.util.MathUtils.constrainedMap
import android.util.TypedValue
-import android.view.View.MeasureSpec.EXACTLY
import android.view.View
+import android.view.View.MeasureSpec.EXACTLY
import android.widget.TextView
import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.TextAnimator
import com.android.systemui.customization.R
-import com.android.systemui.log.core.LogcatOnlyMessageBuffer
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.core.MessageBuffer
import java.io.PrintWriter
@@ -47,11 +47,13 @@
import kotlin.math.min
/**
- * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
- * The time's text color is a gradient that changes its colors based on its controller.
+ * Displays the time with the hour positioned above the minutes (ie: 09 above 30 is 9:30). The
+ * time's text color is a gradient that changes its colors based on its controller.
*/
@SuppressLint("AppCompatCustomView")
-class AnimatableClockView @JvmOverloads constructor(
+class AnimatableClockView
+@JvmOverloads
+constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
@@ -63,7 +65,9 @@
get() = field ?: DEFAULT_LOGGER
var messageBuffer: MessageBuffer
get() = logger.buffer
- set(value) { logger = Logger(value, TAG) }
+ set(value) {
+ logger = Logger(value, TAG)
+ }
var hasCustomPositionUpdatedAnimation: Boolean = false
var migratedClocks: Boolean = false
@@ -77,16 +81,13 @@
private var format: CharSequence? = null
private var descFormat: CharSequence? = null
- @ColorInt
- private var dozingColor = 0
-
- @ColorInt
- private var lockScreenColor = 0
+ @ColorInt private var dozingColor = 0
+ @ColorInt private var lockScreenColor = 0
private var lineSpacingScale = 1f
private val chargeAnimationDelay: Int
private var textAnimator: TextAnimator? = null
- private var onTextAnimatorInitialized: Runnable? = null
+ private var onTextAnimatorInitialized: ((TextAnimator) -> Unit)? = null
private var translateForCenterAnimation = false
private val parentWidth: Int
@@ -94,9 +95,11 @@
// last text size which is not constrained by view height
private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE
- @VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator =
- { layout, invalidateCb ->
- TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb) }
+
+ @VisibleForTesting
+ var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb ->
+ TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb)
+ }
// Used by screenshot tests to provide stability
@VisibleForTesting var isAnimationEnabled: Boolean = true
@@ -109,40 +112,55 @@
get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal
/**
- * The number of pixels below the baseline. For fonts that support languages such as
- * Burmese, this space can be significant and should be accounted for when computing layout.
+ * The number of pixels below the baseline. For fonts that support languages such as Burmese,
+ * this space can be significant and should be accounted for when computing layout.
*/
- val bottom get() = paint?.fontMetrics?.bottom ?: 0f
+ val bottom: Float
+ get() = paint?.fontMetrics?.bottom ?: 0f
init {
- val animatableClockViewAttributes = context.obtainStyledAttributes(
- attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes
- )
+ val animatableClockViewAttributes =
+ context.obtainStyledAttributes(
+ attrs,
+ R.styleable.AnimatableClockView,
+ defStyleAttr,
+ defStyleRes
+ )
try {
- dozingWeightInternal = animatableClockViewAttributes.getInt(
- R.styleable.AnimatableClockView_dozeWeight,
- /* default = */ 100
- )
- lockScreenWeightInternal = animatableClockViewAttributes.getInt(
- R.styleable.AnimatableClockView_lockScreenWeight,
- /* default = */ 300
- )
- chargeAnimationDelay = animatableClockViewAttributes.getInt(
- R.styleable.AnimatableClockView_chargeAnimationDelay, /* default = */ 200
- )
+ dozingWeightInternal =
+ animatableClockViewAttributes.getInt(
+ R.styleable.AnimatableClockView_dozeWeight,
+ /* default = */ 100
+ )
+ lockScreenWeightInternal =
+ animatableClockViewAttributes.getInt(
+ R.styleable.AnimatableClockView_lockScreenWeight,
+ /* default = */ 300
+ )
+ chargeAnimationDelay =
+ animatableClockViewAttributes.getInt(
+ R.styleable.AnimatableClockView_chargeAnimationDelay,
+ /* default = */ 200
+ )
} finally {
animatableClockViewAttributes.recycle()
}
- val textViewAttributes = context.obtainStyledAttributes(
- attrs, android.R.styleable.TextView,
- defStyleAttr, defStyleRes
- )
+ val textViewAttributes =
+ context.obtainStyledAttributes(
+ attrs,
+ android.R.styleable.TextView,
+ defStyleAttr,
+ defStyleRes
+ )
try {
- isSingleLineInternal = textViewAttributes.getBoolean(
- android.R.styleable.TextView_singleLine, /* default = */ false)
+ isSingleLineInternal =
+ textViewAttributes.getBoolean(
+ android.R.styleable.TextView_singleLine,
+ /* default = */ false
+ )
} finally {
textViewAttributes.recycle()
}
@@ -156,9 +174,7 @@
refreshFormat()
}
- /**
- * Whether to use a bolded version based on the user specified fontWeightAdjustment.
- */
+ /** Whether to use a bolded version based on the user specified fontWeightAdjustment. */
fun useBoldedVersion(): Boolean {
// "Bold text" fontWeightAdjustment is 300.
return resources.configuration.fontWeightAdjustment > 100
@@ -169,25 +185,30 @@
contentDescription = DateFormat.format(descFormat, time)
val formattedText = DateFormat.format(format, time)
logger.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
- // Setting text actually triggers a layout pass (because the text view is set to
- // wrap_content width and TextView always relayouts for this). Avoid needless
- // relayout if the text didn't actually change.
- if (!TextUtils.equals(text, formattedText)) {
- text = formattedText
- logger.d({ "refreshTime: done setting new time text to: $str1" }) {
- str1 = formattedText?.toString()
- }
- // Because the TextLayout may mutate under the hood as a result of the new text, we
- // notify the TextAnimator that it may have changed and request a measure/layout. A
- // crash will occur on the next invocation of setTextStyle if the layout is mutated
- // without being notified TextInterpolator being notified.
- if (layout != null) {
- textAnimator?.updateLayout(layout)
- logger.d("refreshTime: done updating textAnimator layout")
- }
- requestLayout()
- logger.d("refreshTime: after requestLayout")
+
+ // Setting text actually triggers a layout pass in TextView (because the text view is set to
+ // wrap_content width and TextView always relayouts for this). This avoids needless relayout
+ // if the text didn't actually change.
+ if (TextUtils.equals(text, formattedText)) {
+ return
}
+
+ text = formattedText
+ logger.d({ "refreshTime: done setting new time text to: $str1" }) {
+ str1 = formattedText?.toString()
+ }
+
+ // Because the TextLayout may mutate under the hood as a result of the new text, we notify
+ // the TextAnimator that it may have changed and request a measure/layout. A crash will
+ // occur on the next invocation of setTextStyle if the layout is mutated without being
+ // notified TextInterpolator being notified.
+ if (layout != null) {
+ textAnimator?.updateLayout(layout)
+ logger.d("refreshTime: done updating textAnimator layout")
+ }
+
+ requestLayout()
+ logger.d("refreshTime: after requestLayout")
}
fun onTimeZoneChanged(timeZone: TimeZone?) {
@@ -206,19 +227,27 @@
@SuppressLint("DrawAllocation")
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
logger.d("onMeasure")
- if (migratedClocks && !isSingleLineInternal &&
- MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) {
+
+ if (
+ migratedClocks &&
+ !isSingleLineInternal &&
+ MeasureSpec.getMode(heightMeasureSpec) == EXACTLY
+ ) {
// Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize
- super.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F))
+ super.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F)
+ )
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val animator = textAnimator
if (animator == null) {
- textAnimator = textAnimatorFactory(layout, ::invalidate)
- onTextAnimatorInitialized?.run()
- onTextAnimatorInitialized = null
+ textAnimator =
+ textAnimatorFactory(layout, ::invalidate)?.also {
+ onTextAnimatorInitialized?.invoke(it)
+ onTextAnimatorInitialized = null
+ }
} else {
animator.updateLayout(layout)
}
@@ -243,15 +272,13 @@
canvas.translate(parentWidth / 4f, 0f)
}
- logger.d({ "onDraw($str1)"}) { str1 = text.toString() }
+ logger.d({ "onDraw($str1)" }) { str1 = text.toString() }
// intentionally doesn't call super.onDraw here or else the text will be rendered twice
textAnimator?.draw(canvas)
canvas.restore()
}
override fun invalidate() {
- @Suppress("UNNECESSARY_SAFE_CALL")
- // logger won't be initialized when called by TextView's constructor
logger.d("invalidate")
super.invalidate()
}
@@ -325,6 +352,7 @@
if (textAnimator == null) {
return
}
+
logger.d("animateFoldAppear")
setTextStyle(
weight = lockScreenWeightInternal,
@@ -348,10 +376,11 @@
}
fun animateCharge(isDozing: () -> Boolean) {
+ // Skip charge animation if dozing animation is already playing.
if (textAnimator == null || textAnimator!!.isRunning()) {
- // Skip charge animation if dozing animation is already playing.
return
}
+
logger.d("animateCharge")
val startAnimPhase2 = Runnable {
setTextStyle(
@@ -409,10 +438,9 @@
/**
* Set text style with an optional animation.
- *
- * By passing -1 to weight, the view preserves its current weight.
- * By passing -1 to textSize, the view preserves its current text size.
- * By passing null to color, the view preserves its current color.
+ * - By passing -1 to weight, the view preserves its current weight.
+ * - By passing -1 to textSize, the view preserves its current text size.
+ * - By passing null to color, the view preserves its current color.
*
* @param weight text weight.
* @param textSize font size.
@@ -428,8 +456,8 @@
delay: Long,
onAnimationEnd: Runnable?
) {
- if (textAnimator != null) {
- textAnimator?.setTextStyle(
+ textAnimator?.let {
+ it.setTextStyle(
weight = weight,
textSize = textSize,
color = color,
@@ -439,23 +467,24 @@
delay = delay,
onAnimationEnd = onAnimationEnd
)
- textAnimator?.glyphFilter = glyphFilter
- } else {
- // when the text animator is set, update its start values
- onTextAnimatorInitialized = Runnable {
- textAnimator?.setTextStyle(
- weight = weight,
- textSize = textSize,
- color = color,
- animate = false,
- duration = duration,
- interpolator = interpolator,
- delay = delay,
- onAnimationEnd = onAnimationEnd
- )
- textAnimator?.glyphFilter = glyphFilter
- }
+ it.glyphFilter = glyphFilter
}
+ ?: run {
+ // when the text animator is set, update its start values
+ onTextAnimatorInitialized = { textAnimator ->
+ textAnimator.setTextStyle(
+ weight = weight,
+ textSize = textSize,
+ color = color,
+ animate = false,
+ duration = duration,
+ interpolator = interpolator,
+ delay = delay,
+ onAnimationEnd = onAnimationEnd
+ )
+ textAnimator.glyphFilter = glyphFilter
+ }
+ }
}
private fun setTextStyle(
@@ -483,12 +512,13 @@
fun refreshFormat(use24HourFormat: Boolean) {
Patterns.update(context)
- format = when {
- isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
- !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR
- isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
- else -> DOUBLE_LINE_FORMAT_12_HOUR
- }
+ format =
+ when {
+ isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
+ !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR
+ isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
+ else -> DOUBLE_LINE_FORMAT_12_HOUR
+ }
logger.d({ "refreshFormat($str1)" }) { str1 = format?.toString() }
descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
@@ -510,10 +540,10 @@
pw.println(" time=$time")
}
- private val moveToCenterDelays
+ private val moveToCenterDelays: List<Int>
get() = if (isLayoutRtl) MOVE_LEFT_DELAYS else MOVE_RIGHT_DELAYS
- private val moveToSideDelays
+ private val moveToSideDelays: List<Int>
get() = if (isLayoutRtl) MOVE_RIGHT_DELAYS else MOVE_LEFT_DELAYS
/**
@@ -531,7 +561,7 @@
fun offsetGlyphsForStepClockAnimation(
clockStartLeft: Int,
clockMoveDirection: Int,
- moveFraction: Float
+ moveFraction: Float,
) {
val isMovingToCenter = if (isLayoutRtl) clockMoveDirection < 0 else clockMoveDirection > 0
val currentMoveAmount = left - clockStartLeft
@@ -558,8 +588,8 @@
*
* @param distance is the total distance in pixels to offset the glyphs when animation
* completes. Negative distance means we are animating the position towards the center.
- * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1
- * means it finished moving.
+ * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
+ * it finished moving.
*/
fun offsetGlyphsForStepClockAnimation(
distance: Float,
@@ -568,13 +598,17 @@
for (i in 0 until NUM_DIGITS) {
val dir = if (isLayoutRtl) -1 else 1
val digitFraction =
- getDigitFraction(digit = i, isMovingToCenter = distance > 0, fraction = fraction)
+ getDigitFraction(
+ digit = i,
+ isMovingToCenter = distance > 0,
+ fraction = fraction,
+ )
val moveAmountForDigit = dir * distance * digitFraction
glyphOffsets[i] = moveAmountForDigit
if (distance > 0) {
- // If distance > 0 then we are moving from the left towards the center.
- // We need ensure that the glyphs are offset to the initial position.
+ // If distance > 0 then we are moving from the left towards the center. We need to
+ // ensure that the glyphs are offset to the initial position.
glyphOffsets[i] -= dir * distance
}
}
@@ -582,27 +616,25 @@
}
private fun getDigitFraction(digit: Int, isMovingToCenter: Boolean, fraction: Float): Float {
- // The delay for the digit, in terms of fraction (i.e. the digit should not move
- // during 0.0 - 0.1).
- val digitInitialDelay =
- if (isMovingToCenter) {
- moveToCenterDelays[digit] * MOVE_DIGIT_STEP
- } else {
- moveToSideDelays[digit] * MOVE_DIGIT_STEP
- }
+ // The delay for the digit, in terms of fraction.
+ // (i.e. the digit should not move during 0.0 - 0.1).
+ val delays = if (isMovingToCenter) moveToCenterDelays else moveToSideDelays
+ val digitInitialDelay = delays[digit] * MOVE_DIGIT_STEP
return MOVE_INTERPOLATOR.getInterpolation(
- constrainedMap(
- 0.0f,
- 1.0f,
- digitInitialDelay,
- digitInitialDelay + AVAILABLE_ANIMATION_TIME,
- fraction,
- )
+ constrainedMap(
+ /* rangeMin= */ 0.0f,
+ /* rangeMax= */ 1.0f,
+ /* valueMin= */ digitInitialDelay,
+ /* valueMax= */ digitInitialDelay + AVAILABLE_ANIMATION_TIME,
+ /* value= */ fraction,
)
+ )
}
- // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
- // This is an optimization to ensure we only recompute the patterns when the inputs change.
+ /**
+ * DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. This
+ * is a cache optimization to ensure we only recompute the patterns when the inputs change.
+ */
private object Patterns {
var sClockView12: String? = null
var sClockView24: String? = null
@@ -610,21 +642,22 @@
fun update(context: Context) {
val locale = Locale.getDefault()
- val res = context.resources
- val clockView12Skel = res.getString(R.string.clock_12hr_format)
- val clockView24Skel = res.getString(R.string.clock_24hr_format)
- val key = locale.toString() + clockView12Skel + clockView24Skel
- if (key == sCacheKey) return
-
- val clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel)
- sClockView12 = clockView12
-
- // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
- // format. The following code removes the AM/PM indicator if we didn't want it.
- if (!clockView12Skel.contains("a")) {
- sClockView12 = clockView12.replace("a".toRegex(), "").trim { it <= ' ' }
+ val clockView12Skel = context.resources.getString(R.string.clock_12hr_format)
+ val clockView24Skel = context.resources.getString(R.string.clock_24hr_format)
+ val key = "$locale$clockView12Skel$clockView24Skel"
+ if (key == sCacheKey) {
+ return
}
+ sClockView12 =
+ DateFormat.getBestDateTimePattern(locale, clockView12Skel).let {
+ // CLDR insists on adding an AM/PM indicator even though it wasn't in the format
+ // string. The following code removes the AM/PM indicator if we didn't want it.
+ if (!clockView12Skel.contains("a")) {
+ it.replace("a".toRegex(), "").trim { it <= ' ' }
+ } else it
+ }
+
sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel)
sCacheKey = key
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 3d8159e..9c9ee53 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -24,7 +24,6 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.util.settings.FakeSettings
-import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -66,7 +65,7 @@
runCurrent()
- Truth.assertThat(actualValue).isFalse()
+ assertThat(actualValue).isFalse()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt
new file mode 100644
index 0000000..ca824cb
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.utils.leaks.FakeLocationController
+import com.google.common.truth.Truth.assertThat
+import java.time.LocalTime
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayRepositoryTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testUser = UserHandle.of(1)!!
+ private val testStartTime = LocalTime.MIDNIGHT
+ private val testEndTime = LocalTime.NOON
+ private val colorDisplayManager =
+ mock<ColorDisplayManager> {
+ whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED)
+ whenever(isNightDisplayActivated).thenReturn(false)
+ whenever(nightDisplayCustomStartTime).thenReturn(testStartTime)
+ whenever(nightDisplayCustomEndTime).thenReturn(testEndTime)
+ }
+ private val locationController = FakeLocationController(LeakCheck())
+ private val nightDisplayListener = mock<NightDisplayListener>()
+ private val listenerBuilder =
+ mock<NightDisplayListenerModule.Builder> {
+ whenever(setUser(ArgumentMatchers.anyInt())).thenReturn(this)
+ whenever(build()).thenReturn(nightDisplayListener)
+ }
+ private val globalSettings = kosmos.fakeGlobalSettings
+ private val secureSettings = kosmos.fakeSettings
+ private val testDispatcher = StandardTestDispatcher()
+ private val scope = TestScope(testDispatcher)
+ private val userScopedColorDisplayManager =
+ mock<UserScopedService<ColorDisplayManager>> {
+ whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager)
+ }
+
+ private val underTest =
+ NightDisplayRepository(
+ testDispatcher,
+ scope.backgroundScope,
+ globalSettings,
+ secureSettings,
+ listenerBuilder,
+ userScopedColorDisplayManager,
+ locationController,
+ )
+
+ @Before
+ fun setup() {
+ enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser)
+ }
+
+ @Test
+ fun nightDisplayState_matchesAutoMode() =
+ scope.runTest {
+ enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser)
+ val callbackCaptor = argumentCaptor<NightDisplayListener.Callback>()
+ val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+
+ runCurrent()
+
+ verify(nightDisplayListener).setCallback(callbackCaptor.capture())
+ val callback = callbackCaptor.value
+
+ assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_DISABLED)
+
+ callback.onAutoModeChanged(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+ assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+
+ callback.onCustomStartTimeChanged(testStartTime)
+ assertThat(lastState!!.startTime).isEqualTo(testStartTime)
+
+ callback.onCustomEndTimeChanged(testEndTime)
+ assertThat(lastState!!.endTime).isEqualTo(testEndTime)
+
+ callback.onAutoModeChanged(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+
+ assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+ }
+
+ @Test
+ fun nightDisplayState_matchesIsNightDisplayActivated() =
+ scope.runTest {
+ val callbackCaptor = argumentCaptor<NightDisplayListener.Callback>()
+
+ val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+ runCurrent()
+
+ verify(nightDisplayListener).setCallback(callbackCaptor.capture())
+ val callback = callbackCaptor.value
+ assertThat(lastState!!.isActivated)
+ .isEqualTo(colorDisplayManager.isNightDisplayActivated)
+
+ callback.onActivated(true)
+ assertThat(lastState!!.isActivated).isTrue()
+
+ callback.onActivated(false)
+ assertThat(lastState!!.isActivated).isFalse()
+ }
+
+ @Test
+ fun nightDisplayState_matchesController_initiallyCustomAutoMode() =
+ scope.runTest {
+ whenever(colorDisplayManager.nightDisplayAutoMode)
+ .thenReturn(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+
+ val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+ runCurrent()
+
+ assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+ }
+
+ @Test
+ fun nightDisplayState_matchesController_initiallyTwilightAutoMode() =
+ scope.runTest {
+ whenever(colorDisplayManager.nightDisplayAutoMode)
+ .thenReturn(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+
+ val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+ runCurrent()
+
+ assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+ }
+
+ @Test
+ fun nightDisplayState_matchesForceAutoMode() =
+ scope.runTest {
+ enrollInForcedNightDisplayAutoMode(false, testUser)
+ val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+ runCurrent()
+
+ assertThat(lastState!!.shouldForceAutoMode).isEqualTo(false)
+
+ enrollInForcedNightDisplayAutoMode(true, testUser)
+ assertThat(lastState!!.shouldForceAutoMode).isEqualTo(true)
+ }
+
+ private fun enrollInForcedNightDisplayAutoMode(enroll: Boolean, userHandle: UserHandle) {
+ globalSettings.putString(
+ Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE,
+ if (enroll) NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE
+ else NIGHT_DISPLAY_FORCED_AUTO_MODE_UNAVAILABLE
+ )
+ secureSettings.putIntForUser(
+ Settings.Secure.NIGHT_DISPLAY_AUTO_MODE,
+ if (enroll) NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET else NIGHT_DISPLAY_AUTO_MODE_RAW_SET,
+ userHandle.identifier
+ )
+ }
+
+ private companion object {
+ const val INITIALLY_FORCE_AUTO_MODE = false
+ const val NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE = "1"
+ const val NIGHT_DISPLAY_FORCED_AUTO_MODE_UNAVAILABLE = "0"
+ const val NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET = -1
+ const val NIGHT_DISPLAY_AUTO_MODE_RAW_SET = 0
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index f4ad764..9b1d4ec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -84,7 +84,6 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
-import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
@@ -161,8 +160,6 @@
@Mock
private InteractionJankMonitor mInteractionJankMonitor;
@Mock
- private PromptCredentialInteractor mBiometricPromptCredentialInteractor;
- @Mock
private PromptSelectorInteractor mPromptSelectionInteractor;
@Mock
private CredentialViewModel mCredentialViewModel;
@@ -1057,7 +1054,6 @@
private final class TestableAuthController extends AuthController {
private int mBuildCount = 0;
- private PromptInfo mLastBiometricPromptInfo;
TestableAuthController(Context context) {
super(context, null /* applicationCoroutineScope */,
@@ -1065,8 +1061,8 @@
mFingerprintManager, mFaceManager, () -> mUdfpsController, mDisplayManager,
mWakefulnessLifecycle, mPanelInteractionDetector, mUserManager,
mLockPatternUtils, () -> mUdfpsLogger, () -> mLogContextInteractor,
- () -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor,
- () -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor,
+ () -> mPromptSelectionInteractor, () -> mCredentialViewModel,
+ () -> mPromptViewModel, mInteractionJankMonitor,
mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper);
}
@@ -1079,8 +1075,6 @@
UserManager userManager,
LockPatternUtils lockPatternUtils, PromptViewModel viewModel) {
- mLastBiometricPromptInfo = promptInfo;
-
AuthDialog dialog;
if (mBuildCount == 0) {
dialog = mDialog1;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt
index 2386957..7628deb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt
@@ -50,6 +50,7 @@
@Mock private lateinit var context: Context
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var handler: Handler
+ @Mock private lateinit var packageInstallerMonitor: PackageInstallerMonitor
private lateinit var repository: PackageChangeRepository
private lateinit var updateMonitor: PackageUpdateMonitor
@@ -60,19 +61,20 @@
MockitoAnnotations.initMocks(this@PackageChangeRepositoryTest)
whenever(context.packageManager).thenReturn(packageManager)
- repository = PackageChangeRepositoryImpl { user ->
- updateMonitor =
- PackageUpdateMonitor(
- user = user,
- bgDispatcher = testDispatcher,
- scope = applicationCoroutineScope,
- context = context,
- bgHandler = handler,
- logger = PackageUpdateLogger(logcatLogBuffer()),
- systemClock = fakeSystemClock,
- )
- updateMonitor
- }
+ repository =
+ PackageChangeRepositoryImpl(packageInstallerMonitor) { user ->
+ updateMonitor =
+ PackageUpdateMonitor(
+ user = user,
+ bgDispatcher = testDispatcher,
+ scope = applicationCoroutineScope,
+ context = context,
+ bgHandler = handler,
+ logger = PackageUpdateLogger(logcatLogBuffer()),
+ systemClock = fakeSystemClock,
+ )
+ updateMonitor
+ }
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
new file mode 100644
index 0000000..5556b04
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.data.repository
+
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageInstaller.SessionInfo
+import android.graphics.Bitmap
+import android.os.fakeExecutorHandler
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.PackageInstallSession
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Correspondence
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PackageInstallerMonitorTest : SysuiTestCase() {
+ @Mock private lateinit var packageInstaller: PackageInstaller
+ @Mock private lateinit var icon1: Bitmap
+ @Mock private lateinit var icon2: Bitmap
+ @Mock private lateinit var icon3: Bitmap
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val handler = kosmos.fakeExecutorHandler
+
+ private lateinit var session1: SessionInfo
+ private lateinit var session2: SessionInfo
+ private lateinit var session3: SessionInfo
+
+ private lateinit var defaultSessions: List<SessionInfo>
+
+ private lateinit var underTest: PackageInstallerMonitor
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ session1 =
+ SessionInfo().apply {
+ sessionId = 1
+ appPackageName = "pkg_name_1"
+ appIcon = icon1
+ }
+ session2 =
+ SessionInfo().apply {
+ sessionId = 2
+ appPackageName = "pkg_name_2"
+ appIcon = icon2
+ }
+ session3 =
+ SessionInfo().apply {
+ sessionId = 3
+ appPackageName = "pkg_name_3"
+ appIcon = icon3
+ }
+ defaultSessions = listOf(session1, session2)
+
+ whenever(packageInstaller.allSessions).thenReturn(defaultSessions)
+ whenever(packageInstaller.getSessionInfo(1)).thenReturn(session1)
+ whenever(packageInstaller.getSessionInfo(2)).thenReturn(session2)
+
+ underTest =
+ PackageInstallerMonitor(
+ handler,
+ kosmos.applicationCoroutineScope,
+ logcatLogBuffer("PackageInstallerRepositoryImplTest"),
+ packageInstaller,
+ )
+ }
+
+ @Test
+ fun installSessions_callbacksRegisteredOnlyWhenFlowIsCollected() =
+ testScope.runTest {
+ // Verify callback not added before flow is collected
+ verify(packageInstaller, never()).registerSessionCallback(any(), eq(handler))
+
+ // Start collecting the flow
+ val job =
+ backgroundScope.launch {
+ underTest.installSessionsForPrimaryUser.collect {
+ // Do nothing with the value
+ }
+ }
+ runCurrent()
+
+ // Verify callback added only after flow is collected
+ val callback =
+ withArgCaptor<PackageInstaller.SessionCallback> {
+ verify(packageInstaller).registerSessionCallback(capture(), eq(handler))
+ }
+
+ // Verify callback not removed
+ verify(packageInstaller, never()).unregisterSessionCallback(any())
+
+ // Stop collecting the flow
+ job.cancel()
+ runCurrent()
+
+ // Verify callback removed once flow stops being collected
+ verify(packageInstaller).unregisterSessionCallback(eq(callback))
+ }
+
+ @Test
+ fun installSessions_newSessionsAreAdded() =
+ testScope.runTest {
+ val installSessions by collectLastValue(underTest.installSessionsForPrimaryUser)
+ assertThat(installSessions)
+ .comparingElementsUsing(represents)
+ .containsExactlyElementsIn(defaultSessions)
+
+ val callback =
+ withArgCaptor<PackageInstaller.SessionCallback> {
+ verify(packageInstaller).registerSessionCallback(capture(), eq(handler))
+ }
+
+ // New session added
+ whenever(packageInstaller.getSessionInfo(3)).thenReturn(session3)
+ callback.onCreated(3)
+ runCurrent()
+
+ // Verify flow updated with the new session
+ assertThat(installSessions)
+ .comparingElementsUsing(represents)
+ .containsExactlyElementsIn(defaultSessions + session3)
+ }
+
+ @Test
+ fun installSessions_finishedSessionsAreRemoved() =
+ testScope.runTest {
+ val installSessions by collectLastValue(underTest.installSessionsForPrimaryUser)
+ assertThat(installSessions)
+ .comparingElementsUsing(represents)
+ .containsExactlyElementsIn(defaultSessions)
+
+ val callback =
+ withArgCaptor<PackageInstaller.SessionCallback> {
+ verify(packageInstaller).registerSessionCallback(capture(), eq(handler))
+ }
+
+ // Session 1 finished successfully
+ callback.onFinished(1, /* success = */ true)
+ runCurrent()
+
+ // Verify flow updated with session 1 removed
+ assertThat(installSessions)
+ .comparingElementsUsing(represents)
+ .containsExactlyElementsIn(defaultSessions - session1)
+ }
+
+ @Test
+ fun installSessions_sessionsUpdatedOnBadgingChange() =
+ testScope.runTest {
+ val installSessions by collectLastValue(underTest.installSessionsForPrimaryUser)
+ assertThat(installSessions)
+ .comparingElementsUsing(represents)
+ .containsExactlyElementsIn(defaultSessions)
+
+ val callback =
+ withArgCaptor<PackageInstaller.SessionCallback> {
+ verify(packageInstaller).registerSessionCallback(capture(), eq(handler))
+ }
+
+ // App icon for session 1 updated
+ val newSession =
+ SessionInfo().apply {
+ sessionId = 1
+ appPackageName = "pkg_name_1"
+ appIcon = mock()
+ }
+ whenever(packageInstaller.getSessionInfo(1)).thenReturn(newSession)
+ callback.onBadgingChanged(1)
+ runCurrent()
+
+ // Verify flow updated with the new session 1
+ assertThat(installSessions)
+ .comparingElementsUsing(represents)
+ .containsExactlyElementsIn(defaultSessions - session1 + newSession)
+ }
+
+ private val represents =
+ Correspondence.from<PackageInstallSession, SessionInfo>(
+ { actual, expected ->
+ actual?.sessionId == expected?.sessionId &&
+ actual?.packageName == expected?.appPackageName &&
+ actual?.icon == expected?.getAppIcon()
+ },
+ "represents",
+ )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
index def63ec..bfed33c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
@@ -16,12 +16,16 @@
package com.android.systemui.communal
+import android.platform.test.annotations.EnableFlags
import android.service.dream.dreamManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -62,6 +66,7 @@
powerInteractor = kosmos.powerInteractor,
keyguardInteractor = kosmos.keyguardInteractor,
keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ communalInteractor = kosmos.communalInteractor,
dreamManager = dreamManager,
bgScope = kosmos.applicationCoroutineScope,
)
@@ -84,6 +89,32 @@
}
@Test
+ @EnableFlags(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE)
+ fun restartDreamingWhenTransitioningFromDreamingToOccludedToDreaming() =
+ testScope.runTest {
+ keyguardRepository.setDreaming(false)
+ powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
+ runCurrent()
+
+ verify(dreamManager, never()).startDream()
+
+ kosmos.fakeKeyguardRepository.setKeyguardOccluded(true)
+ kosmos.fakeKeyguardRepository.setDreaming(true)
+ runCurrent()
+
+ transition(from = KeyguardState.DREAMING, to = KeyguardState.OCCLUDED)
+ kosmos.fakeKeyguardRepository.setKeyguardOccluded(false)
+ kosmos.fakeKeyguardRepository.setDreaming(false)
+ runCurrent()
+
+ transition(from = KeyguardState.OCCLUDED, to = KeyguardState.DREAMING)
+ runCurrent()
+
+ verify(dreamManager).startDream()
+ }
+
+ @Test
fun shouldNotStartDreamWhenIneligibleToDream() =
testScope.runTest {
keyguardRepository.setDreaming(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index fe4d32d..6ce6cdb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -17,16 +17,18 @@
package com.android.systemui.communal.data.repository
import android.app.backup.BackupManager
-import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
import android.content.ComponentName
import android.content.applicationContext
+import android.graphics.Bitmap
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.repository.fakePackageChangeRepository
+import com.android.systemui.common.shared.model.PackageInstallSession
import com.android.systemui.communal.data.backup.CommunalBackupUtils
import com.android.systemui.communal.data.db.CommunalItemRank
import com.android.systemui.communal.data.db.CommunalWidgetDao
@@ -46,10 +48,10 @@
import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
-import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
@@ -58,7 +60,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.anyInt
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@@ -68,10 +69,10 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
- @Mock private lateinit var appWidgetManager: AppWidgetManager
@Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
- @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
@Mock private lateinit var providerInfoA: AppWidgetProviderInfo
+ @Mock private lateinit var providerInfoB: AppWidgetProviderInfo
+ @Mock private lateinit var providerInfoC: AppWidgetProviderInfo
@Mock private lateinit var communalWidgetHost: CommunalWidgetHost
@Mock private lateinit var communalWidgetDao: CommunalWidgetDao
@Mock private lateinit var backupManager: BackupManager
@@ -79,9 +80,11 @@
private lateinit var backupUtils: CommunalBackupUtils
private lateinit var logBuffer: LogBuffer
private lateinit var fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>>
+ private lateinit var fakeProviders: MutableStateFlow<Map<Int, AppWidgetProviderInfo?>>
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private val packageChangeRepository = kosmos.fakePackageChangeRepository
private val fakeAllowlist =
listOf(
@@ -96,6 +99,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
fakeWidgets = MutableStateFlow(emptyMap())
+ fakeProviders = MutableStateFlow(emptyMap())
logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoImplTest")
backupUtils = CommunalBackupUtils(kosmos.applicationContext)
@@ -103,12 +107,11 @@
overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
- whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
whenever(communalWidgetDao.getWidgets()).thenReturn(fakeWidgets)
+ whenever(communalWidgetHost.appWidgetProviders).thenReturn(fakeProviders)
underTest =
CommunalWidgetRepositoryImpl(
- Optional.of(appWidgetManager),
appWidgetHost,
testScope.backgroundScope,
kosmos.testDispatcher,
@@ -117,6 +120,7 @@
logBuffer,
backupManager,
backupUtils,
+ packageChangeRepository,
)
}
@@ -126,15 +130,13 @@
val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
fakeWidgets.value = mapOf(communalItemRankEntry to communalWidgetItemEntry)
- whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
-
- installedProviders(listOf(stopwatchProviderInfo))
+ fakeProviders.value = mapOf(1 to providerInfoA)
val communalWidgets by collectLastValue(underTest.communalWidgets)
verify(communalWidgetDao).getWidgets()
assertThat(communalWidgets)
.containsExactly(
- CommunalWidgetContentModel(
+ CommunalWidgetContentModel.Available(
appWidgetId = communalWidgetItemEntry.widgetId,
providerInfo = providerInfoA,
priority = communalItemRankEntry.rank,
@@ -146,6 +148,102 @@
}
@Test
+ fun communalWidgets_widgetsWithoutMatchingProvidersAreSkipped() =
+ testScope.runTest {
+ // Set up 4 widgets, but widget 3 and 4 don't have matching providers
+ fakeWidgets.value =
+ mapOf(
+ CommunalItemRank(uid = 1L, rank = 1) to
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+ CommunalItemRank(uid = 2L, rank = 2) to
+ CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L),
+ CommunalItemRank(uid = 3L, rank = 3) to
+ CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L),
+ CommunalItemRank(uid = 4L, rank = 4) to
+ CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L),
+ )
+ fakeProviders.value =
+ mapOf(
+ 1 to providerInfoA,
+ 2 to providerInfoB,
+ )
+
+ // Expect to see only widget 1 and 2
+ val communalWidgets by collectLastValue(underTest.communalWidgets)
+ assertThat(communalWidgets)
+ .containsExactly(
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 1,
+ providerInfo = providerInfoA,
+ priority = 1,
+ ),
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 2,
+ providerInfo = providerInfoB,
+ priority = 2,
+ ),
+ )
+ }
+
+ @Test
+ fun communalWidgets_updatedWhenProvidersUpdate() =
+ testScope.runTest {
+ // Set up widgets and providers
+ fakeWidgets.value =
+ mapOf(
+ CommunalItemRank(uid = 1L, rank = 1) to
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+ CommunalItemRank(uid = 2L, rank = 2) to
+ CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L),
+ )
+ fakeProviders.value =
+ mapOf(
+ 1 to providerInfoA,
+ 2 to providerInfoB,
+ )
+
+ // Expect two widgets
+ val communalWidgets by collectLastValue(underTest.communalWidgets)
+ assertThat(communalWidgets).isNotNull()
+ assertThat(communalWidgets)
+ .containsExactly(
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 1,
+ providerInfo = providerInfoA,
+ priority = 1,
+ ),
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 2,
+ providerInfo = providerInfoB,
+ priority = 2,
+ ),
+ )
+
+ // Provider info updated for widget 1
+ fakeProviders.value =
+ mapOf(
+ 1 to providerInfoC,
+ 2 to providerInfoB,
+ )
+ runCurrent()
+
+ assertThat(communalWidgets)
+ .containsExactly(
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 1,
+ // Verify that provider info updated
+ providerInfo = providerInfoC,
+ priority = 1,
+ ),
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 2,
+ providerInfo = providerInfoB,
+ priority = 2,
+ ),
+ )
+ }
+
+ @Test
fun addWidget_allocateId_bindWidget_andAddToDb() =
testScope.runTest {
val provider = ComponentName("pkg_name", "cls_name")
@@ -434,9 +532,102 @@
assertThat(restoredWidget2.rank).isEqualTo(expectedWidget2.rank)
}
- private fun installedProviders(providers: List<AppWidgetProviderInfo>) {
- whenever(appWidgetManager.installedProviders).thenReturn(providers)
- }
+ @Test
+ fun pendingWidgets() =
+ testScope.runTest {
+ fakeWidgets.value =
+ mapOf(
+ CommunalItemRank(uid = 1L, rank = 1) to
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+ CommunalItemRank(uid = 2L, rank = 2) to
+ CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L),
+ )
+
+ // Widget 1 is installed
+ fakeProviders.value = mapOf(1 to providerInfoA)
+
+ // Widget 2 is pending install
+ val fakeIcon = mock<Bitmap>()
+ packageChangeRepository.setInstallSessions(
+ listOf(
+ PackageInstallSession(
+ sessionId = 1,
+ packageName = "pk_2",
+ icon = fakeIcon,
+ user = UserHandle.CURRENT,
+ )
+ )
+ )
+
+ val communalWidgets by collectLastValue(underTest.communalWidgets)
+ assertThat(communalWidgets)
+ .containsExactly(
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 1,
+ providerInfo = providerInfoA,
+ priority = 1,
+ ),
+ CommunalWidgetContentModel.Pending(
+ appWidgetId = 2,
+ priority = 2,
+ packageName = "pk_2",
+ icon = fakeIcon,
+ user = UserHandle.CURRENT,
+ ),
+ )
+ }
+
+ @Test
+ fun pendingWidgets_pendingWidgetBecomesAvailableAfterInstall() =
+ testScope.runTest {
+ fakeWidgets.value =
+ mapOf(
+ CommunalItemRank(uid = 1L, rank = 1) to
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+ )
+
+ // Widget 1 is pending install
+ val fakeIcon = mock<Bitmap>()
+ packageChangeRepository.setInstallSessions(
+ listOf(
+ PackageInstallSession(
+ sessionId = 1,
+ packageName = "pk_1",
+ icon = fakeIcon,
+ user = UserHandle.CURRENT,
+ )
+ )
+ )
+
+ val communalWidgets by collectLastValue(underTest.communalWidgets)
+ assertThat(communalWidgets)
+ .containsExactly(
+ CommunalWidgetContentModel.Pending(
+ appWidgetId = 1,
+ priority = 1,
+ packageName = "pk_1",
+ icon = fakeIcon,
+ user = UserHandle.CURRENT,
+ ),
+ )
+
+ // Package for widget 1 finished installing
+ packageChangeRepository.setInstallSessions(emptyList())
+
+ // Provider info for widget 1 becomes available
+ fakeProviders.value = mapOf(1 to providerInfoA)
+
+ runCurrent()
+
+ assertThat(communalWidgets)
+ .containsExactly(
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 1,
+ providerInfo = providerInfoA,
+ priority = 1,
+ ),
+ )
+ }
private fun setAppWidgetIds(ids: List<Int>) {
whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 456fb79..766798c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -23,6 +23,7 @@
import android.appwidget.AppWidgetProviderInfo
import android.content.Intent
import android.content.pm.UserInfo
+import android.graphics.Bitmap
import android.os.UserHandle
import android.os.UserManager
import android.os.userManager
@@ -871,7 +872,14 @@
// One widget is filtered out and the remaining two link to main user id.
assertThat(checkNotNull(widgetContent).size).isEqualTo(2)
widgetContent!!.forEachIndexed { _, model ->
- assertThat(model.providerInfo.profile?.identifier).isEqualTo(MAIN_USER_INFO.id)
+ assertThat(model is CommunalContentModel.WidgetContent.Widget).isTrue()
+ assertThat(
+ (model as CommunalContentModel.WidgetContent.Widget)
+ .providerInfo
+ .profile
+ ?.identifier
+ )
+ .isEqualTo(MAIN_USER_INFO.id)
}
}
@@ -1037,9 +1045,9 @@
runCurrent()
val widgetContent by collectLastValue(underTest.widgetContent)
- // Given three widgets, and one of them is associated with work profile.
+ // One available work widget, one pending work widget, and one regular available widget.
val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
- val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+ val widget2 = createPendingWidgetForUser(2, userId = USER_INFO_WORK.id)
val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
val widgets = listOf(widget1, widget2, widget3)
widgetRepository.setCommunalWidgets(widgets)
@@ -1049,11 +1057,9 @@
DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
)
- // Widget under work profile is filtered out and the remaining two link to main user id.
- assertThat(widgetContent).hasSize(2)
- widgetContent!!.forEach { model ->
- assertThat(model.providerInfo.profile?.identifier).isEqualTo(MAIN_USER_INFO.id)
- }
+ // Widgets under work profile are filtered out. Only the regular widget remains.
+ assertThat(widgetContent).hasSize(1)
+ assertThat(widgetContent?.get(0)?.appWidgetId).isEqualTo(3)
}
@Test
@@ -1076,7 +1082,7 @@
val widgetContent by collectLastValue(underTest.widgetContent)
// Given three widgets, and one of them is associated with work profile.
val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
- val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+ val widget2 = createPendingWidgetForUser(2, userId = USER_INFO_WORK.id)
val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
val widgets = listOf(widget1, widget2, widget3)
widgetRepository.setCommunalWidgets(widgets)
@@ -1086,10 +1092,11 @@
DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
)
- // Widget under work profile is available.
+ // Widgets under work profile are available.
assertThat(widgetContent).hasSize(3)
- assertThat(widgetContent!![0].providerInfo.profile?.identifier)
- .isEqualTo(USER_INFO_WORK.id)
+ assertThat(widgetContent?.get(0)?.appWidgetId).isEqualTo(1)
+ assertThat(widgetContent?.get(1)?.appWidgetId).isEqualTo(2)
+ assertThat(widgetContent?.get(2)?.appWidgetId).isEqualTo(3)
}
@Test
@@ -1182,8 +1189,11 @@
)
}
- private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
- mock<CommunalWidgetContentModel> {
+ private fun createWidgetForUser(
+ appWidgetId: Int,
+ userId: Int
+ ): CommunalWidgetContentModel.Available =
+ mock<CommunalWidgetContentModel.Available> {
whenever(this.appWidgetId).thenReturn(appWidgetId)
val providerInfo =
mock<AppWidgetProviderInfo>().apply {
@@ -1193,11 +1203,27 @@
whenever(this.providerInfo).thenReturn(providerInfo)
}
+ private fun createPendingWidgetForUser(
+ appWidgetId: Int,
+ priority: Int = 0,
+ packageName: String = "",
+ icon: Bitmap? = null,
+ userId: Int = 0,
+ ): CommunalWidgetContentModel.Pending {
+ return CommunalWidgetContentModel.Pending(
+ appWidgetId = appWidgetId,
+ priority = priority,
+ packageName = packageName,
+ icon = icon,
+ user = UserHandle(userId),
+ )
+ }
+
private fun createWidgetWithCategory(
appWidgetId: Int,
category: Int
): CommunalWidgetContentModel =
- mock<CommunalWidgetContentModel> {
+ mock<CommunalWidgetContentModel.Available> {
whenever(this.appWidgetId).thenReturn(appWidgetId)
val providerInfo = mock<AppWidgetProviderInfo>().apply { widgetCategory = category }
whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
index 02d927a..f9d5073 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
@@ -86,4 +86,26 @@
)
assertThat(isUmoOnCommunal).isFalse()
}
+
+ @Test
+ fun testIsUmoOnCommunalDuringTransitionBetweenOccludedAndGlanceableHub() =
+ testScope.runTest {
+ val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal)
+ assertThat(isUmoOnCommunal).isNull()
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope
+ )
+ assertThat(isUmoOnCommunal).isTrue()
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+
+ assertThat(isUmoOnCommunal).isFalse()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
index 89a4c04..b3a12a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
@@ -28,6 +28,8 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -36,6 +38,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -96,4 +101,137 @@
assertThat(appWidgetIdToRemove).isEqualTo(2)
}
+
+ @Test
+ fun observer_onHostStartListeningTriggeredWhileObserverActive() =
+ testScope.runTest {
+ // Observer added
+ val observer = mock<CommunalAppWidgetHost.Observer>()
+ underTest.addObserver(observer)
+ runCurrent()
+
+ // Verify callback triggered
+ verify(observer, never()).onHostStartListening()
+ underTest.startListening()
+ runCurrent()
+ verify(observer).onHostStartListening()
+
+ clearInvocations(observer)
+
+ // Observer removed
+ underTest.removeObserver(observer)
+ runCurrent()
+
+ // Verify callback not triggered
+ underTest.startListening()
+ runCurrent()
+ verify(observer, never()).onHostStartListening()
+ }
+
+ @Test
+ fun observer_onHostStopListeningTriggeredWhileObserverActive() =
+ testScope.runTest {
+ // Observer added
+ val observer = mock<CommunalAppWidgetHost.Observer>()
+ underTest.addObserver(observer)
+ runCurrent()
+
+ // Verify callback triggered
+ verify(observer, never()).onHostStopListening()
+ underTest.stopListening()
+ runCurrent()
+ verify(observer).onHostStopListening()
+
+ clearInvocations(observer)
+
+ // Observer removed
+ underTest.removeObserver(observer)
+ runCurrent()
+
+ // Verify callback not triggered
+ underTest.stopListening()
+ runCurrent()
+ verify(observer, never()).onHostStopListening()
+ }
+
+ @Test
+ fun observer_onAllocateAppWidgetIdTriggeredWhileObserverActive() =
+ testScope.runTest {
+ // Observer added
+ val observer = mock<CommunalAppWidgetHost.Observer>()
+ underTest.addObserver(observer)
+ runCurrent()
+
+ // Verify callback triggered
+ verify(observer, never()).onAllocateAppWidgetId(any())
+ val id = underTest.allocateAppWidgetId()
+ runCurrent()
+ verify(observer).onAllocateAppWidgetId(eq(id))
+
+ clearInvocations(observer)
+
+ // Observer removed
+ underTest.removeObserver(observer)
+ runCurrent()
+
+ // Verify callback not triggered
+ underTest.allocateAppWidgetId()
+ runCurrent()
+ verify(observer, never()).onAllocateAppWidgetId(any())
+ }
+
+ @Test
+ fun observer_onDeleteAppWidgetIdTriggeredWhileObserverActive() =
+ testScope.runTest {
+ // Observer added
+ val observer = mock<CommunalAppWidgetHost.Observer>()
+ underTest.addObserver(observer)
+ runCurrent()
+
+ // Verify callback triggered
+ verify(observer, never()).onDeleteAppWidgetId(any())
+ underTest.deleteAppWidgetId(1)
+ runCurrent()
+ verify(observer).onDeleteAppWidgetId(eq(1))
+
+ clearInvocations(observer)
+
+ // Observer removed
+ underTest.removeObserver(observer)
+ runCurrent()
+
+ // Verify callback not triggered
+ underTest.deleteAppWidgetId(2)
+ runCurrent()
+ verify(observer, never()).onDeleteAppWidgetId(any())
+ }
+
+ @Test
+ fun observer_multipleObservers() =
+ testScope.runTest {
+ // Set up two observers
+ val observer1 = mock<CommunalAppWidgetHost.Observer>()
+ val observer2 = mock<CommunalAppWidgetHost.Observer>()
+ underTest.addObserver(observer1)
+ underTest.addObserver(observer2)
+ runCurrent()
+
+ // Verify both observers triggered
+ verify(observer1, never()).onHostStartListening()
+ verify(observer2, never()).onHostStartListening()
+ underTest.startListening()
+ runCurrent()
+ verify(observer1).onHostStartListening()
+ verify(observer2).onHostStartListening()
+
+ // Observer 1 removed
+ underTest.removeObserver(observer1)
+ runCurrent()
+
+ // Verify only observer 2 is triggered
+ underTest.stopListening()
+ runCurrent()
+ verify(observer2).onHostStopListening()
+ verify(observer1, never()).onHostStopListening()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 9aebc30..6ca04df 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -123,12 +123,12 @@
// Widgets available.
val widgets =
listOf(
- CommunalWidgetContentModel(
+ CommunalWidgetContentModel.Available(
appWidgetId = 0,
priority = 30,
providerInfo = providerInfo,
),
- CommunalWidgetContentModel(
+ CommunalWidgetContentModel.Available(
appWidgetId = 1,
priority = 20,
providerInfo = providerInfo,
@@ -177,12 +177,12 @@
// Widgets available.
val widgets =
listOf(
- CommunalWidgetContentModel(
+ CommunalWidgetContentModel.Available(
appWidgetId = 0,
priority = 30,
providerInfo = providerInfo,
),
- CommunalWidgetContentModel(
+ CommunalWidgetContentModel.Available(
appWidgetId = 1,
priority = 20,
providerInfo = providerInfo,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 569116c..1f8cb8a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -90,7 +90,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
@Mock private lateinit var user: UserInfo
@Mock private lateinit var providerInfo: AppWidgetProviderInfo
@@ -111,7 +111,7 @@
private lateinit var underTest: CommunalViewModel
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
@@ -186,12 +186,12 @@
// Widgets available.
val widgets =
listOf(
- CommunalWidgetContentModel(
+ CommunalWidgetContentModel.Available(
appWidgetId = 0,
priority = 30,
providerInfo = providerInfo,
),
- CommunalWidgetContentModel(
+ CommunalWidgetContentModel.Available(
appWidgetId = 1,
priority = 20,
providerInfo = providerInfo,
@@ -245,7 +245,7 @@
widgetRepository.setCommunalWidgets(
listOf(
- CommunalWidgetContentModel(
+ CommunalWidgetContentModel.Available(
appWidgetId = 1,
priority = 1,
providerInfo = providerInfo,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index 6cae5d3..3d2eabf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -18,6 +18,7 @@
import android.appwidget.AppWidgetProviderInfo
import android.content.pm.UserInfo
+import android.graphics.Bitmap
import android.os.UserHandle
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -60,6 +61,7 @@
private val kosmos = testKosmos()
@Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
+ @Mock private lateinit var communalWidgetHost: CommunalWidgetHost
private lateinit var appWidgetIdToRemove: MutableSharedFlow<Int>
@@ -78,6 +80,7 @@
underTest =
CommunalAppWidgetHostStartable(
appWidgetHost,
+ communalWidgetHost,
kosmos.communalInteractor,
kosmos.fakeUserTracker,
kosmos.applicationCoroutineScope,
@@ -143,16 +146,44 @@
}
@Test
+ fun observeHostWhenCommunalIsAvailable() =
+ with(kosmos) {
+ testScope.runTest {
+ setCommunalAvailable(true)
+ communalInteractor.setEditModeOpen(false)
+ verify(communalWidgetHost, never()).startObservingHost()
+ verify(communalWidgetHost, never()).stopObservingHost()
+
+ underTest.start()
+ runCurrent()
+
+ verify(communalWidgetHost).startObservingHost()
+ verify(communalWidgetHost, never()).stopObservingHost()
+
+ setCommunalAvailable(false)
+ runCurrent()
+
+ verify(communalWidgetHost).stopObservingHost()
+ }
+ }
+
+ @Test
fun removeAppWidgetReportedByHost() =
with(kosmos) {
testScope.runTest {
// Set up communal widgets
val widget1 =
- mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(1) }
+ mock<CommunalWidgetContentModel.Available> {
+ whenever(this.appWidgetId).thenReturn(1)
+ }
val widget2 =
- mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(2) }
+ mock<CommunalWidgetContentModel.Available> {
+ whenever(this.appWidgetId).thenReturn(2)
+ }
val widget3 =
- mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(3) }
+ mock<CommunalWidgetContentModel.Available> {
+ whenever(this.appWidgetId).thenReturn(3)
+ }
fakeCommunalWidgetRepository.setCommunalWidgets(listOf(widget1, widget2, widget3))
underTest.start()
@@ -184,8 +215,9 @@
userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK),
selectedUserIndex = 0,
)
+ // One work widget, one pending work widget, and one personal widget.
val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
- val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+ val widget2 = createPendingWidgetForUser(2, USER_INFO_WORK.id)
val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
val widgets = listOf(widget1, widget2, widget3)
fakeCommunalWidgetRepository.setCommunalWidgets(widgets)
@@ -209,8 +241,8 @@
fakeKeyguardRepository.setKeyguardShowing(true)
runCurrent()
- // Widget created for work profile is removed.
- assertThat(communalWidgets).containsExactly(widget2, widget3)
+ // Both work widgets are removed.
+ assertThat(communalWidgets).containsExactly(widget3)
}
}
@@ -227,14 +259,32 @@
)
}
- private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
- mock<CommunalWidgetContentModel> {
+ private fun createWidgetForUser(
+ appWidgetId: Int,
+ userId: Int
+ ): CommunalWidgetContentModel.Available =
+ mock<CommunalWidgetContentModel.Available> {
whenever(this.appWidgetId).thenReturn(appWidgetId)
val providerInfo = mock<AppWidgetProviderInfo>()
whenever(providerInfo.profile).thenReturn(UserHandle(userId))
whenever(this.providerInfo).thenReturn(providerInfo)
}
+ private fun createPendingWidgetForUser(
+ appWidgetId: Int,
+ userId: Int,
+ priority: Int = 0,
+ packageName: String = "",
+ icon: Bitmap? = null,
+ ): CommunalWidgetContentModel.Pending =
+ CommunalWidgetContentModel.Pending(
+ appWidgetId = appWidgetId,
+ priority = priority,
+ packageName = packageName,
+ icon = icon,
+ user = UserHandle(userId),
+ )
+
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
index 88f5e1b..054e516 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.widgets
+import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
@@ -26,6 +27,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.testKosmos
@@ -40,6 +43,7 @@
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -47,6 +51,8 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -59,6 +65,10 @@
@Mock private lateinit var appWidgetManager: AppWidgetManager
@Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
+ @Mock private lateinit var providerInfo1: AppWidgetProviderInfo
+ @Mock private lateinit var providerInfo2: AppWidgetProviderInfo
+ @Mock private lateinit var providerInfo3: AppWidgetProviderInfo
+
private val selectedUserInteractor: SelectedUserInteractor by lazy {
kosmos.selectedUserInteractor
}
@@ -69,8 +79,19 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(
+ appWidgetManager.bindAppWidgetIdIfAllowed(
+ any<Int>(),
+ any<UserHandle>(),
+ any<ComponentName>(),
+ any<Bundle>()
+ )
+ )
+ .thenReturn(true)
+
underTest =
CommunalWidgetHost(
+ kosmos.applicationCoroutineScope,
Optional.of(appWidgetManager),
appWidgetHost,
selectedUserInteractor,
@@ -89,15 +110,6 @@
val user = UserHandle(checkNotNull(userId))
whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId)
- whenever(
- appWidgetManager.bindAppWidgetIdIfAllowed(
- any<Int>(),
- any<UserHandle>(),
- any<ComponentName>(),
- any<Bundle>(),
- )
- )
- .thenReturn(true)
// bind the widget with the current user when no user is explicitly set
val result = underTest.allocateIdAndBindWidget(provider)
@@ -121,15 +133,6 @@
val user = UserHandle(0)
whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId)
- whenever(
- appWidgetManager.bindAppWidgetIdIfAllowed(
- any<Int>(),
- any<UserHandle>(),
- any<ComponentName>(),
- any<Bundle>()
- )
- )
- .thenReturn(true)
// provider and user handle are both set
val result = underTest.allocateIdAndBindWidget(provider, user)
@@ -172,6 +175,261 @@
assertThat(result).isNull()
}
+ @Test
+ fun listener_exactlyOneListenerRegisteredForEachWidgetWhenHostStartListening() =
+ testScope.runTest {
+ // 3 widgets registered with the host
+ whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2, 3))
+
+ underTest.startObservingHost()
+ runCurrent()
+
+ // Make sure no listener is set before host starts listening
+ verify(appWidgetHost, never()).setListener(any(), any())
+
+ // Host starts listening
+ val observer =
+ withArgCaptor<CommunalAppWidgetHost.Observer> {
+ verify(appWidgetHost).addObserver(capture())
+ }
+ observer.onHostStartListening()
+ runCurrent()
+
+ // Verify a listener is set for each widget
+ verify(appWidgetHost, times(3)).setListener(any(), any())
+ verify(appWidgetHost).setListener(eq(1), any())
+ verify(appWidgetHost).setListener(eq(2), any())
+ verify(appWidgetHost).setListener(eq(3), any())
+ }
+
+ @Test
+ fun listener_listenersRemovedWhenHostStopListening() =
+ testScope.runTest {
+ // 3 widgets registered with the host
+ whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2, 3))
+
+ underTest.startObservingHost()
+ runCurrent()
+
+ // Host starts listening
+ val observer =
+ withArgCaptor<CommunalAppWidgetHost.Observer> {
+ verify(appWidgetHost).addObserver(capture())
+ }
+ observer.onHostStartListening()
+ runCurrent()
+
+ // Verify none of the listener is removed before host stop listening
+ verify(appWidgetHost, never()).removeListener(any())
+
+ observer.onHostStopListening()
+
+ // Verify each listener is removed
+ verify(appWidgetHost, times(3)).removeListener(any())
+ verify(appWidgetHost).removeListener(eq(1))
+ verify(appWidgetHost).removeListener(eq(2))
+ verify(appWidgetHost).removeListener(eq(3))
+ }
+
+ @Test
+ fun listener_addNewListenerWhenNewIdAllocated() =
+ testScope.runTest {
+ whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf())
+ val observer = start()
+
+ // Verify no listener is set before a new app widget id is allocated
+ verify(appWidgetHost, never()).setListener(any(), any())
+
+ // Allocate an app widget id
+ observer.onAllocateAppWidgetId(1)
+
+ // Verify new listener set for that app widget id
+ verify(appWidgetHost).setListener(eq(1), any())
+ }
+
+ @Test
+ fun listener_removeListenerWhenWidgetDeleted() =
+ testScope.runTest {
+ whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1))
+ val observer = start()
+
+ // Verify listener not removed before widget deleted
+ verify(appWidgetHost, never()).removeListener(eq(1))
+
+ // Widget deleted
+ observer.onDeleteAppWidgetId(1)
+
+ // Verify listener removed for that widget
+ verify(appWidgetHost).removeListener(eq(1))
+ }
+
+ @Test
+ fun providerInfo_populatesWhenStartListening() =
+ testScope.runTest {
+ whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2))
+ whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1)
+ whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2)
+
+ val providerInfoValues by collectValues(underTest.appWidgetProviders)
+
+ // Assert that the map is empty before host starts listening
+ assertThat(providerInfoValues).hasSize(1)
+ assertThat(providerInfoValues[0]).isEmpty()
+
+ start()
+ runCurrent()
+
+ // Assert that the provider info map is populated after host started listening, and that
+ // all providers are emitted at once
+ assertThat(providerInfoValues).hasSize(2)
+ assertThat(providerInfoValues[1])
+ .containsExactlyEntriesIn(
+ mapOf(
+ Pair(1, providerInfo1),
+ Pair(2, providerInfo2),
+ )
+ )
+ }
+
+ @Test
+ fun providerInfo_clearsWhenStopListening() =
+ testScope.runTest {
+ whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2))
+ whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1)
+ whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2)
+
+ val observer = start()
+ runCurrent()
+
+ // Assert that the provider info map is populated
+ val providerInfo by collectLastValue(underTest.appWidgetProviders)
+ assertThat(providerInfo)
+ .containsExactlyEntriesIn(
+ mapOf(
+ Pair(1, providerInfo1),
+ Pair(2, providerInfo2),
+ )
+ )
+
+ // Host stop listening
+ observer.onHostStopListening()
+
+ // Assert that the provider info map is cleared
+ assertThat(providerInfo).isEmpty()
+ }
+
+ @Test
+ fun providerInfo_onUpdate() =
+ testScope.runTest {
+ whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2))
+ whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1)
+ whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2)
+
+ val providerInfo by collectLastValue(underTest.appWidgetProviders)
+
+ start()
+ runCurrent()
+
+ // Assert that the provider info map is populated
+ assertThat(providerInfo)
+ .containsExactlyEntriesIn(
+ mapOf(
+ Pair(1, providerInfo1),
+ Pair(2, providerInfo2),
+ )
+ )
+
+ // Provider info for widget 1 updated
+ val listener =
+ withArgCaptor<AppWidgetHost.AppWidgetHostListener> {
+ verify(appWidgetHost).setListener(eq(1), capture())
+ }
+ listener.onUpdateProviderInfo(providerInfo3)
+ runCurrent()
+
+ // Assert that the update is reflected in the flow
+ assertThat(providerInfo)
+ .containsExactlyEntriesIn(
+ mapOf(
+ Pair(1, providerInfo3),
+ Pair(2, providerInfo2),
+ )
+ )
+ }
+
+ @Test
+ fun providerInfo_updateWhenANewWidgetIsBound() =
+ testScope.runTest {
+ whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2))
+ whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1)
+ whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2)
+
+ val providerInfo by collectLastValue(underTest.appWidgetProviders)
+
+ start()
+ runCurrent()
+
+ // Assert that the provider info map is populated
+ assertThat(providerInfo)
+ .containsExactlyEntriesIn(
+ mapOf(
+ Pair(1, providerInfo1),
+ Pair(2, providerInfo2),
+ )
+ )
+
+ // Bind a new widget
+ whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(3)
+ whenever(appWidgetManager.getAppWidgetInfo(3)).thenReturn(providerInfo3)
+ val newWidgetComponentName = ComponentName.unflattenFromString("pkg_new/cls_new")!!
+ underTest.allocateIdAndBindWidget(newWidgetComponentName)
+ runCurrent()
+
+ // Assert that the new provider is reflected in the flow
+ assertThat(providerInfo)
+ .containsExactlyEntriesIn(
+ mapOf(
+ Pair(1, providerInfo1),
+ Pair(2, providerInfo2),
+ Pair(3, providerInfo3),
+ )
+ )
+ }
+
+ @Test
+ fun providerInfo_updateWhenWidgetRemoved() =
+ testScope.runTest {
+ whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2))
+ whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1)
+ whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2)
+
+ val providerInfo by collectLastValue(underTest.appWidgetProviders)
+
+ val observer = start()
+ runCurrent()
+
+ // Assert that the provider info map is populated
+ assertThat(providerInfo)
+ .containsExactlyEntriesIn(
+ mapOf(
+ Pair(1, providerInfo1),
+ Pair(2, providerInfo2),
+ )
+ )
+
+ // Remove widget 1
+ observer.onDeleteAppWidgetId(1)
+ runCurrent()
+
+ // Assert that provider info for widget 1 is removed
+ assertThat(providerInfo)
+ .containsExactlyEntriesIn(
+ mapOf(
+ Pair(2, providerInfo2),
+ )
+ )
+ }
+
private fun selectUser() {
kosmos.fakeUserRepository.selectedUser.value =
SelectedUserModel(
@@ -179,4 +437,16 @@
selectionStatus = SelectionStatus.SELECTION_COMPLETE
)
}
+
+ private fun TestScope.start(): CommunalAppWidgetHost.Observer {
+ underTest.startObservingHost()
+ runCurrent()
+
+ val observer =
+ withArgCaptor<CommunalAppWidgetHost.Observer> {
+ verify(appWidgetHost).addObserver(capture())
+ }
+ observer.onHostStartListening()
+ return observer
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 2b3f40f..e3dd9ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -170,7 +170,7 @@
@Test
public void testBurnInProtectionStopsWhenContentViewDetached() {
mController.onViewDetached();
- verify(mHandler).removeCallbacks(any(Runnable.class));
+ verify(mHandler).removeCallbacksAndMessages(null);
}
@Test
@@ -281,4 +281,16 @@
verify(mAnimationsController).cancelAnimations();
}
+
+ @Test
+ public void onViewAttached_addsScrimExpansionCallback() {
+ mController.onViewAttached();
+ verify(mBouncerlessScrimController).addCallback(any());
+ }
+
+ @Test
+ public void onViewDetached_removesScrimExpansionCallback() {
+ mController.onViewDetached();
+ verify(mBouncerlessScrimController).removeCallback(any());
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
index feb7298..7292985 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
@@ -33,7 +33,6 @@
import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY
import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.fakeUserTracker
@@ -64,7 +63,7 @@
private val kosmos = testKosmos()
- private lateinit var underTest: HomeControlsComponentInteractor
+ private val underTest by lazy { kosmos.homeControlsComponentInteractor }
@Before
fun setUp() =
@@ -73,8 +72,7 @@
fakeUserRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
whenever(controlsComponent.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
-
- underTest = homeControlsComponentInteractor
+ Unit
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index 3b6f6a2..f31eb7f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -58,7 +58,7 @@
@ExperimentalCoroutinesApi
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class UdfpsKeyguardInteractorTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class UdfpsKeyguardInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
val keyguardRepository = kosmos.fakeKeyguardRepository
@@ -88,7 +88,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
index f52c66e..cde703b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
@@ -43,7 +43,7 @@
@ExperimentalCoroutinesApi
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
val repository = kosmos.fakeKeyguardTransitionRepository
@@ -60,7 +60,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
index fee18dd..d632936 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -16,13 +16,15 @@
package com.android.systemui.keyguard.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -31,29 +33,25 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.flow.MutableStateFlow
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.Mock
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BouncerToGoneFlowsTest : SysuiTestCase() {
- @Mock private lateinit var shadeInteractor: ShadeInteractor
-
- private val shadeExpansionStateFlow = MutableStateFlow(0.1f)
+@RunWith(ParameterizedAndroidJunit4::class)
+class BouncerToGoneFlowsTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
@@ -61,16 +59,31 @@
}
private val testScope = kosmos.testScope
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val shadeRepository = kosmos.shadeRepository
private val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
private val primaryBouncerInteractor = kosmos.mockPrimaryBouncerInteractor
- private val underTest = kosmos.bouncerToGoneFlows
+
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+ private lateinit var underTest: BouncerToGoneFlows
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(false)
+ underTest = kosmos.bouncerToGoneFlows
}
@Test
@@ -79,7 +92,7 @@
val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
runCurrent()
- shadeRepository.setLockscreenShadeExpansion(1f)
+ shadeTestUtil.setLockscreenShadeExpansion(1f)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
keyguardTransitionRepository.sendTransitionSteps(
@@ -99,12 +112,13 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun scrimAlpha_runDimissFromKeyguard_shadeNotExpanded() =
testScope.runTest {
val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
runCurrent()
- shadeRepository.setLockscreenShadeExpansion(0f)
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 2e1765a..838b2a7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -60,7 +60,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardRootViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
@@ -75,7 +75,6 @@
private val viewState = ViewStateAccessor()
- // add to init block
companion object {
@JvmStatic
@Parameters(name = "{0}")
@@ -85,7 +84,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index ec2cb04..de4b999 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -47,7 +47,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenContentViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos: Kosmos = testKosmos()
@@ -62,7 +62,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
index e3ae3ba..bc381f2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
@@ -49,7 +49,7 @@
@ExperimentalCoroutinesApi
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) }
@@ -73,7 +73,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index adeb395..9337793 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -51,7 +51,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
@@ -73,7 +73,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index f8da74f..6ce7e88 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -54,7 +54,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
@@ -76,7 +76,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index d5df159..58c6817 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -46,7 +46,7 @@
@ExperimentalCoroutinesApi
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization?) :
+class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization) :
SysuiTestCase() {
private val kosmos =
testKosmos().apply {
@@ -67,7 +67,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt
new file mode 100644
index 0000000..8d1aa73
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.content.ServiceConnection
+import android.content.applicationContext
+import android.content.packageManager
+import android.os.Binder
+import android.os.Handler
+import android.os.RemoteException
+import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
+import android.service.quicksettings.Tile
+import android.testing.TestableContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.fakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class CloseShadeRightAfterClickTestB339290820 : SysuiTestCase() {
+
+ private val testableContext: TestableContext
+ private val bindDelayExecutor: FakeExecutor
+ private val kosmos =
+ testKosmos().apply {
+ testableContext = testCase.context
+ bindDelayExecutor = FakeExecutor(fakeSystemClock)
+ testableContext.setMockPackageManager(packageManager)
+ customTileSpec = TileSpec.create(testComponentName)
+ applicationContext = ContextWrapperDelayedBind(testableContext, bindDelayExecutor)
+ }
+
+ @Before
+ fun setUp() {
+ kosmos.apply {
+ whenever(packageManager.getPackageUidAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(Binder.getCallingUid())
+ packageManagerAdapterFacade.setIsActive(true)
+ testableContext.addMockService(testComponentName, iQSTileService.asBinder())
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ fun testStopListeningShortlyAfterClick_clickIsSent() {
+ with(kosmos) {
+ val tile = FakeCustomTileInterface(tileServices)
+ // Flush any bind from startup
+ FakeExecutor.exhaustExecutors(fakeExecutor, bindDelayExecutor)
+
+ // Open QS
+ tile.setListening(true)
+ fakeExecutor.runAllReady()
+ tile.click()
+ fakeExecutor.runAllReady()
+
+ // No clicks yet because the latch is preventing the bind
+ assertThat(iQSTileService.clicks).isEmpty()
+
+ // Close QS
+ tile.setListening(false)
+ fakeExecutor.runAllReady()
+ // And finally bind
+ FakeExecutor.exhaustExecutors(fakeExecutor, bindDelayExecutor)
+
+ assertThat(iQSTileService.clicks).containsExactly(tile.token)
+ }
+ }
+}
+
+private val testComponentName = ComponentName("pkg", "srv")
+
+// This is a fake `CustomTile` that implements what we need for the test. Mainly setListening and
+// click
+private class FakeCustomTileInterface(tileServices: TileServices) : CustomTileInterface {
+ override val user: Int
+ get() = 0
+ override val qsTile: Tile = Tile()
+ override val component: ComponentName = testComponentName
+ private var listening = false
+ private val serviceManager = tileServices.getTileWrapper(this)
+ private val serviceInterface = serviceManager.tileService
+
+ val token = Binder()
+
+ override fun getTileSpec(): String {
+ return CustomTile.toSpec(component)
+ }
+
+ override fun refreshState() {}
+
+ override fun updateTileState(tile: Tile, uid: Int) {}
+
+ override fun onDialogShown() {}
+
+ override fun onDialogHidden() {}
+
+ override fun startActivityAndCollapse(pendingIntent: PendingIntent) {}
+
+ override fun startUnlockAndRun() {}
+
+ fun setListening(listening: Boolean) {
+ if (listening == this.listening) return
+ this.listening = listening
+
+ try {
+ if (listening) {
+ if (!serviceManager.isActiveTile) {
+ serviceManager.setBindRequested(true)
+ serviceInterface.onStartListening()
+ }
+ } else {
+ serviceInterface.onStopListening()
+ serviceManager.setBindRequested(false)
+ }
+ } catch (e: RemoteException) {
+ // Called through wrapper, won't happen here.
+ }
+ }
+
+ fun click() {
+ try {
+ if (serviceManager.isActiveTile) {
+ serviceManager.setBindRequested(true)
+ serviceInterface.onStartListening()
+ }
+ serviceInterface.onClick(token)
+ } catch (e: RemoteException) {
+ // Called through wrapper, won't happen here.
+ }
+ }
+}
+
+private class ContextWrapperDelayedBind(
+ val context: Context,
+ val executor: FakeExecutor,
+) : ContextWrapper(context) {
+ override fun bindServiceAsUser(
+ service: Intent,
+ conn: ServiceConnection,
+ flags: Int,
+ user: UserHandle
+ ): Boolean {
+ executor.execute { super.bindServiceAsUser(service, conn, flags, user) }
+ return true
+ }
+
+ override fun bindServiceAsUser(
+ service: Intent,
+ conn: ServiceConnection,
+ flags: BindServiceFlags,
+ user: UserHandle
+ ): Boolean {
+ executor.execute { super.bindServiceAsUser(service, conn, flags, user) }
+ return true
+ }
+
+ override fun bindServiceAsUser(
+ service: Intent?,
+ conn: ServiceConnection?,
+ flags: Int,
+ handler: Handler?,
+ user: UserHandle?
+ ): Boolean {
+ executor.execute { super.bindServiceAsUser(service, conn, flags, handler, user) }
+ return true
+ }
+
+ override fun bindServiceAsUser(
+ service: Intent,
+ conn: ServiceConnection,
+ flags: BindServiceFlags,
+ handler: Handler,
+ user: UserHandle
+ ): Boolean {
+ executor.execute { super.bindServiceAsUser(service, conn, flags, handler, user) }
+ return true
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
index da60c18..dfc004a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
@@ -30,10 +30,10 @@
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
import com.android.systemui.qs.tiles.impl.custom.commons.copy
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
@@ -47,11 +47,11 @@
@OptIn(ExperimentalCoroutinesApi::class)
class CustomTileRepositoryTest : SysuiTestCase() {
- private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
+ private val kosmos = Kosmos().apply { customTileSpec = TileSpec.create(TEST_COMPONENT) }
private val underTest: CustomTileRepository =
with(kosmos) {
CustomTileRepositoryImpl(
- tileSpec,
+ customTileSpec,
customTileStatePersister,
packageManagerAdapterFacade.packageManagerAdapter,
testScope.testScheduler,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
index a5c5544..a29289a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
@@ -38,8 +38,8 @@
import com.android.systemui.qs.tiles.impl.custom.customTilePackagesUpdatesRepository
import com.android.systemui.qs.tiles.impl.custom.customTileRepository
import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.user.data.repository.userRepository
@@ -60,12 +60,12 @@
private val kosmos =
testKosmos().apply {
componentName = TEST_COMPONENT
- tileSpec = TileSpec.create(componentName)
+ customTileSpec = TileSpec.create(componentName)
}
private val underTest =
with(kosmos) {
CustomTileDataInteractor(
- tileSpec = tileSpec,
+ tileSpec = customTileSpec,
defaultsRepository = customTileDefaultsRepository,
serviceInteractor = customTileServiceInteractor,
customTileInteractor = customTileInteractor,
@@ -180,7 +180,7 @@
setup()
customTileDefaultsRepository.putDefaults(
TEST_USER_1.userHandle,
- tileSpec.componentName,
+ customTileSpec.componentName,
CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label),
)
@@ -198,7 +198,7 @@
setup()
customTileDefaultsRepository.putDefaults(
TEST_USER_1.userHandle,
- tileSpec.componentName,
+ customTileSpec.componentName,
CustomTileDefaults.Error,
)
@@ -216,7 +216,7 @@
setup()
customTileDefaultsRepository.putDefaults(
TEST_USER_2.userHandle,
- tileSpec.componentName,
+ customTileSpec.componentName,
CustomTileDefaults.Error,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
index 9546a32..33299d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -31,9 +31,9 @@
import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
import com.android.systemui.qs.tiles.impl.custom.customTileDefaultsRepository
import com.android.systemui.qs.tiles.impl.custom.customTileRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,12 +50,12 @@
@OptIn(ExperimentalCoroutinesApi::class)
class CustomTileInteractorTest : SysuiTestCase() {
- private val kosmos = testKosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
+ private val kosmos = testKosmos().apply { customTileSpec = TileSpec.create(TEST_COMPONENT) }
private val underTest: CustomTileInteractor =
with(kosmos) {
CustomTileInteractor(
- tileSpec = tileSpec,
+ tileSpec = customTileSpec,
defaultsRepository = customTileDefaultsRepository,
customTileRepository = customTileRepository,
tileScope = testScope.backgroundScope,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
index a2127a4..3972938 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
@@ -33,9 +33,9 @@
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject.Companion.assertThat
import com.android.systemui.qs.tiles.impl.custom.customTileQsTileConfig
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
import com.android.systemui.qs.tiles.impl.custom.domain.CustomTileMapper
import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -51,7 +51,8 @@
class CustomTileMapperTest : SysuiTestCase() {
private val uriGrantsManager: IUriGrantsManager = mock {}
- private val kosmos = testKosmos().apply { tileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
+ private val kosmos =
+ testKosmos().apply { customTileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
private val underTest by lazy {
CustomTileMapper(
context = mock { whenever(createContextAsUser(any(), any())).thenReturn(context) },
@@ -202,7 +203,7 @@
) =
CustomTileDataModel(
UserHandle.of(1),
- tileSpec.componentName,
+ customTileSpec.componentName,
Tile().apply {
state = tileState
label = "test label"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
index c709f16..72e5766 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
@@ -44,9 +44,9 @@
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick
import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.any
@@ -68,7 +68,7 @@
private val kosmos =
testKosmos().apply {
componentName = TEST_COMPONENT
- tileSpec = TileSpec.create(componentName)
+ customTileSpec = TileSpec.create(componentName)
testCase = this@CustomTileUserActionInteractorTest
}
@@ -79,7 +79,7 @@
mock {
whenever(packageManager).thenReturn(packageManagerFacade.packageManager)
},
- tileSpec = tileSpec,
+ tileSpec = customTileSpec,
qsTileLogger = qsTileLogger,
windowManager = windowManagerFacade.windowManager,
displayTracker = mock {},
@@ -227,7 +227,7 @@
private fun pendingIntent(): PendingIntent = mock { whenever(isActivity).thenReturn(true) }
private fun Kosmos.customTileModel(
- componentName: ComponentName = tileSpec.componentName,
+ componentName: ComponentName = customTileSpec.componentName,
activityLaunchForClick: PendingIntent? = null,
tileState: Int = 111,
) =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt
new file mode 100644
index 0000000..a0aa2d4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.domain.interactor
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.time.DateFormatUtil
+import com.android.systemui.utils.leaks.FakeLocationController
+import com.google.common.truth.Truth.assertThat
+import java.time.LocalTime
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayTileDataInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testUser = UserHandle.of(1)!!
+ private val testStartTime = LocalTime.MIDNIGHT
+ private val testEndTime = LocalTime.NOON
+ private val colorDisplayManager =
+ mock<ColorDisplayManager> {
+ whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED)
+ whenever(isNightDisplayActivated).thenReturn(false)
+ whenever(nightDisplayCustomStartTime).thenReturn(testStartTime)
+ whenever(nightDisplayCustomEndTime).thenReturn(testEndTime)
+ }
+ private val locationController = FakeLocationController(LeakCheck())
+ private val nightDisplayListener = mock<NightDisplayListener>()
+ private val listenerBuilder =
+ mock<NightDisplayListenerModule.Builder> {
+ whenever(setUser(anyInt())).thenReturn(this)
+ whenever(build()).thenReturn(nightDisplayListener)
+ }
+ private val globalSettings = kosmos.fakeGlobalSettings
+ private val secureSettings = kosmos.fakeSettings
+ private val dateFormatUtil = mock<DateFormatUtil> { whenever(is24HourFormat).thenReturn(false) }
+ private val testDispatcher = StandardTestDispatcher()
+ private val scope = TestScope(testDispatcher)
+ private val userScopedColorDisplayManager =
+ mock<UserScopedService<ColorDisplayManager>> {
+ whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager)
+ }
+ private val nightDisplayRepository =
+ NightDisplayRepository(
+ testDispatcher,
+ scope.backgroundScope,
+ globalSettings,
+ secureSettings,
+ listenerBuilder,
+ userScopedColorDisplayManager,
+ locationController,
+ )
+
+ private val underTest: NightDisplayTileDataInteractor =
+ NightDisplayTileDataInteractor(context, dateFormatUtil, nightDisplayRepository)
+
+ @Test
+ fun availability_matchesColorDisplayManager() = runTest {
+ val availability by collectLastValue(underTest.availability(testUser))
+
+ val expectedAvailability = ColorDisplayManager.isNightDisplayAvailable(context)
+ assertThat(availability).isEqualTo(expectedAvailability)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..adc8bcb
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.domain.interactor
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.utils.leaks.FakeLocationController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayTileUserActionInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
+ private val testUser = UserHandle.of(1)
+ private val colorDisplayManager =
+ mock<ColorDisplayManager> {
+ whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED)
+ whenever(isNightDisplayActivated).thenReturn(false)
+ }
+ private val locationController = FakeLocationController(LeakCheck())
+ private val nightDisplayListener = mock<NightDisplayListener>()
+ private val listenerBuilder =
+ mock<NightDisplayListenerModule.Builder> {
+ whenever(setUser(ArgumentMatchers.anyInt())).thenReturn(this)
+ whenever(build()).thenReturn(nightDisplayListener)
+ }
+ private val globalSettings = kosmos.fakeGlobalSettings
+ private val secureSettings = kosmos.fakeSettings
+ private val testDispatcher = StandardTestDispatcher()
+ private val scope = TestScope(testDispatcher)
+ private val userScopedColorDisplayManager =
+ mock<UserScopedService<ColorDisplayManager>> {
+ whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager)
+ }
+ private val nightDisplayRepository =
+ NightDisplayRepository(
+ testDispatcher,
+ scope.backgroundScope,
+ globalSettings,
+ secureSettings,
+ listenerBuilder,
+ userScopedColorDisplayManager,
+ locationController,
+ )
+
+ private val underTest =
+ NightDisplayTileUserActionInteractor(
+ nightDisplayRepository,
+ qsTileIntentUserActionHandler,
+ kosmos.qsTileLogger
+ )
+
+ @Test
+ fun handleClick_inactive_activates() =
+ scope.runTest {
+ val startingModel = NightDisplayTileModel.AutoModeOff(false, false)
+
+ underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+ verify(colorDisplayManager).setNightDisplayActivated(true)
+ }
+
+ @Test
+ fun handleClick_active_disables() =
+ scope.runTest {
+ val startingModel = NightDisplayTileModel.AutoModeOff(true, false)
+
+ underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+ verify(colorDisplayManager).setNightDisplayActivated(false)
+ }
+
+ @Test
+ fun handleClick_whenAutoModeTwilight_flipsState() =
+ scope.runTest {
+ val originalState = true
+ val startingModel = NightDisplayTileModel.AutoModeTwilight(originalState, false, false)
+
+ underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+ verify(colorDisplayManager).setNightDisplayActivated(!originalState)
+ }
+
+ @Test
+ fun handleClick_whenAutoModeCustom_flipsState() =
+ scope.runTest {
+ val originalState = true
+ val startingModel =
+ NightDisplayTileModel.AutoModeCustom(originalState, false, null, null, false)
+
+ underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+ verify(colorDisplayManager).setNightDisplayActivated(!originalState)
+ }
+
+ @Test
+ fun handleLongClickWhenEnabled() =
+ scope.runTest {
+ val enabledState = true
+
+ underTest.handleInput(
+ QSTileInputTestKtx.longClick(
+ NightDisplayTileModel.AutoModeOff(enabledState, false),
+ testUser
+ )
+ )
+
+ assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+
+ val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+ val actualIntentAction = intentInput.intent.action
+ val expectedIntentAction = Settings.ACTION_NIGHT_DISPLAY_SETTINGS
+ assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+ }
+
+ @Test
+ fun handleLongClickWhenDisabled() =
+ scope.runTest {
+ val enabledState = false
+
+ underTest.handleInput(
+ QSTileInputTestKtx.longClick(
+ NightDisplayTileModel.AutoModeOff(enabledState, false),
+ testUser
+ )
+ )
+
+ assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+
+ val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+ val actualIntentAction = intentInput.intent.action
+ val expectedIntentAction = Settings.ACTION_NIGHT_DISPLAY_SETTINGS
+ assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
new file mode 100644
index 0000000..5d2e701
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.service.quicksettings.Tile
+import android.text.TextUtils
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.impl.night.qsNightDisplayTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.mock
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val config = kosmos.qsNightDisplayTileConfig
+
+ private val testStartTime = LocalTime.MIDNIGHT
+ private val testEndTime = LocalTime.NOON
+
+ private lateinit var mapper: NightDisplayTileMapper
+
+ @Before
+ fun setup() {
+ mapper =
+ NightDisplayTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(R.drawable.qs_nightlight_icon_on, TestStubDrawable())
+ addOverride(R.drawable.qs_nightlight_icon_off, TestStubDrawable())
+ }
+ .resources,
+ context.theme,
+ mock<QSTileLogger>(),
+ )
+ }
+
+ @Test
+ fun disabledModel_whenAutoModeOff() {
+ val inputModel = NightDisplayTileModel.AutoModeOff(false, false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ /** Force enable does not change the mode by itself. */
+ @Test
+ fun disabledModel_whenAutoModeOff_whenForceEnable() {
+ val inputModel = NightDisplayTileModel.AutoModeOff(false, true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun enabledModel_whenAutoModeOff() {
+ val inputModel = NightDisplayTileModel.AutoModeOff(true, false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun enabledModel_forceAutoMode_whenAutoModeOff() {
+ val inputModel = NightDisplayTileModel.AutoModeOff(true, true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun enabledModel_autoModeTwilight_locationOff() {
+ val inputModel = NightDisplayTileModel.AutoModeTwilight(true, false, false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState = createNightDisplayTileState(QSTileState.ActivationState.ACTIVE, null)
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun enabledModel_autoModeTwilight_locationOn() {
+ val inputModel = NightDisplayTileModel.AutoModeTwilight(true, false, true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.quick_settings_night_secondary_label_until_sunrise)
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun disabledModel_autoModeTwilight_locationOn() {
+ val inputModel = NightDisplayTileModel.AutoModeTwilight(false, false, true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset)
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun disabledModel_autoModeTwilight_locationOff() {
+ val inputModel = NightDisplayTileModel.AutoModeTwilight(false, false, false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState = createNightDisplayTileState(QSTileState.ActivationState.INACTIVE, null)
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun disabledModel_autoModeCustom_24Hour() {
+ val inputModel =
+ NightDisplayTileModel.AutoModeCustom(false, false, testStartTime, null, true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.getString(
+ R.string.quick_settings_night_secondary_label_on_at,
+ formatter24Hour.format(testStartTime)
+ )
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun disabledModel_autoModeCustom_12Hour() {
+ val inputModel =
+ NightDisplayTileModel.AutoModeCustom(false, false, testStartTime, null, false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.getString(
+ R.string.quick_settings_night_secondary_label_on_at,
+ formatter12Hour.format(testStartTime)
+ )
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ /** Should have the same outcome as [disabledModel_autoModeCustom_12Hour] */
+ @Test
+ fun disabledModel_autoModeCustom_12Hour_isEnrolledForcedAutoMode() {
+ val inputModel =
+ NightDisplayTileModel.AutoModeCustom(false, true, testStartTime, null, false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.getString(
+ R.string.quick_settings_night_secondary_label_on_at,
+ formatter12Hour.format(testStartTime)
+ )
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun enabledModel_autoModeCustom_24Hour() {
+ val inputModel = NightDisplayTileModel.AutoModeCustom(true, false, null, testEndTime, true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(
+ R.string.quick_settings_secondary_label_until,
+ formatter24Hour.format(testEndTime)
+ )
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun enabledModel_autoModeCustom_12Hour() {
+ val inputModel = NightDisplayTileModel.AutoModeCustom(true, false, null, testEndTime, false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(
+ R.string.quick_settings_secondary_label_until,
+ formatter12Hour.format(testEndTime)
+ )
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ /** Should have the same state as [enabledModel_autoModeCustom_24Hour] */
+ @Test
+ fun enabledModel_autoModeCustom_24Hour_forceEnabled() {
+ val inputModel = NightDisplayTileModel.AutoModeCustom(true, true, null, testEndTime, true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(
+ R.string.quick_settings_secondary_label_until,
+ formatter24Hour.format(testEndTime)
+ )
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createNightDisplayTileState(
+ activationState: QSTileState.ActivationState,
+ secondaryLabel: String?
+ ): QSTileState {
+ val label = context.getString(R.string.quick_settings_night_display_label)
+
+ val contentDescription =
+ if (TextUtils.isEmpty(secondaryLabel)) label
+ else TextUtils.concat(label, ", ", secondaryLabel)
+ return QSTileState(
+ {
+ Icon.Loaded(
+ context.getDrawable(
+ if (activationState == QSTileState.ActivationState.ACTIVE)
+ R.drawable.qs_nightlight_icon_on
+ else R.drawable.qs_nightlight_icon_off
+ )!!,
+ null
+ )
+ },
+ label,
+ activationState,
+ secondaryLabel,
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ contentDescription,
+ null,
+ QSTileState.SideViewIcon.None,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+
+ private companion object {
+ val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
+ val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
new file mode 100644
index 0000000..a2ffe70
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.scene.ui.viewmodel
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.model.TransitionKeys.GoneToSplitShade
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.testKosmos
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+@EnableSceneContainer
+class GoneSceneViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val shadeRepository by lazy { kosmos.shadeRepository }
+ private lateinit var underTest: GoneSceneViewModel
+
+ @Before
+ fun setUp() {
+ underTest =
+ GoneSceneViewModel(
+ applicationScope = testScope.backgroundScope,
+ shadeInteractor = kosmos.shadeInteractor,
+ )
+ }
+
+ @Test
+ fun downTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ shadeRepository.setShadeMode(ShadeMode.Split)
+ runCurrent()
+
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey)
+ .isEqualTo(GoneToSplitShade)
+ }
+
+ @Test
+ fun downTransitionKey_splitShadeDisabled_isNull() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ shadeRepository.setShadeMode(ShadeMode.Single)
+ runCurrent()
+
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
index aa0ca18..78c4def 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -60,7 +60,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class ShadeInteractorImplTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class ShadeInteractorImplTest(flags: FlagsParameterization) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
@@ -85,7 +85,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
index 44c9695..cecc70c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -60,7 +60,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class ShadeStartableTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class ShadeStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val shadeInteractor by lazy { kosmos.shadeInteractor }
@@ -80,7 +80,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 4b7e5e7..4622f0c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -42,6 +42,7 @@
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.GoneToSplitShade
import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -161,6 +162,27 @@
}
@Test
+ fun upTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ shadeRepository.setShadeMode(ShadeMode.Split)
+ runCurrent()
+
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey)
+ .isEqualTo(GoneToSplitShade)
+ }
+
+ @Test
+ fun upTransitionKey_splitShadeDisabled_isNull() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ shadeRepository.setShadeMode(ShadeMode.Single)
+ runCurrent()
+
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey).isNull()
+ }
+
+ @Test
fun isClickable_deviceUnlocked_false() =
testScope.runTest {
val isClickable by collectLastValue(underTest.isClickable)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index d353a62..f06e04b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -33,7 +33,10 @@
import com.android.systemui.flags.parameterizeSceneContainerFlag
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -65,11 +68,11 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@TestableLooper.RunWithLooper
-class StatusBarStateControllerImplTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
-
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val mockDarkAnimator = mock<ObjectAnimator>()
private lateinit var underTest: StatusBarStateControllerImpl
@@ -84,7 +87,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
@@ -98,6 +101,7 @@
uiEventLogger,
{ kosmos.interactionJankMonitor },
JavaAdapter(testScope.backgroundScope),
+ { kosmos.keyguardTransitionInteractor },
{ kosmos.shadeInteractor },
{ kosmos.deviceUnlockedInteractor },
{ kosmos.sceneInteractor },
@@ -330,4 +334,25 @@
assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
}
+
+ @Test
+ fun leaveOpenOnKeyguard_whenGone_isFalse() =
+ testScope.runTest {
+ underTest.start()
+ underTest.setLeaveOpenOnKeyguardHide(true)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ )
+ assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(true)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+ assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(false)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index cbbc4d8..9367a93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -59,7 +59,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterization?) :
+class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterization) :
SysuiTestCase() {
companion object {
@@ -71,7 +71,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 7ac549a..cc5df74 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -20,12 +20,13 @@
import android.app.NotificationManager.Policy
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -33,7 +34,7 @@
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
@@ -56,11 +57,13 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@EnableFlags(FooterViewRefactor.FLAG_NAME)
-class NotificationListViewModelTest : SysuiTestCase() {
+class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
@@ -72,16 +75,30 @@
private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
private val fakePowerRepository = kosmos.fakePowerRepository
private val fakeRemoteInputRepository = kosmos.fakeRemoteInputRepository
- private val fakeShadeRepository = kosmos.fakeShadeRepository
private val fakeUserSetupRepository = kosmos.fakeUserSetupRepository
private val headsUpRepository = kosmos.headsUpNotificationRepository
private val zenModeRepository = kosmos.zenModeRepository
- val underTest = kosmos.notificationListViewModel
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+ private lateinit var underTest: NotificationListViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ underTest = kosmos.notificationListViewModel
}
@Test
@@ -163,7 +180,7 @@
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND quick settings are expanded
- fakeShadeRepository.legacyQsFullscreen.value = true
+ shadeTestUtil.setQsFullscreen(true)
runCurrent()
// THEN empty shade is not visible
@@ -178,9 +195,10 @@
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND quick settings are expanded
- fakeShadeRepository.setQsExpansion(1f)
- // AND split shade is enabled
+ shadeTestUtil.setQsExpansion(1f)
+ // AND split shade is expanded
overrideResource(R.bool.config_use_split_notification_shade, true)
+ shadeTestUtil.setShadeExpansion(1f)
fakeConfigurationController.notifyConfigurationChanged()
runCurrent()
@@ -290,7 +308,7 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND shade is open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
runCurrent()
// THEN footer is visible
@@ -306,7 +324,7 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND shade is open on lockscreen
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
runCurrent()
// THEN footer is visible
@@ -337,7 +355,7 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND shade is open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
// AND user is not set up
fakeUserSetupRepository.setUserSetUp(false)
runCurrent()
@@ -355,7 +373,7 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND shade is open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
// AND device is starting to go to sleep
fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
runCurrent()
@@ -373,10 +391,10 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND shade is open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
// AND quick settings are expanded
- fakeShadeRepository.setQsExpansion(1f)
- fakeShadeRepository.legacyQsFullscreen.value = true
+ shadeTestUtil.setQsExpansion(1f)
+ shadeTestUtil.setQsFullscreen(true)
runCurrent()
// THEN footer is not visible
@@ -390,11 +408,11 @@
// WHEN has notifs
activeNotificationListRepository.setActiveNotifs(count = 2)
+ // AND quick settings are expanded
+ shadeTestUtil.setQsExpansion(1f)
// AND shade is open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
- // AND quick settings are expanded
- fakeShadeRepository.setQsExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
// AND split shade is enabled
overrideResource(R.bool.config_use_split_notification_shade, true)
fakeConfigurationController.notifyConfigurationChanged()
@@ -413,7 +431,7 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND shade is open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
// AND remote input is active
fakeRemoteInputRepository.isRemoteInputActive.value = true
runCurrent()
@@ -431,7 +449,7 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND shade is open and fully expanded
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
runCurrent()
// THEN footer visibility animates
@@ -447,7 +465,7 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND we are on the keyguard
fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
runCurrent()
// THEN footer visibility does not animate
@@ -461,7 +479,7 @@
// WHEN shade is closed
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(0f)
+ shadeTestUtil.setShadeExpansion(0f)
runCurrent()
// THEN footer is hidden
@@ -475,7 +493,7 @@
// WHEN shade is open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
runCurrent()
// THEN footer is hidden
@@ -489,8 +507,8 @@
// WHEN QS partially open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setQsExpansion(0.5f)
- fakeShadeRepository.setLegacyShadeExpansion(0.5f)
+ shadeTestUtil.setQsExpansion(0.5f)
+ shadeTestUtil.setShadeExpansion(0.5f)
runCurrent()
// THEN footer is hidden
@@ -588,7 +606,7 @@
testScope.runTest {
val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
- fakeShadeRepository.setQsExpansion(0.0f)
+ shadeTestUtil.setQsExpansion(0.0f)
fakeKeyguardRepository.setKeyguardShowing(false)
runCurrent()
@@ -601,7 +619,7 @@
testScope.runTest {
val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
- fakeShadeRepository.setQsExpansion(0.0f)
+ shadeTestUtil.setQsExpansion(0.0f)
fakeKeyguardRepository.setKeyguardShowing(true)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 2cd295c..f2ce745 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -75,7 +75,7 @@
@RunWith(ParameterizedAndroidJunit4::class)
// SharedNotificationContainerViewModel is only bound when FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT is on
@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
companion object {
@JvmStatic
@@ -89,7 +89,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
val aodBurnInViewModel = mock(AodBurnInViewModel::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 8ce5037..63f19fb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -249,22 +249,40 @@
@Test
- fun testDelete_showingEntryKeyBecomesPreviousHunKey() {
+ fun testDelete_deleteSecondToLastEntry_showingEntryKeyBecomesPreviousHunKey() {
mAvalancheController.previousHunKey = ""
// Entry is showing
+ val firstEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = firstEntry
+
+ // There's another entry waiting to show next
+ val secondEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.addToNext(secondEntry, runnableMock!!)
+
+ // Delete
+ mAvalancheController.delete(firstEntry, runnableMock, "testLabel")
+
+ // Next entry is shown
+ assertThat(mAvalancheController.previousHunKey).isEqualTo(firstEntry.mEntry!!.key)
+ }
+
+ @Test
+ fun testDelete_deleteLastEntry_previousHunKeyCleared() {
+ mAvalancheController.previousHunKey = "key"
+
+ // Nothing waiting to show
+ mAvalancheController.clearNext()
+
+ // One entry is showing
val showingEntry = createHeadsUpEntry(id = 0)
mAvalancheController.headsUpEntryShowing = showingEntry
- // There's another entry waiting to show next
- val nextEntry = createHeadsUpEntry(id = 1)
- mAvalancheController.addToNext(nextEntry, runnableMock!!)
-
// Delete
- mAvalancheController.delete(showingEntry, runnableMock, "testLabel")
+ mAvalancheController.delete(showingEntry, runnableMock!!, "testLabel")
// Next entry is shown
- assertThat(mAvalancheController.previousHunKey).isEqualTo(showingEntry.mEntry!!.key)
+ assertThat(mAvalancheController.previousHunKey).isEqualTo("");
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
index 27a813f..fdea5a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
@@ -32,7 +32,7 @@
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
-import com.android.systemui.volume.panel.volumePanelViewModel
+import com.android.systemui.volume.panel.ui.viewmodel.volumePanelViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt
index 6256eec..ab184ab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt
@@ -22,14 +22,14 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
-import com.android.systemui.volume.panel.availableCriteria
-import com.android.systemui.volume.panel.criteriaByKey
-import com.android.systemui.volume.panel.defaultCriteria
+import com.android.systemui.volume.panel.domain.availableCriteria
+import com.android.systemui.volume.panel.domain.defaultCriteria
import com.android.systemui.volume.panel.domain.model.ComponentModel
-import com.android.systemui.volume.panel.enabledComponents
+import com.android.systemui.volume.panel.domain.unavailableCriteria
import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
-import com.android.systemui.volume.panel.unavailableCriteria
+import com.android.systemui.volume.panel.ui.composable.enabledComponents
import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,7 +47,7 @@
with(kosmos) {
ComponentsInteractorImpl(
enabledComponents,
- defaultCriteria,
+ { defaultCriteria },
testScope.backgroundScope,
criteriaByKey,
)
@@ -66,9 +66,9 @@
)
criteriaByKey =
mapOf(
- BOTTOM_BAR to availableCriteria,
- COMPONENT_1 to unavailableCriteria,
- COMPONENT_2 to availableCriteria,
+ BOTTOM_BAR to Provider { availableCriteria },
+ COMPONENT_1 to Provider { unavailableCriteria },
+ COMPONENT_2 to Provider { availableCriteria },
)
initUnderTest()
@@ -96,8 +96,8 @@
)
criteriaByKey =
mapOf(
- BOTTOM_BAR to availableCriteria,
- COMPONENT_2 to availableCriteria,
+ BOTTOM_BAR to Provider { availableCriteria },
+ COMPONENT_2 to Provider { availableCriteria },
)
defaultCriteria = unavailableCriteria
initUnderTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt
index 3dbf23e..e3dc552 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt
@@ -20,9 +20,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.testKosmos
-import com.android.systemui.volume.panel.componentByKey
-import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider
import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.mockVolumePanelUiComponentProvider
import com.google.common.truth.Truth
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
index 82ce6d7..b37184d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
@@ -20,8 +20,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.testKosmos
-import com.android.systemui.volume.panel.mockVolumePanelUiComponent
import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.mockVolumePanelUiComponent
import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
import com.google.common.truth.Truth
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
index 4e06855..f6ada4c16 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
@@ -28,15 +28,15 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.policy.fakeConfigurationController
import com.android.systemui.testKosmos
-import com.android.systemui.volume.panel.componentByKey
-import com.android.systemui.volume.panel.componentsLayoutManager
-import com.android.systemui.volume.panel.criteriaByKey
-import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider
+import com.android.systemui.volume.panel.domain.interactor.criteriaByKey
+import com.android.systemui.volume.panel.domain.unavailableCriteria
import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.mockVolumePanelUiComponentProvider
+import com.android.systemui.volume.panel.ui.composable.componentByKey
import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
-import com.android.systemui.volume.panel.unavailableCriteria
-import com.android.systemui.volume.panel.volumePanelViewModel
+import com.android.systemui.volume.panel.ui.layout.componentsLayoutManager
import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -95,7 +95,7 @@
COMPONENT_2 to mockVolumePanelUiComponentProvider,
BOTTOM_BAR to mockVolumePanelUiComponentProvider,
)
- criteriaByKey = mapOf(COMPONENT_2 to unavailableCriteria)
+ criteriaByKey = mapOf(COMPONENT_2 to Provider { unavailableCriteria })
}) {
testScope.runTest {
val componentsLayout by collectLastValue(underTest.componentsLayout)
diff --git a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
index cf7a730..a30a122 100644
--- a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
+++ b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
@@ -13,12 +13,16 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
- <!-- Since this layer list has just one layer, we can remove it and replace with the inner
- layer drawable. However this should only be done when the flag
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/qs_tile_ripple_color">
+ <!-- We don't really use the ripple effect here, but changing it to LayerDrawable causes
+ performance regression, see: b/339412453.
+ Since this ripple has just one layer inside, we can try to remove that extra "background"
+ layer. However this should only be done when the flag
com.android.systemui.qs_tile_focus_state has completed all its stages and this drawable
fully replaces the previous one to ensure consistency with code sections searching for
- specific ids in drawable hierarchy -->
+ specific ids in drawable hierarchy
+ -->
<item
android:id="@id/background">
<layer-list>
@@ -48,4 +52,4 @@
</item>
</layer-list>
</item>
-</layer-list>
\ No newline at end of file
+</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay2.xml b/packages/SystemUI/res/layout/clipboard_overlay2.xml
index 33ad2cd..521369e 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay2.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay2.xml
@@ -31,7 +31,7 @@
android:layout_width="0dp"
android:elevation="4dp"
android:background="@drawable/shelf_action_chip_container_background"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginStart="@dimen/overlay_action_container_minimum_edge_spacing"
android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/actions_container"
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index 76f7f3b..2cb4b02 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -33,8 +33,7 @@
android:layout_width="wrap_content"
android:elevation="4dp"
android:background="@drawable/shelf_action_chip_container_background"
- android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
+ android:layout_marginHorizontal="@dimen/overlay_action_container_minimum_edge_spacing"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/guideline"
>
@@ -61,7 +60,7 @@
android:id="@+id/screenshot_preview_border"
android:layout_width="0dp"
android:layout_height="0dp"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginStart="@dimen/overlay_action_container_minimum_edge_spacing"
android:layout_marginTop="@dimen/overlay_border_width_neg"
android:layout_marginEnd="@dimen/overlay_border_width_neg"
android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
@@ -153,7 +152,7 @@
android:id="@+id/screenshot_message_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginHorizontal="@dimen/overlay_action_container_minimum_edge_spacing"
android:layout_marginTop="4dp"
android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a1daebd..517b44f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -285,6 +285,9 @@
the amount by the view is positioned above the screen before the animation starts. -->
<dimen name="heads_up_appear_y_above_screen">32dp</dimen>
+ <!-- padding between the old and new heads up notifications for the hun cycling animation -->
+ <dimen name="heads_up_cycling_padding">8dp</dimen>
+
<!-- padding between the heads up and the statusbar -->
<dimen name="heads_up_status_bar_padding">8dp</dimen>
@@ -446,6 +449,8 @@
<dimen name="overlay_preview_container_margin">8dp</dimen>
<dimen name="overlay_action_container_margin_horizontal">8dp</dimen>
<dimen name="overlay_action_container_margin_bottom">6dp</dimen>
+ <!-- minimum distance to the left, right or bottom edges. -->
+ <dimen name="overlay_action_container_minimum_edge_spacing">12dp</dimen>
<dimen name="overlay_bg_protection_height">242dp</dimen>
<dimen name="overlay_action_container_corner_radius">20dp</dimen>
<dimen name="overlay_action_container_padding_vertical">8dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 45bcd82..b5ec5b2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1158,6 +1158,8 @@
<string name="button_to_configure_widgets_text">Customize widgets</string>
<!-- Description for the App icon of disabled widget. [CHAR LIMIT=NONE] -->
<string name="icon_description_for_disabled_widget">App icon for disabled widget</string>
+ <!-- Description for the App icon of a package that is currently being installed. [CHAR LIMIT=NONE] -->
+ <string name="icon_description_for_pending_widget">App icon for a widget being installed</string>
<!-- Label for the button which configures widgets [CHAR LIMIT=NONE] -->
<string name="edit_widget">Edit widget</string>
<!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
@@ -1634,9 +1636,15 @@
<string name="volume_panel_collapsed_sliders">Volume sliders collapsed</string>
<!-- Hint for accessibility. A stream name is a parameter. For example: double tap to mute media [CHAR_LIMIT=NONE] -->
- <string name="volume_panel_hint_mute">mute %s</string>
+ <string name="volume_panel_hint_mute">Mute %s</string>
<!-- Hint for accessibility. A stream name is a parameter. For example: double tap to unmute media [CHAR_LIMIT=NONE] -->
- <string name="volume_panel_hint_unmute">unmute %s</string>
+ <string name="volume_panel_hint_unmute">Unmute %s</string>
+
+ <!-- Hint for accessibility. This is announced when the stream is muted [CHAR_LIMIT=NONE] -->
+ <string name="volume_panel_hint_muted">muted</string>
+
+ <!-- Hint for accessibility. This is announced when ring mode is set to Vibrate. [CHAR_LIMIT=NONE] -->
+ <string name="volume_panel_hint_vibrate">vibrate</string>
<!-- Title with application label for media output settings when there is media playing. [CHAR LIMIT=20] -->
<string name="media_output_label_title">Playing <xliff:g id="label" example="Music Player">%s</xliff:g> on</string>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
index 422f02f..8979ef1 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
@@ -16,7 +16,6 @@
package com.android.systemui.biometrics
import android.Manifest
-import android.annotation.IntDef
import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX
@@ -39,14 +38,9 @@
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
import com.android.internal.widget.LockPatternUtils
-import java.lang.annotation.Retention
-import java.lang.annotation.RetentionPolicy
+import com.android.systemui.biometrics.shared.model.PromptKind
object Utils {
- const val CREDENTIAL_PIN = 1
- const val CREDENTIAL_PATTERN = 2
- const val CREDENTIAL_PASSWORD = 3
-
/** Base set of layout flags for fingerprint overlay widgets. */
const val FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS =
(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
@@ -91,17 +85,16 @@
(promptInfo.authenticators and Authenticators.BIOMETRIC_WEAK) != 0
@JvmStatic
- @CredentialType
- fun getCredentialType(utils: LockPatternUtils, userId: Int): Int =
+ fun getCredentialType(utils: LockPatternUtils, userId: Int): PromptKind =
when (utils.getKeyguardStoredPasswordQuality(userId)) {
- PASSWORD_QUALITY_SOMETHING -> CREDENTIAL_PATTERN
+ PASSWORD_QUALITY_SOMETHING -> PromptKind.Pattern
PASSWORD_QUALITY_NUMERIC,
- PASSWORD_QUALITY_NUMERIC_COMPLEX -> CREDENTIAL_PIN
+ PASSWORD_QUALITY_NUMERIC_COMPLEX -> PromptKind.Pin
PASSWORD_QUALITY_ALPHABETIC,
PASSWORD_QUALITY_ALPHANUMERIC,
PASSWORD_QUALITY_COMPLEX,
- PASSWORD_QUALITY_MANAGED -> CREDENTIAL_PASSWORD
- else -> CREDENTIAL_PASSWORD
+ PASSWORD_QUALITY_MANAGED -> PromptKind.Password
+ else -> PromptKind.Password
}
@JvmStatic
@@ -129,8 +122,4 @@
return windowMetrics?.windowInsets?.getInsets(WindowInsets.Type.navigationBars())
?: Insets.NONE
}
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD)
- annotation class CredentialType
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
rename to packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
index a97e2dc..b99c514 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,16 +16,21 @@
package com.android.systemui.biometrics.shared.model
-import com.android.systemui.biometrics.Utils
-
-// TODO(b/251476085): this should eventually replace Utils.CredentialType
-/** Credential options for biometric prompt. Shadows [Utils.CredentialType]. */
sealed interface PromptKind {
+ object None : PromptKind
+
data class Biometric(
+ /** The available modalities for the authentication on the prompt. */
val activeModalities: BiometricModalities = BiometricModalities(),
+ // TODO(b/330908557): Use this value to decide whether to show two pane layout, instead of
+ // simply depending on rotations.
+ val showTwoPane: Boolean = false,
) : PromptKind
object Pin : PromptKind
object Pattern : PromptKind
object Password : PromptKind
+
+ fun isBiometric() = this is Biometric
+ fun isCredential() = (this is Pin) or (this is Pattern) or (this is Password)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 460779c..f33acf2 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -43,6 +43,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -432,6 +433,7 @@
listenForDozeAmountTransition(this)
listenForAnyStateToAodTransition(this)
listenForAnyStateToLockscreenTransition(this)
+ listenForAnyStateToDozingTransition(this)
} else {
listenForDozeAmount(this)
}
@@ -578,6 +580,21 @@
}
}
+ /**
+ * When keyguard is displayed due to pulsing notifications when AOD is off,
+ * we should make sure clock is in dozing state instead of LS state
+ */
+ @VisibleForTesting
+ internal fun listenForAnyStateToDozingTransition(scope: CoroutineScope): Job {
+ return scope.launch {
+ keyguardTransitionInteractor
+ .transitionStepsToState(DOZING)
+ .filter { it.transitionState == TransitionState.FINISHED }
+ .collect { handleDoze(1f) }
+ }
+ }
+
+
@VisibleForTesting
internal fun listenForDozing(scope: CoroutineScope): Job {
return scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 177d933..35c2024 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -30,6 +30,8 @@
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.util.SparseArray;
import android.view.Display;
import android.view.SurfaceControl;
@@ -41,6 +43,8 @@
import android.view.accessibility.IRemoteMagnificationAnimationCallback;
import android.window.InputTransferToken;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.CoreStartable;
@@ -69,6 +73,9 @@
public class Magnification implements CoreStartable, CommandQueue.Callbacks {
private static final String TAG = "Magnification";
+ @VisibleForTesting static final int DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS = 300;
+ private static final int MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL = 1;
+
private final ModeSwitchesController mModeSwitchesController;
private final Context mContext;
private final Handler mHandler;
@@ -209,8 +216,26 @@
SysUiState sysUiState, OverviewProxyService overviewProxyService,
SecureSettings secureSettings, DisplayTracker displayTracker,
DisplayManager displayManager, AccessibilityLogger a11yLogger) {
+ this(context, mainHandler.getLooper(), executor, commandQueue,
+ modeSwitchesController, sysUiState, overviewProxyService, secureSettings,
+ displayTracker, displayManager, a11yLogger);
+ }
+
+ @VisibleForTesting
+ public Magnification(Context context, Looper looper, @Main Executor executor,
+ CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
+ SysUiState sysUiState, OverviewProxyService overviewProxyService,
+ SecureSettings secureSettings, DisplayTracker displayTracker,
+ DisplayManager displayManager, AccessibilityLogger a11yLogger) {
mContext = context;
- mHandler = mainHandler;
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ if (msg.what == MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL) {
+ showMagnificationButtonInternal(msg.arg1, msg.arg2);
+ }
+ }
+ };
mExecutor = executor;
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mCommandQueue = commandQueue;
@@ -350,6 +375,21 @@
@MainThread
void showMagnificationButton(int displayId, int magnificationMode) {
+ if (Flags.delayShowMagnificationButton()) {
+ if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) {
+ return;
+ }
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(
+ MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode),
+ DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS);
+ } else {
+ showMagnificationButtonInternal(displayId, magnificationMode);
+ }
+ }
+
+ @MainThread
+ private void showMagnificationButtonInternal(int displayId, int magnificationMode) {
// not to show mode switch button if settings panel is already showing to
// prevent settings panel be covered by the button.
if (isMagnificationSettingsPanelShowing(displayId)) {
@@ -360,6 +400,9 @@
@MainThread
void removeMagnificationButton(int displayId) {
+ if (Flags.delayShowMagnificationButton()) {
+ mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL);
+ }
mModeSwitchesController.removeButton(displayId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt
new file mode 100644
index 0000000..8f071e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.model
+
+import java.time.LocalTime
+
+sealed interface NightDisplayChangeEvent {
+ data class OnAutoModeChanged(val autoMode: Int) : NightDisplayChangeEvent
+ data class OnActivatedChanged(val isActivated: Boolean) : NightDisplayChangeEvent
+ data class OnCustomStartTimeChanged(val startTime: LocalTime?) : NightDisplayChangeEvent
+ data class OnCustomEndTimeChanged(val endTime: LocalTime?) : NightDisplayChangeEvent
+ data class OnForceAutoModeChanged(val shouldForceAutoMode: Boolean) : NightDisplayChangeEvent
+ data class OnLocationEnabledChanged(val locationEnabled: Boolean) : NightDisplayChangeEvent
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt
new file mode 100644
index 0000000..196876e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.model
+
+import java.time.LocalTime
+
+/** models the state of NightDisplayRepository */
+data class NightDisplayState(
+ val autoMode: Int = 0,
+ val isActivated: Boolean = true,
+ val startTime: LocalTime? = null,
+ val endTime: LocalTime? = null,
+ val shouldForceAutoMode: Boolean = false,
+ val locationEnabled: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
index ae9f57f..6032f0b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
@@ -18,7 +18,7 @@
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.channels.awaitClose
@@ -29,6 +29,8 @@
interface AccessibilityRepository {
/** @see [AccessibilityManager.isTouchExplorationEnabled] */
val isTouchExplorationEnabled: Flow<Boolean>
+ /** @see [AccessibilityManager.isEnabled] */
+ val isEnabled: Flow<Boolean>
companion object {
operator fun invoke(a11yManager: AccessibilityManager): AccessibilityRepository =
@@ -47,6 +49,15 @@
awaitClose { manager.removeTouchExplorationStateChangeListener(listener) }
}
.distinctUntilChanged()
+
+ override val isEnabled: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val listener = AccessibilityManager.AccessibilityStateChangeListener(::trySend)
+ manager.addAccessibilityStateChangeListener(listener)
+ trySend(manager.isEnabled)
+ awaitClose { manager.removeAccessibilityStateChangeListener(listener) }
+ }
+ .distinctUntilChanged()
}
@Module
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt
new file mode 100644
index 0000000..bf44fab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.systemui.accessibility.data.model.NightDisplayChangeEvent
+import com.android.systemui.accessibility.data.model.NightDisplayState
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.policy.LocationController
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.kotlin.isLocationEnabledFlow
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import java.time.LocalTime
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.scan
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+class NightDisplayRepository
+@Inject
+constructor(
+ @Background private val bgCoroutineContext: CoroutineContext,
+ @Application private val scope: CoroutineScope,
+ private val globalSettings: GlobalSettings,
+ private val secureSettings: SecureSettings,
+ private val nightDisplayListenerBuilder: NightDisplayListenerModule.Builder,
+ private val colorDisplayManagerUserScopedService: UserScopedService<ColorDisplayManager>,
+ private val locationController: LocationController,
+) {
+ private val stateFlowUserMap = mutableMapOf<Int, Flow<NightDisplayState>>()
+
+ fun nightDisplayState(user: UserHandle): Flow<NightDisplayState> =
+ stateFlowUserMap.getOrPut(user.identifier) {
+ return merge(
+ colorDisplayManagerChangeEventFlow(user),
+ shouldForceAutoMode(user).map {
+ NightDisplayChangeEvent.OnForceAutoModeChanged(it)
+ },
+ locationController.isLocationEnabledFlow().map {
+ NightDisplayChangeEvent.OnLocationEnabledChanged(it)
+ }
+ )
+ .scan(initialState(user)) { state, event ->
+ when (event) {
+ is NightDisplayChangeEvent.OnActivatedChanged ->
+ state.copy(isActivated = event.isActivated)
+ is NightDisplayChangeEvent.OnAutoModeChanged ->
+ state.copy(autoMode = event.autoMode)
+ is NightDisplayChangeEvent.OnCustomStartTimeChanged ->
+ state.copy(startTime = event.startTime)
+ is NightDisplayChangeEvent.OnCustomEndTimeChanged ->
+ state.copy(endTime = event.endTime)
+ is NightDisplayChangeEvent.OnForceAutoModeChanged ->
+ state.copy(shouldForceAutoMode = event.shouldForceAutoMode)
+ is NightDisplayChangeEvent.OnLocationEnabledChanged ->
+ state.copy(locationEnabled = event.locationEnabled)
+ }
+ }
+ .conflate()
+ .onStart { emit(initialState(user)) }
+ .flowOn(bgCoroutineContext)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), NightDisplayState())
+ }
+
+ /** Track changes in night display enabled state and its auto mode */
+ private fun colorDisplayManagerChangeEventFlow(user: UserHandle) = callbackFlow {
+ val nightDisplayListener = nightDisplayListenerBuilder.setUser(user.identifier).build()
+ val nightDisplayCallback =
+ object : NightDisplayListener.Callback {
+ override fun onActivated(activated: Boolean) {
+ trySend(NightDisplayChangeEvent.OnActivatedChanged(activated))
+ }
+
+ override fun onAutoModeChanged(autoMode: Int) {
+ trySend(NightDisplayChangeEvent.OnAutoModeChanged(autoMode))
+ }
+
+ override fun onCustomStartTimeChanged(startTime: LocalTime?) {
+ trySend(NightDisplayChangeEvent.OnCustomStartTimeChanged(startTime))
+ }
+
+ override fun onCustomEndTimeChanged(endTime: LocalTime?) {
+ trySend(NightDisplayChangeEvent.OnCustomEndTimeChanged(endTime))
+ }
+ }
+ nightDisplayListener.setCallback(nightDisplayCallback)
+ awaitClose { nightDisplayListener.setCallback(null) }
+ }
+
+ /** @return true when the option to force auto mode is available and a value has not been set */
+ private fun shouldForceAutoMode(userHandle: UserHandle): Flow<Boolean> =
+ combine(isForceAutoModeAvailable, isDisplayAutoModeRawNotSet(userHandle)) {
+ isForceAutoModeAvailable,
+ isDisplayAutoModeRawNotSet,
+ ->
+ isForceAutoModeAvailable && isDisplayAutoModeRawNotSet
+ }
+
+ private val isForceAutoModeAvailable: Flow<Boolean> =
+ globalSettings
+ .observerFlow(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME)
+ .onStart { emit(Unit) }
+ .map {
+ globalSettings.getString(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) ==
+ NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE
+ }
+ .distinctUntilChanged()
+
+ /** Inspired by [ColorDisplayService.getNightDisplayAutoModeRawInternal] */
+ private fun isDisplayAutoModeRawNotSet(userHandle: UserHandle): Flow<Boolean> =
+ if (userHandle.identifier == UserHandle.USER_NULL) {
+ flowOf(IS_AUTO_MODE_RAW_NOT_SET_DEFAULT)
+ } else {
+ secureSettings
+ .observerFlow(userHandle.identifier, DISPLAY_AUTO_MODE_RAW_SETTING_NAME)
+ .onStart { emit(Unit) }
+ .map {
+ secureSettings.getIntForUser(
+ DISPLAY_AUTO_MODE_RAW_SETTING_NAME,
+ userHandle.identifier
+ ) == NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET
+ }
+ }
+ .distinctUntilChanged()
+
+ suspend fun setNightDisplayAutoMode(autoMode: Int, user: UserHandle) {
+ withContext(bgCoroutineContext) {
+ colorDisplayManagerUserScopedService.forUser(user).nightDisplayAutoMode = autoMode
+ }
+ }
+
+ suspend fun setNightDisplayActivated(activated: Boolean, user: UserHandle) {
+ withContext(bgCoroutineContext) {
+ colorDisplayManagerUserScopedService.forUser(user).isNightDisplayActivated = activated
+ }
+ }
+
+ private fun initialState(user: UserHandle): NightDisplayState {
+ val colorDisplayManager = colorDisplayManagerUserScopedService.forUser(user)
+ return NightDisplayState(
+ colorDisplayManager.nightDisplayAutoMode,
+ colorDisplayManager.isNightDisplayActivated,
+ colorDisplayManager.nightDisplayCustomStartTime,
+ colorDisplayManager.nightDisplayCustomEndTime,
+ globalSettings.getString(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) ==
+ NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE &&
+ secureSettings.getIntForUser(DISPLAY_AUTO_MODE_RAW_SETTING_NAME, user.identifier) ==
+ NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET,
+ locationController.isLocationEnabled,
+ )
+ }
+
+ private companion object {
+ const val NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET = -1
+ const val NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE = "1"
+ const val IS_AUTO_MODE_RAW_NOT_SET_DEFAULT = true
+ const val IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME =
+ Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE
+ const val DISPLAY_AUTO_MODE_RAW_SETTING_NAME = Settings.Secure.NIGHT_DISPLAY_AUTO_MODE
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt
index 968ce0d..93b624a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt
@@ -28,6 +28,8 @@
private val a11yRepo: AccessibilityRepository,
) {
/** @see [android.view.accessibility.AccessibilityManager.isTouchExplorationEnabled] */
- val isTouchExplorationEnabled: Flow<Boolean>
- get() = a11yRepo.isTouchExplorationEnabled
+ val isTouchExplorationEnabled: Flow<Boolean> = a11yRepo.isTouchExplorationEnabled
+
+ /** @see [android.view.accessibility.AccessibilityManager.isEnabled] */
+ val isEnabled: Flow<Boolean> = a11yRepo.isEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 54dd6d0..ed9597d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -41,6 +41,10 @@
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
+import com.android.systemui.qs.tiles.impl.night.domain.interactor.NightDisplayTileDataInteractor
+import com.android.systemui.qs.tiles.impl.night.domain.interactor.NightDisplayTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.impl.night.ui.NightDisplayTileMapper
import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileDataInteractor
import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
@@ -117,6 +121,7 @@
const val FONT_SCALING_TILE_SPEC = "font_scaling"
const val REDUCE_BRIGHTNESS_TILE_SPEC = "reduce_brightness"
const val ONE_HANDED_TILE_SPEC = "onehanded"
+ const val NIGHT_DISPLAY_TILE_SPEC = "night"
@Provides
@IntoMap
@@ -279,5 +284,41 @@
mapper,
)
else StubQSTileViewModel
+
+ @Provides
+ @IntoMap
+ @StringKey(NIGHT_DISPLAY_TILE_SPEC)
+ fun provideNightDisplayTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(NIGHT_DISPLAY_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_nightlight_icon_off,
+ labelRes = R.string.quick_settings_night_display_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /**
+ * Inject NightDisplay Tile into tileViewModelMap in QSModule. The tile is hidden behind a
+ * flag.
+ */
+ @Provides
+ @IntoMap
+ @StringKey(NIGHT_DISPLAY_TILE_SPEC)
+ fun provideNightDisplayTileViewModel(
+ factory: QSTileViewModelFactory.Static<NightDisplayTileModel>,
+ mapper: NightDisplayTileMapper,
+ stateInteractor: NightDisplayTileDataInteractor,
+ userActionInteractor: NightDisplayTileUserActionInteractor
+ ): QSTileViewModel =
+ if (Flags.qsNewTilesFuture())
+ factory.create(
+ TileSpec.create(NIGHT_DISPLAY_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
+ else StubQSTileViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 5ba0b2d..298c0f7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -73,9 +73,9 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
-import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
import com.android.systemui.biometrics.shared.model.BiometricModalities;
+import com.android.systemui.biometrics.shared.model.PromptKind;
import com.android.systemui.biometrics.ui.BiometricPromptLayout;
import com.android.systemui.biometrics.ui.CredentialView;
import com.android.systemui.biometrics.ui.binder.BiometricViewBinder;
@@ -149,7 +149,6 @@
private final CoroutineScope mApplicationCoroutineScope;
// TODO(b/287311775): these should be migrated out once ready
- private final Provider<PromptCredentialInteractor> mPromptCredentialInteractor;
private final @NonNull Provider<PromptSelectorInteractor> mPromptSelectorInteractorProvider;
// TODO(b/287311775): these should be migrated out of the view
private final Provider<CredentialViewModel> mCredentialViewModelProvider;
@@ -310,7 +309,6 @@
@NonNull UserManager userManager,
@NonNull LockPatternUtils lockPatternUtils,
@NonNull InteractionJankMonitor jankMonitor,
- @NonNull Provider<PromptCredentialInteractor> promptCredentialInteractor,
@NonNull Provider<PromptSelectorInteractor> promptSelectorInteractor,
@NonNull PromptViewModel promptViewModel,
@NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@@ -318,7 +316,7 @@
@NonNull VibratorHelper vibratorHelper) {
this(config, applicationCoroutineScope, fpProps, faceProps,
wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
- jankMonitor, promptSelectorInteractor, promptCredentialInteractor, promptViewModel,
+ jankMonitor, promptSelectorInteractor, promptViewModel,
credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor,
vibratorHelper);
}
@@ -334,7 +332,6 @@
@NonNull LockPatternUtils lockPatternUtils,
@NonNull InteractionJankMonitor jankMonitor,
@NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider,
- @NonNull Provider<PromptCredentialInteractor> credentialInteractor,
@NonNull PromptViewModel promptViewModel,
@NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull Handler mainHandler,
@@ -357,13 +354,19 @@
mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
mBiometricCallback = new BiometricCallback();
+ mFpProps = fpProps;
+ mFaceProps = faceProps;
+ final BiometricModalities biometricModalities = new BiometricModalities(
+ Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
+ Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
+
mPromptSelectorInteractorProvider = promptSelectorInteractorProvider;
- mPromptSelectorInteractorProvider.get().setShouldShowBpWithoutIconForCredential(
- config.mPromptInfo);
+ mPromptSelectorInteractorProvider.get().setPrompt(mConfig.mPromptInfo, mEffectiveUserId,
+ biometricModalities, mConfig.mOperationId, mConfig.mOpPackageName,
+ false /*onSwitchToCredential*/);
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
- if (constraintBp() && (Utils.isBiometricAllowed(config.mPromptInfo)
- || mPromptViewModel.getShowBpWithoutIconForCredential().getValue())) {
+ if (constraintBp() && mPromptViewModel.getPromptKind().getValue().isBiometric()) {
mLayout = (ConstraintLayout) layoutInflater.inflate(
R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */);
} else {
@@ -397,10 +400,7 @@
mPanelController = new AuthPanelController(mContext, mPanelView);
mBackgroundExecutor = bgExecutor;
mInteractionJankMonitor = jankMonitor;
- mPromptCredentialInteractor = credentialInteractor;
mCredentialViewModelProvider = credentialViewModelProvider;
- mFpProps = fpProps;
- mFaceProps = faceProps;
showPrompt(config, layoutInflater, promptViewModel,
Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
@@ -429,11 +429,12 @@
@Nullable FaceSensorPropertiesInternal faceProps,
@NonNull VibratorHelper vibratorHelper
) {
- if (Utils.isBiometricAllowed(config.mPromptInfo)
- || mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) {
+ if (mPromptViewModel.getPromptKind().getValue().isBiometric()) {
addBiometricView(config, layoutInflater, viewModel, fpProps, faceProps, vibratorHelper);
- } else if (constraintBp() && Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
- addCredentialView(true, false);
+ } else if (mPromptViewModel.getPromptKind().getValue().isCredential()) {
+ if (constraintBp()) {
+ addCredentialView(true, false);
+ }
} else {
mPromptSelectorInteractorProvider.get().resetPrompt();
}
@@ -444,12 +445,6 @@
@Nullable FingerprintSensorPropertiesInternal fpProps,
@Nullable FaceSensorPropertiesInternal faceProps,
@NonNull VibratorHelper vibratorHelper) {
- mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication(
- config.mPromptInfo,
- config.mUserId,
- config.mOperationId,
- new BiometricModalities(fpProps, faceProps),
- config.mOpPackageName);
if (constraintBp()) {
mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, null,
@@ -500,34 +495,24 @@
private void addCredentialView(boolean animatePanel, boolean animateContents) {
final LayoutInflater factory = LayoutInflater.from(mContext);
- @Utils.CredentialType final int credentialType = Utils.getCredentialType(
- mLockPatternUtils, mEffectiveUserId);
-
- switch (credentialType) {
- case Utils.CREDENTIAL_PATTERN:
- mCredentialView = factory.inflate(
- R.layout.auth_credential_pattern_view, null, false);
- break;
- case Utils.CREDENTIAL_PIN:
- mCredentialView = factory.inflate(R.layout.auth_credential_pin_view, null, false);
- break;
- case Utils.CREDENTIAL_PASSWORD:
- mCredentialView = factory.inflate(
- R.layout.auth_credential_password_view, null, false);
- break;
- default:
- throw new IllegalStateException("Unknown credential type: " + credentialType);
+ PromptKind credentialType = Utils.getCredentialType(mLockPatternUtils, mEffectiveUserId);
+ final int layoutResourceId;
+ if (credentialType instanceof PromptKind.Pattern) {
+ layoutResourceId = R.layout.auth_credential_pattern_view;
+ } else if (credentialType instanceof PromptKind.Pin) {
+ layoutResourceId = R.layout.auth_credential_pin_view;
+ } else if (credentialType instanceof PromptKind.Password) {
+ layoutResourceId = R.layout.auth_credential_password_view;
+ } else {
+ throw new IllegalStateException("Unknown credential type: " + credentialType);
}
+ mCredentialView = factory.inflate(layoutResourceId, null, false);
// The background is used for detecting taps / cancelling authentication. Since the
// credential view is full-screen and should not be canceled from background taps,
// disable it.
mBackgroundView.setOnClickListener(null);
mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
-
- mPromptSelectorInteractorProvider.get().useCredentialsForAuthentication(
- mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId,
- mConfig.mOpPackageName);
final CredentialViewModel vm = mCredentialViewModelProvider.get();
vm.setAnimateContents(animateContents);
((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel,
@@ -562,10 +547,9 @@
() -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED));
if (constraintBp()) {
// Do nothing on attachment with constraintLayout
- } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo)
- || mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) {
+ } else if (mPromptViewModel.getPromptKind().getValue().isBiometric()) {
mBiometricScrollView.addView(mBiometricView.asView());
- } else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
+ } else if (mPromptViewModel.getPromptKind().getValue().isCredential()) {
addCredentialView(true /* animatePanel */, false /* animateContents */);
} else {
throw new IllegalStateException("Unknown configuration: "
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index ca88d40d..b69e196 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -69,7 +69,6 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.CoreStartable;
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
-import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
@@ -139,7 +138,6 @@
private Job mBiometricContextListenerJob = null;
// TODO: these should be migrated out once ready
- @NonNull private final Provider<PromptCredentialInteractor> mPromptCredentialInteractor;
@NonNull private final Provider<PromptSelectorInteractor> mPromptSelectorInteractor;
@NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider;
@NonNull private final Provider<PromptViewModel> mPromptViewModelProvider;
@@ -735,7 +733,6 @@
@NonNull LockPatternUtils lockPatternUtils,
@NonNull Lazy<UdfpsLogger> udfpsLogger,
@NonNull Lazy<LogContextInteractor> logContextInteractor,
- @NonNull Provider<PromptCredentialInteractor> promptCredentialInteractorProvider,
@NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider,
@NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull Provider<PromptViewModel> promptViewModelProvider,
@@ -768,7 +765,6 @@
mLogContextInteractor = logContextInteractor;
mPromptSelectorInteractor = promptSelectorInteractorProvider;
- mPromptCredentialInteractor = promptCredentialInteractorProvider;
mPromptViewModelProvider = promptViewModelProvider;
mCredentialViewModelProvider = credentialViewModelProvider;
@@ -1316,8 +1312,8 @@
config.mScaleProvider = this::getScaleFactor;
return new AuthContainerView(config, mApplicationCoroutineScope, mFpProps, mFaceProps,
wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
- mInteractionJankMonitor, mPromptCredentialInteractor, mPromptSelectorInteractor,
- viewModel, mCredentialViewModelProvider, bgExecutor, mVibratorHelper);
+ mInteractionJankMonitor, mPromptSelectorInteractor, viewModel,
+ mCredentialViewModelProvider, bgExecutor, mVibratorHelper);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index 9ad3f43..58b238b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -16,12 +16,8 @@
package com.android.systemui.biometrics.data.repository
-import android.hardware.biometrics.Flags
import android.hardware.biometrics.PromptInfo
-import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.AuthController
-import com.android.systemui.biometrics.Utils
-import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed
import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -56,8 +52,8 @@
/** The gatekeeper challenge, if one is associated with this prompt. */
val challenge: StateFlow<Long?>
- /** The kind of credential to use (biometric, pin, pattern, etc.). */
- val kind: StateFlow<PromptKind>
+ /** The kind of prompt to use (biometric, pin, pattern, etc.). */
+ val promptKind: StateFlow<PromptKind>
/** The package name that the prompt is called from. */
val opPackageName: StateFlow<String?>
@@ -69,18 +65,6 @@
*/
val isConfirmationRequired: Flow<Boolean>
- /**
- * If biometric prompt without icon needs to show for displaying content prior to credential
- * view.
- */
- val showBpWithoutIconForCredential: StateFlow<Boolean>
-
- /**
- * Update whether biometric prompt without icon needs to show for displaying content prior to
- * credential view, which should be set before [setPrompt].
- */
- fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo)
-
/** Update the prompt configuration, which should be set before [isShowing]. */
fun setPrompt(
promptInfo: PromptInfo,
@@ -125,8 +109,8 @@
private val _userId: MutableStateFlow<Int?> = MutableStateFlow(null)
override val userId = _userId.asStateFlow()
- private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric())
- override val kind = _kind.asStateFlow()
+ private val _promptKind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.None)
+ override val promptKind = _promptKind.asStateFlow()
private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
override val opPackageName = _opPackageName.asStateFlow()
@@ -145,21 +129,6 @@
}
.distinctUntilChanged()
- private val _showBpWithoutIconForCredential: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val showBpWithoutIconForCredential = _showBpWithoutIconForCredential.asStateFlow()
-
- override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) {
- val hasCredentialViewShown = kind.value !is PromptKind.Biometric
- val showBpForCredential =
- Flags.customBiometricPrompt() &&
- constraintBp() &&
- !Utils.isBiometricAllowed(promptInfo) &&
- isDeviceCredentialAllowed(promptInfo) &&
- promptInfo.contentView != null &&
- !promptInfo.isContentViewMoreOptionsButtonUsed
- _showBpWithoutIconForCredential.value = showBpForCredential && !hasCredentialViewShown
- }
-
override fun setPrompt(
promptInfo: PromptInfo,
userId: Int,
@@ -167,7 +136,7 @@
kind: PromptKind,
opPackageName: String,
) {
- _kind.value = kind
+ _promptKind.value = kind
_userId.value = userId
_challenge.value = gatekeeperChallenge
_promptInfo.value = promptInfo
@@ -178,7 +147,7 @@
_promptInfo.value = null
_userId.value = null
_challenge.value = null
- _kind.value = PromptKind.Biometric()
+ _promptKind.value = PromptKind.None
_opPackageName.value = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
index b7c0fa8..14ba8a2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -16,10 +16,8 @@
package com.android.systemui.biometrics.domain.interactor
-import android.hardware.biometrics.PromptInfo
import com.android.internal.widget.LockPatternView
import com.android.internal.widget.LockscreenCredential
-import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.data.repository.PromptRepository
import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
@@ -42,12 +40,6 @@
* Business logic for BiometricPrompt's CredentialViews, which primarily includes checking a users
* PIN, pattern, or password credential instead of a biometric.
*
- * This is used to cache the calling app's options that were given to the underlying authenticate
- * APIs and should be set before any UI is shown to the user.
- *
- * There can be at most one request active at a given time. Use [resetPrompt] when no request is
- * active to clear the cache.
- *
* Views that use any biometric should use [PromptSelectorInteractor] instead.
*/
class PromptCredentialInteractor
@@ -74,13 +66,13 @@
biometricPromptRepository.promptInfo,
biometricPromptRepository.challenge,
biometricPromptRepository.userId,
- biometricPromptRepository.kind
- ) { promptInfo, challenge, userId, kind ->
+ biometricPromptRepository.promptKind
+ ) { promptInfo, challenge, userId, promptKind ->
if (promptInfo == null || userId == null || challenge == null) {
return@combine null
}
- when (kind) {
+ when (promptKind) {
PromptKind.Pin ->
BiometricPromptRequest.Credential.Pin(
info = promptInfo,
@@ -137,28 +129,6 @@
private val _verificationError = MutableStateFlow<CredentialStatus.Fail?>(null)
val verificationError: Flow<CredentialStatus.Fail?> = _verificationError.asStateFlow()
- /** Update the current request to use credential-based authentication instead of biometrics. */
- fun useCredentialsForAuthentication(
- promptInfo: PromptInfo,
- @Utils.CredentialType kind: Int,
- userId: Int,
- challenge: Long,
- opPackageName: String,
- ) {
- biometricPromptRepository.setPrompt(
- promptInfo,
- userId,
- challenge,
- kind.asBiometricPromptCredential(),
- opPackageName,
- )
- }
-
- /** Unset the current authentication request. */
- fun resetPrompt() {
- biometricPromptRepository.unsetPrompt()
- }
-
/**
* Check a credential and return the attestation token (HAT) if successful.
*
@@ -231,13 +201,3 @@
_verificationError.value = null
}
}
-
-// TODO(b/251476085): remove along with Utils.CredentialType
-/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */
-private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind =
- when (this) {
- Utils.CREDENTIAL_PIN -> PromptKind.Pin
- Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
- Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
- else -> PromptKind.Biometric()
- }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index 45816c1..4ba780f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics.domain.interactor
+import android.hardware.biometrics.Flags
import android.hardware.biometrics.PromptInfo
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.biometrics.Utils
@@ -53,12 +54,16 @@
/** Static metadata about the current prompt. */
val prompt: Flow<BiometricPromptRequest.Biometric?>
+ /** The kind of prompt to use (biometric, pin, pattern, etc.). */
+ val promptKind: StateFlow<PromptKind>
+
/** If using a credential is allowed. */
val isCredentialAllowed: Flow<Boolean>
/**
- * The kind of credential the user may use as a fallback or [PromptKind.Biometric] if unknown or
- * not [isCredentialAllowed].
+ * The kind of credential the user may use as a fallback or [PromptKind.None] if unknown or not
+ * [isCredentialAllowed]. This is separate from [promptKind], even if [promptKind] is
+ * [PromptKind.Biometric], [credentialKind] should still be one of pin/pattern/password.
*/
val credentialKind: Flow<PromptKind>
@@ -71,34 +76,20 @@
/** Fingerprint sensor type */
val sensorType: Flow<FingerprintSensorType>
- /**
- * If biometric prompt without icon needs to show for displaying content prior to credential
- * view.
- */
- val showBpWithoutIconForCredential: StateFlow<Boolean>
+ /** Switch to the credential view. */
+ fun onSwitchToCredential()
/**
- * Update whether biometric prompt without icon needs to show for displaying content prior to
- * credential view, which should be set before [PromptRepository.setPrompt].
+ * Update the kind of prompt (biometric prompt w/ or w/o sensor icon, pin view, pattern view,
+ * etc).
*/
- fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo)
-
- /** Use biometrics for authentication. */
- fun useBiometricsForAuthentication(
+ fun setPrompt(
promptInfo: PromptInfo,
- userId: Int,
- challenge: Long,
+ effectiveUserId: Int,
modalities: BiometricModalities,
- opPackageName: String,
- )
-
- /** Use credential-based authentication instead of biometrics. */
- fun useCredentialsForAuthentication(
- promptInfo: PromptInfo,
- @Utils.CredentialType kind: Int,
- userId: Int,
challenge: Long,
opPackageName: String,
+ onSwitchToCredential: Boolean,
)
/** Unset the current authentication request. */
@@ -111,7 +102,7 @@
constructor(
fingerprintPropertyRepository: FingerprintPropertyRepository,
private val promptRepository: PromptRepository,
- lockPatternUtils: LockPatternUtils,
+ private val lockPatternUtils: LockPatternUtils,
) : PromptSelectorInteractor {
override val prompt: Flow<BiometricPromptRequest.Biometric?> =
@@ -119,7 +110,7 @@
promptRepository.promptInfo,
promptRepository.challenge,
promptRepository.userId,
- promptRepository.kind,
+ promptRepository.promptKind,
promptRepository.opPackageName,
) { promptInfo, challenge, userId, kind, opPackageName ->
if (
@@ -141,6 +132,8 @@
}
}
+ override val promptKind: StateFlow<PromptKind> = promptRepository.promptKind
+
override val isConfirmationRequired: Flow<Boolean> =
promptRepository.isConfirmationRequired.distinctUntilChanged()
@@ -152,55 +145,61 @@
override val credentialKind: Flow<PromptKind> =
combine(prompt, isCredentialAllowed) { prompt, isAllowed ->
if (prompt != null && isAllowed) {
- when (
- getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId)
- ) {
- Utils.CREDENTIAL_PIN -> PromptKind.Pin
- Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
- Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
- else -> PromptKind.Biometric()
- }
+ getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId)
} else {
- PromptKind.Biometric()
+ PromptKind.None
}
}
override val sensorType: Flow<FingerprintSensorType> = fingerprintPropertyRepository.sensorType
- override val showBpWithoutIconForCredential = promptRepository.showBpWithoutIconForCredential
-
- override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) {
- promptRepository.setShouldShowBpWithoutIconForCredential(promptInfo)
- }
-
- override fun useBiometricsForAuthentication(
- promptInfo: PromptInfo,
- userId: Int,
- challenge: Long,
- modalities: BiometricModalities,
- opPackageName: String,
- ) {
- promptRepository.setPrompt(
- promptInfo = promptInfo,
- userId = userId,
- gatekeeperChallenge = challenge,
- kind = PromptKind.Biometric(modalities),
- opPackageName = opPackageName,
+ override fun onSwitchToCredential() {
+ val modalities: BiometricModalities =
+ if (promptRepository.promptKind.value.isBiometric())
+ (promptRepository.promptKind.value as PromptKind.Biometric).activeModalities
+ else BiometricModalities()
+ setPrompt(
+ promptRepository.promptInfo.value!!,
+ promptRepository.userId.value!!,
+ modalities,
+ promptRepository.challenge.value!!,
+ promptRepository.opPackageName.value!!,
+ true /*onSwitchToCredential*/
)
}
- override fun useCredentialsForAuthentication(
+ override fun setPrompt(
promptInfo: PromptInfo,
- @Utils.CredentialType kind: Int,
- userId: Int,
+ effectiveUserId: Int,
+ modalities: BiometricModalities,
challenge: Long,
opPackageName: String,
+ onSwitchToCredential: Boolean,
) {
+ val hasCredentialViewShown = promptKind.value.isCredential()
+ val showBpForCredential =
+ Flags.customBiometricPrompt() &&
+ com.android.systemui.Flags.constraintBp() &&
+ !Utils.isBiometricAllowed(promptInfo) &&
+ isDeviceCredentialAllowed(promptInfo) &&
+ promptInfo.contentView != null &&
+ !promptInfo.isContentViewMoreOptionsButtonUsed
+ val showBpWithoutIconForCredential = showBpForCredential && !hasCredentialViewShown
+ var kind: PromptKind = PromptKind.None
+ if (onSwitchToCredential) {
+ kind = getCredentialType(lockPatternUtils, effectiveUserId)
+ } else if (Utils.isBiometricAllowed(promptInfo) || showBpWithoutIconForCredential) {
+ // TODO(b/330908557): check to show one pane or two pane
+ kind = PromptKind.Biometric(modalities)
+ } else if (isDeviceCredentialAllowed(promptInfo)) {
+ kind = getCredentialType(lockPatternUtils, effectiveUserId)
+ }
+
promptRepository.setPrompt(
promptInfo = promptInfo,
- userId = userId,
+ userId = effectiveUserId,
gatekeeperChallenge = challenge,
- kind = kind.asBiometricPromptCredential(),
+ kind = kind,
opPackageName = opPackageName,
)
}
@@ -209,13 +208,3 @@
promptRepository.unsetPrompt()
}
}
-
-// TODO(b/251476085): remove along with Utils.CredentialType
-/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */
-private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind =
- when (this) {
- Utils.CREDENTIAL_PIN -> PromptKind.Pin
- Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
- Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
- else -> PromptKind.Biometric()
- }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index da56951..65c5b6b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -230,7 +230,7 @@
}
lifecycleScope.launch {
- viewModel.showBpWithoutIconForCredential.collect { showWithoutIcon ->
+ viewModel.hideSensorIcon.collect { showWithoutIcon ->
if (!showWithoutIcon) {
PromptIconViewBinder.bind(
iconView,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index d1ad783..f0969ed 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -55,6 +55,7 @@
import com.android.systemui.res.R
import kotlin.math.abs
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
/** Helper for [BiometricViewBinder] to handle resize transitions. */
@@ -169,14 +170,14 @@
val flipConstraintSet = ConstraintSet()
view.doOnLayout {
- fun setVisibilities(size: PromptSize) {
+ fun setVisibilities(hideSensorIcon: Boolean, size: PromptSize) {
viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) }
largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
largeConstraintSet.setVisibility(R.id.scrollView, View.GONE)
- if (viewModel.showBpWithoutIconForCredential.value) {
+ if (hideSensorIcon) {
smallConstraintSet.setVisibility(iconHolderView.id, View.GONE)
smallConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
smallConstraintSet.setVisibility(R.id.indicator, View.GONE)
@@ -362,12 +363,16 @@
}
}
}
+ lifecycleScope.launch {
+ combine(viewModel.hideSensorIcon, viewModel.size, ::Pair).collect {
+ (hideSensorIcon, size) ->
+ setVisibilities(hideSensorIcon, size)
+ }
+ }
lifecycleScope.launch {
combine(viewModel.position, viewModel.size, ::Pair).collect {
(position, size) ->
- setVisibilities(size)
-
if (position.isLeft) {
if (size.isSmall) {
flipConstraintSet.clone(smallConstraintSet)
@@ -481,7 +486,7 @@
v.showContentOrHide(forceHide = size.isSmall)
}
- if (viewModel.showBpWithoutIconForCredential.value) {
+ if (viewModel.hideSensorIcon.first()) {
iconHolderView.visibility = View.GONE
}
@@ -492,10 +497,6 @@
viewsToFadeInOnSizeChange.forEach { it.alpha = 0f }
}
- // TODO(b/302735104): Fix wrong height due to the delay of
- // PromptContentView. addOnLayoutChangeListener() will cause crash
- // when showing credential view, since |PromptIconViewModel| won't
- // release the flow.
// propagate size changes to legacy panel controller and animate
// transitions
view.doOnLayout {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 2104f3e..a8c5976 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -67,7 +67,7 @@
@Inject
constructor(
displayStateInteractor: DisplayStateInteractor,
- promptSelectorInteractor: PromptSelectorInteractor,
+ private val promptSelectorInteractor: PromptSelectorInteractor,
@Application private val context: Context,
private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
private val biometricStatusInteractor: BiometricStatusInteractor,
@@ -195,8 +195,11 @@
/** The kind of credential the user has. */
val credentialKind: Flow<PromptKind> = promptSelectorInteractor.credentialKind
- val showBpWithoutIconForCredential: StateFlow<Boolean> =
- promptSelectorInteractor.showBpWithoutIconForCredential
+ /** The kind of prompt to use (biometric, pin, pattern, etc.). */
+ val promptKind: StateFlow<PromptKind> = promptSelectorInteractor.promptKind
+
+ /** Whether the sensor icon on biometric prompt ui should be hidden. */
+ val hideSensorIcon: Flow<Boolean> = modalities.map { it.isEmpty }.distinctUntilChanged()
/** The label to use for the cancel button. */
val negativeButtonText: Flow<String> =
@@ -896,6 +899,7 @@
*/
fun onSwitchToCredential() {
_forceLargeSize.value = true
+ promptSelectorInteractor.onSwitchToCredential()
}
private fun vibrateOnSuccess() {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index b42a903..5653bc2 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -399,12 +399,13 @@
}
private boolean shouldBeRegisteredToSensors() {
- return mScreenOn
- && (mState == StatusBarState.KEYGUARD
- || (mState == StatusBarState.SHADE
- && mKeyguardStateController.isOccluded()
- && mKeyguardStateController.isShowing()))
- && !mShowingAod;
+ final boolean isKeyguard = mState == StatusBarState.KEYGUARD;
+
+ final boolean isShadeOverOccludedKeyguard = mState == StatusBarState.SHADE
+ && mKeyguardStateController.isShowing()
+ && mKeyguardStateController.isOccluded();
+
+ return mScreenOn && !mShowingAod && (isKeyguard || isShadeOverOccludedKeyguard);
}
private void updateSensorRegistration() {
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt
index 5c64dc6..1c16429 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt
@@ -18,6 +18,7 @@
import android.os.UserHandle
import com.android.systemui.common.shared.model.PackageChangeModel
+import com.android.systemui.common.shared.model.PackageInstallSession
import kotlinx.coroutines.flow.Flow
interface PackageChangeRepository {
@@ -28,4 +29,7 @@
* [UserHandle.USER_ALL] may be used to listen to all users.
*/
fun packageChanged(user: UserHandle): Flow<PackageChangeModel>
+
+ /** Emits a list of all known install sessions associated with the primary user. */
+ val packageInstallSessionsForPrimaryUser: Flow<List<PackageInstallSession>>
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt
index 712a352..41b03f1 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt
@@ -18,6 +18,7 @@
import android.os.UserHandle
import com.android.systemui.common.shared.model.PackageChangeModel
+import com.android.systemui.common.shared.model.PackageInstallSession
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -27,6 +28,7 @@
class PackageChangeRepositoryImpl
@Inject
constructor(
+ packageInstallerMonitor: PackageInstallerMonitor,
private val monitorFactory: PackageUpdateMonitor.Factory,
) : PackageChangeRepository {
/**
@@ -37,4 +39,7 @@
override fun packageChanged(user: UserHandle): Flow<PackageChangeModel> =
monitor.packageChanged.filter { user == UserHandle.ALL || user == it.user }
+
+ override val packageInstallSessionsForPrimaryUser: Flow<List<PackageInstallSession>> =
+ packageInstallerMonitor.installSessionsForPrimaryUser
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
new file mode 100644
index 0000000..46db346
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.data.repository
+
+import android.content.pm.PackageInstaller
+import android.os.Handler
+import com.android.internal.annotations.GuardedBy
+import com.android.systemui.common.shared.model.PackageInstallSession
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.PackageChangeRepoLog
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.dropWhile
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+
+/** Monitors package install sessions for all users. */
+@SysUISingleton
+class PackageInstallerMonitor
+@Inject
+constructor(
+ @Background private val bgHandler: Handler,
+ @Background private val bgScope: CoroutineScope,
+ @PackageChangeRepoLog logBuffer: LogBuffer,
+ private val packageInstaller: PackageInstaller,
+) : PackageInstaller.SessionCallback() {
+
+ private val logger = Logger(logBuffer, TAG)
+
+ @GuardedBy("sessions") private val sessions = mutableMapOf<Int, PackageInstallSession>()
+
+ private val _installSessions =
+ MutableStateFlow<List<PackageInstallSession>>(emptyList()).apply {
+ subscriptionCount
+ .map { count -> count > 0 }
+ .distinctUntilChanged()
+ // Drop initial false value
+ .dropWhile { !it }
+ .onEach { isActive ->
+ if (isActive) {
+ synchronized(sessions) {
+ sessions.putAll(
+ packageInstaller.allSessions
+ .map { session -> session.toModel() }
+ .associateBy { it.sessionId }
+ )
+ updateInstallerSessionsFlow()
+ }
+ packageInstaller.registerSessionCallback(
+ this@PackageInstallerMonitor,
+ bgHandler
+ )
+ } else {
+ synchronized(sessions) {
+ sessions.clear()
+ updateInstallerSessionsFlow()
+ }
+ packageInstaller.unregisterSessionCallback(this@PackageInstallerMonitor)
+ }
+ }
+ .launchIn(bgScope)
+ }
+
+ val installSessionsForPrimaryUser: Flow<List<PackageInstallSession>> =
+ _installSessions.asStateFlow()
+
+ /** Called when a new installer session is created. */
+ override fun onCreated(sessionId: Int) {
+ logger.i({ "session created $int1" }) { int1 = sessionId }
+ updateSession(sessionId)
+ }
+
+ /** Called when new installer session has finished. */
+ override fun onFinished(sessionId: Int, success: Boolean) {
+ logger.i({ "session finished $int1" }) { int1 = sessionId }
+ synchronized(sessions) {
+ sessions.remove(sessionId)
+ updateInstallerSessionsFlow()
+ }
+ }
+
+ /**
+ * Badging details for the session changed. For example, the app icon or label has been updated.
+ */
+ override fun onBadgingChanged(sessionId: Int) {
+ logger.i({ "session badging changed $int1" }) { int1 = sessionId }
+ updateSession(sessionId)
+ }
+
+ /**
+ * A session is considered active when there is ongoing forward progress being made. For
+ * example, a package started downloading.
+ */
+ override fun onActiveChanged(sessionId: Int, active: Boolean) {
+ // Active status updates are not tracked for now
+ }
+
+ override fun onProgressChanged(sessionId: Int, progress: Float) {
+ // Progress updates are not tracked for now
+ }
+
+ private fun updateSession(sessionId: Int) {
+ val session = packageInstaller.getSessionInfo(sessionId)
+
+ synchronized(sessions) {
+ if (session == null) {
+ sessions.remove(sessionId)
+ } else {
+ sessions[sessionId] = session.toModel()
+ }
+ updateInstallerSessionsFlow()
+ }
+ }
+
+ @GuardedBy("sessions")
+ private fun updateInstallerSessionsFlow() {
+ _installSessions.value = sessions.values.toList()
+ }
+
+ companion object {
+ const val TAG = "PackageInstallerMonitor"
+
+ private fun PackageInstaller.SessionInfo.toModel(): PackageInstallSession {
+ return PackageInstallSession(
+ sessionId = this.sessionId,
+ packageName = this.appPackageName,
+ icon = this.getAppIcon(),
+ user = this.user,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/PackageInstallSession.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/PackageInstallSession.kt
new file mode 100644
index 0000000..7025229
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/PackageInstallSession.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.shared.model
+
+import android.graphics.Bitmap
+import android.os.UserHandle
+
+/** Represents a session of a package being installed on device. */
+data class PackageInstallSession(
+ val sessionId: Int,
+ val packageName: String,
+ val icon: Bitmap?,
+ val user: UserHandle,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
index 9e7fb4e..153b7aa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -20,6 +20,8 @@
import android.app.DreamManager
import com.android.systemui.CoreStartable
import com.android.systemui.Flags.communalHub
+import com.android.systemui.Flags.restartDreamOnUnocclude
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -27,8 +29,10 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
+import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -43,6 +47,7 @@
private val powerInteractor: PowerInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val communalInteractor: CommunalInteractor,
private val dreamManager: DreamManager,
@Background private val bgScope: CoroutineScope,
) : CoreStartable {
@@ -52,6 +57,19 @@
return
}
+ // Return to dream from occluded when not already dreaming.
+ if (restartDreamOnUnocclude()) {
+ keyguardTransitionInteractor.startedKeyguardTransitionStep
+ .sample(keyguardInteractor.isDreaming, ::Pair)
+ .filter {
+ it.first.from == KeyguardState.OCCLUDED &&
+ it.first.to == KeyguardState.DREAMING &&
+ !it.second
+ }
+ .onEach { dreamManager.startDream() }
+ .launchIn(bgScope)
+ }
+
// Restart the dream underneath the hub in order to support the ability to swipe
// away the hub to enter the dream.
keyguardTransitionInteractor.finishedKeyguardState
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index 40d7440..b27fcfc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -126,7 +126,7 @@
private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
getSharedPrefsForUser(user)
- .observe(CTA_DISMISSED_STATE)
+ .observe()
// Emit at the start of collection to ensure we get an initial value
.onStart { emit(Unit) }
.map { getCtaDismissedState() }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index 1f54e70..fdb797d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -17,14 +17,13 @@
package com.android.systemui.communal.data.repository
import android.app.backup.BackupManager
-import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
import android.os.UserHandle
-import androidx.annotation.WorkerThread
+import com.android.systemui.common.data.repository.PackageChangeRepository
+import com.android.systemui.common.shared.model.PackageInstallSession
import com.android.systemui.communal.data.backup.CommunalBackupUtils
-import com.android.systemui.communal.data.db.CommunalItemRank
import com.android.systemui.communal.data.db.CommunalWidgetDao
-import com.android.systemui.communal.data.db.CommunalWidgetItem
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.proto.toCommunalHubState
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
@@ -36,13 +35,15 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.util.kotlin.getValue
-import java.util.Optional
import javax.inject.Inject
import kotlin.coroutines.cancellation.CancellationException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -88,7 +89,6 @@
class CommunalWidgetRepositoryImpl
@Inject
constructor(
- appWidgetManagerOptional: Optional<AppWidgetManager>,
private val appWidgetHost: CommunalAppWidgetHost,
@Background private val bgScope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
@@ -97,6 +97,7 @@
@CommunalLog logBuffer: LogBuffer,
private val backupManager: BackupManager,
private val backupUtils: CommunalBackupUtils,
+ packageChangeRepository: PackageChangeRepository,
) : CommunalWidgetRepository {
companion object {
const val TAG = "CommunalWidgetRepository"
@@ -104,12 +105,39 @@
private val logger = Logger(logBuffer, TAG)
- private val appWidgetManager by appWidgetManagerOptional
+ /** Widget metadata from database + matching [AppWidgetProviderInfo] if any. */
+ private val widgetEntries: Flow<List<CommunalWidgetEntry>> =
+ combine(
+ communalWidgetDao.getWidgets(),
+ communalWidgetHost.appWidgetProviders,
+ ) { entries, providers ->
+ entries.mapNotNull { (rank, widget) ->
+ CommunalWidgetEntry(
+ appWidgetId = widget.widgetId,
+ componentName = widget.componentName,
+ priority = rank.rank,
+ providerInfo = providers[widget.widgetId]
+ )
+ }
+ }
+ @OptIn(ExperimentalCoroutinesApi::class)
override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
- communalWidgetDao
- .getWidgets()
- .map { it.mapNotNull(::mapToContentModel) }
+ widgetEntries
+ .flatMapLatest { widgetEntries ->
+ // If and only if any widget is missing provider info, combine with the package
+ // installer sessions flow to check whether they are pending installation. This can
+ // happen after widgets are freshly restored from a backup. In most cases, provider
+ // info is available to all widgets, and is unnecessary to involve an API call to
+ // the package installer.
+ if (widgetEntries.any { it.providerInfo == null }) {
+ packageChangeRepository.packageInstallSessionsForPrimaryUser.map { sessions ->
+ widgetEntries.mapNotNull { entry -> mapToContentModel(entry, sessions) }
+ }
+ } else {
+ flowOf(widgetEntries.map(::mapToContentModel))
+ }
+ }
// As this reads from a database and triggers IPCs to AppWidgetManager,
// it should be executed in the background.
.flowOn(bgDispatcher)
@@ -245,6 +273,9 @@
}
appWidgetHost.deleteAppWidgetId(widgetId)
}
+
+ // Providers may have changed
+ communalWidgetHost.refreshProviders()
}
}
@@ -255,16 +286,57 @@
}
}
- @WorkerThread
- private fun mapToContentModel(
- entry: Map.Entry<CommunalItemRank, CommunalWidgetItem>
- ): CommunalWidgetContentModel? {
- val (_, widgetId) = entry.value
- val providerInfo = appWidgetManager?.getAppWidgetInfo(widgetId) ?: return null
- return CommunalWidgetContentModel(
- appWidgetId = widgetId,
- providerInfo = providerInfo,
- priority = entry.key.rank,
+ /**
+ * Maps a [CommunalWidgetEntry] to a [CommunalWidgetContentModel] with the assumption that the
+ * [AppWidgetProviderInfo] of the entry is available.
+ */
+ private fun mapToContentModel(entry: CommunalWidgetEntry): CommunalWidgetContentModel {
+ return CommunalWidgetContentModel.Available(
+ appWidgetId = entry.appWidgetId,
+ providerInfo = entry.providerInfo!!,
+ priority = entry.priority,
)
}
+
+ /**
+ * Maps a [CommunalWidgetEntry] to a [CommunalWidgetContentModel] with a list of install
+ * sessions. If the [AppWidgetProviderInfo] of the entry is absent, and its package is in the
+ * install sessions, the entry is mapped to a pending widget.
+ */
+ private fun mapToContentModel(
+ entry: CommunalWidgetEntry,
+ installSessions: List<PackageInstallSession>,
+ ): CommunalWidgetContentModel? {
+ if (entry.providerInfo != null) {
+ return CommunalWidgetContentModel.Available(
+ appWidgetId = entry.appWidgetId,
+ providerInfo = entry.providerInfo!!,
+ priority = entry.priority,
+ )
+ }
+
+ val session =
+ installSessions.firstOrNull {
+ it.packageName ==
+ ComponentName.unflattenFromString(entry.componentName)?.packageName
+ }
+ return if (session != null) {
+ CommunalWidgetContentModel.Pending(
+ appWidgetId = entry.appWidgetId,
+ priority = entry.priority,
+ packageName = session.packageName,
+ icon = session.icon,
+ user = session.user,
+ )
+ } else {
+ null
+ }
+ }
+
+ private data class CommunalWidgetEntry(
+ val appWidgetId: Int,
+ val componentName: String,
+ val priority: Int,
+ var providerInfo: AppWidgetProviderInfo? = null,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 5091a99..06c8396 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -163,6 +163,13 @@
initialValue = false,
)
+ /** Whether to start dreaming when returning from occluded */
+ val dreamFromOccluded: Flow<Boolean> =
+ keyguardTransitionInteractor
+ .transitionStepsToState(KeyguardState.OCCLUDED)
+ .map { it.from == KeyguardState.DREAMING }
+ .stateIn(scope = applicationScope, SharingStarted.Eagerly, false)
+
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
*
@@ -403,19 +410,30 @@
updateOnWorkProfileBroadcastReceived,
) { widgets, allowedCategories, _ ->
widgets.map { widget ->
- if (widget.providerInfo.widgetCategory and allowedCategories != 0) {
- // At least one category this widget specified is allowed, so show it
- WidgetContent.Widget(
- appWidgetId = widget.appWidgetId,
- providerInfo = widget.providerInfo,
- appWidgetHost = appWidgetHost,
- inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
- )
- } else {
- WidgetContent.DisabledWidget(
- appWidgetId = widget.appWidgetId,
- providerInfo = widget.providerInfo,
- )
+ when (widget) {
+ is CommunalWidgetContentModel.Available -> {
+ if (widget.providerInfo.widgetCategory and allowedCategories != 0) {
+ // At least one category this widget specified is allowed, so show it
+ WidgetContent.Widget(
+ appWidgetId = widget.appWidgetId,
+ providerInfo = widget.providerInfo,
+ appWidgetHost = appWidgetHost,
+ inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
+ )
+ } else {
+ WidgetContent.DisabledWidget(
+ appWidgetId = widget.appWidgetId,
+ providerInfo = widget.providerInfo,
+ )
+ }
+ }
+ is CommunalWidgetContentModel.Pending -> {
+ WidgetContent.PendingWidget(
+ appWidgetId = widget.appWidgetId,
+ packageName = widget.packageName,
+ icon = widget.icon,
+ )
+ }
}
}
}
@@ -430,7 +448,15 @@
} else {
// Get associated work profile for the currently selected user.
val workProfile = userTracker.userProfiles.find { it.isManagedProfile }
- list.filter { it.providerInfo.profile.identifier != workProfile?.id }
+ list.filter { model ->
+ val uid =
+ when (model) {
+ is CommunalWidgetContentModel.Available ->
+ model.providerInfo.profile.identifier
+ is CommunalWidgetContentModel.Pending -> model.user.identifier
+ }
+ uid != workProfile?.id
+ }
}
/** A flow of available smartspace targets. Currently only showing timers. */
@@ -513,7 +539,11 @@
): List<CommunalWidgetContentModel> {
val currentUserIds = userTracker.userProfiles.map { it.id }.toSet()
return list.filter { widget ->
- currentUserIds.contains(widget.providerInfo.profile?.identifier)
+ when (widget) {
+ is CommunalWidgetContentModel.Available ->
+ currentUserIds.contains(widget.providerInfo.profile?.identifier)
+ is CommunalWidgetContentModel.Pending -> true
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 7061227..122240d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -19,6 +19,7 @@
import android.appwidget.AppWidgetProviderInfo
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
import android.content.pm.ApplicationInfo
+import android.graphics.Bitmap
import android.widget.RemoteViews
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
@@ -45,11 +46,10 @@
sealed interface WidgetContent : CommunalContentModel {
val appWidgetId: Int
- val providerInfo: AppWidgetProviderInfo
data class Widget(
override val appWidgetId: Int,
- override val providerInfo: AppWidgetProviderInfo,
+ val providerInfo: AppWidgetProviderInfo,
val appWidgetHost: CommunalAppWidgetHost,
val inQuietMode: Boolean,
) : WidgetContent {
@@ -66,7 +66,7 @@
data class DisabledWidget(
override val appWidgetId: Int,
- override val providerInfo: AppWidgetProviderInfo
+ val providerInfo: AppWidgetProviderInfo
) : WidgetContent {
override val key = KEY.disabledWidget(appWidgetId)
// Widget size is always half.
@@ -75,6 +75,16 @@
val appInfo: ApplicationInfo?
get() = providerInfo.providerInfo?.applicationInfo
}
+
+ data class PendingWidget(
+ override val appWidgetId: Int,
+ val packageName: String,
+ val icon: Bitmap? = null,
+ ) : WidgetContent {
+ override val key = KEY.pendingWidget(appWidgetId)
+ // Widget size is always half.
+ override val size = CommunalContentSize.HALF
+ }
}
/** A placeholder item representing a new widget being added */
@@ -127,6 +137,10 @@
return "disabled_widget_$id"
}
+ fun pendingWidget(id: Int): String {
+ return "pending_widget_$id"
+ }
+
fun widgetPlaceholder(): String {
return "widget_placeholder_${UUID.randomUUID()}"
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index e141dc4..53aecc1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -17,10 +17,27 @@
package com.android.systemui.communal.shared.model
import android.appwidget.AppWidgetProviderInfo
+import android.graphics.Bitmap
+import android.os.UserHandle
/** Encapsulates data for a communal widget. */
-data class CommunalWidgetContentModel(
- val appWidgetId: Int,
- val providerInfo: AppWidgetProviderInfo,
- val priority: Int,
-)
+sealed interface CommunalWidgetContentModel {
+ val appWidgetId: Int
+ val priority: Int
+
+ /** Widget is ready to display */
+ data class Available(
+ override val appWidgetId: Int,
+ val providerInfo: AppWidgetProviderInfo,
+ override val priority: Int,
+ ) : CommunalWidgetContentModel
+
+ /** Widget is pending installation */
+ data class Pending(
+ override val appWidgetId: Int,
+ override val priority: Int,
+ val packageName: String,
+ val icon: Bitmap?,
+ val user: UserHandle,
+ ) : CommunalWidgetContentModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 3f92223..f6122ad 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -104,7 +104,12 @@
): Boolean =
withContext(backgroundDispatcher) {
val widgets = communalInteractor.widgetContent.first()
- val excludeList = widgets.mapTo(ArrayList()) { it.providerInfo }
+ val excludeList =
+ widgets.filterIsInstance<CommunalContentModel.WidgetContent.Widget>().mapTo(
+ ArrayList()
+ ) {
+ it.providerInfo
+ }
getWidgetPickerActivityIntent(resources, packageManager, excludeList)?.let {
try {
activityLauncher.launch(it)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index 337d873..9114aab 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -33,6 +33,7 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
/** View model for transitions related to the communal hub. */
@@ -49,6 +50,27 @@
communalInteractor: CommunalInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
+ // Show UMO on glanceable hub immediately on transition into glanceable hub
+ private val showUmoFromOccludedToGlanceableHub: Flow<Boolean> =
+ keyguardTransitionInteractor
+ .transitionStepsFromState(KeyguardState.OCCLUDED)
+ .filter {
+ it.to == KeyguardState.GLANCEABLE_HUB &&
+ (it.transitionState == TransitionState.STARTED ||
+ it.transitionState == TransitionState.CANCELED)
+ }
+ .map { it.transitionState == TransitionState.STARTED }
+
+ private val showUmoFromGlanceableHubToOccluded: Flow<Boolean> =
+ keyguardTransitionInteractor
+ .transitionStepsFromState(KeyguardState.GLANCEABLE_HUB)
+ .filter {
+ it.to == KeyguardState.OCCLUDED &&
+ (it.transitionState == TransitionState.FINISHED ||
+ it.transitionState == TransitionState.CANCELED)
+ }
+ .map { it.transitionState != TransitionState.FINISHED }
+
/**
* Whether UMO location should be on communal. This flow is responsive to transitions so that a
* new value is emitted at the right step of a transition to/from communal hub that the location
@@ -60,6 +82,8 @@
glanceableHubToLockscreenTransitionViewModel.showUmo,
dreamToGlanceableHubTransitionViewModel.showUmo,
glanceableHubToDreamTransitionViewModel.showUmo,
+ showUmoFromOccludedToGlanceableHub,
+ showUmoFromGlanceableHubToOccluded,
)
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
index 5f1d89e..b7e8205 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -24,6 +24,7 @@
import android.widget.RemoteViews
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
+import javax.annotation.concurrent.GuardedBy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
@@ -47,6 +48,8 @@
/** App widget ids that have been removed and no longer available. */
val appWidgetIdToRemove: SharedFlow<Int> = _appWidgetIdToRemove.asSharedFlow()
+ @GuardedBy("observers") private val observers = mutableSetOf<Observer>()
+
override fun onCreateView(
context: Context,
appWidgetId: Int,
@@ -77,6 +80,61 @@
}
}
+ override fun allocateAppWidgetId(): Int {
+ return super.allocateAppWidgetId().also { appWidgetId ->
+ backgroundScope.launch {
+ observers.forEach { observer -> observer.onAllocateAppWidgetId(appWidgetId) }
+ }
+ }
+ }
+
+ override fun deleteAppWidgetId(appWidgetId: Int) {
+ super.deleteAppWidgetId(appWidgetId)
+ backgroundScope.launch {
+ observers.forEach { observer -> observer.onDeleteAppWidgetId(appWidgetId) }
+ }
+ }
+
+ override fun startListening() {
+ super.startListening()
+ backgroundScope.launch { observers.forEach { observer -> observer.onHostStartListening() } }
+ }
+
+ override fun stopListening() {
+ super.stopListening()
+ backgroundScope.launch { observers.forEach { observer -> observer.onHostStopListening() } }
+ }
+
+ fun addObserver(observer: Observer) {
+ synchronized(observers) { observers.add(observer) }
+ }
+
+ fun removeObserver(observer: Observer) {
+ synchronized(observers) { observers.remove(observer) }
+ }
+
+ /**
+ * Allows another class to observe the [CommunalAppWidgetHost] and handle any logic there.
+ *
+ * This is mainly for testability as it is difficult to test a real instance of [AppWidgetHost]
+ * which communicates with framework services.
+ *
+ * Note: all the callbacks are launched from the background scope.
+ */
+ interface Observer {
+ /** Called immediately after the host has started listening for widget updates. */
+ fun onHostStartListening() {}
+
+ /** Called immediately after the host has stopped listening for widget updates. */
+ fun onHostStopListening() {}
+
+ /** Called immediately after a new app widget id has been allocated. */
+ fun onAllocateAppWidgetId(appWidgetId: Int) {}
+
+ /** Called immediately after an app widget id is to be deleted. */
+ fun onDeleteAppWidgetId(appWidgetId: Int) {}
+ }
+
companion object {
private const val TAG = "CommunalAppWidgetHost"
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
index 2ccab07..301da51 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -39,6 +39,7 @@
@Inject
constructor(
private val appWidgetHost: CommunalAppWidgetHost,
+ private val communalWidgetHost: CommunalWidgetHost,
private val communalInteractor: CommunalInteractor,
private val userTracker: UserTracker,
@Background private val bgScope: CoroutineScope,
@@ -70,9 +71,11 @@
// Always ensure this is called on the main/ui thread.
withContext(uiDispatcher) {
if (active) {
+ communalWidgetHost.startObservingHost()
appWidgetHost.startListening()
} else {
appWidgetHost.stopListening()
+ communalWidgetHost.stopObservingHost()
}
}
@@ -83,7 +86,15 @@
private fun validateWidgetsAndDeleteOrphaned(widgets: List<CommunalWidgetContentModel>) {
val currentUserIds = userTracker.userProfiles.map { it.id }.toSet()
widgets
- .filter { widget -> !currentUserIds.contains(widget.providerInfo.profile?.identifier) }
+ .filter { widget ->
+ val uid =
+ when (widget) {
+ is CommunalWidgetContentModel.Available ->
+ widget.providerInfo.profile?.identifier
+ is CommunalWidgetContentModel.Pending -> widget.user.identifier
+ }
+ !currentUserIds.contains(uid)
+ }
.onEach { widget -> communalInteractor.deleteWidget(id = widget.appWidgetId) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
index 93e2b37..42107c1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.widgets
+import android.appwidget.AppWidgetHost.AppWidgetHostListener
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
@@ -23,6 +24,9 @@
import android.content.ComponentName
import android.os.Bundle
import android.os.UserHandle
+import android.widget.RemoteViews
+import androidx.annotation.WorkerThread
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -30,6 +34,11 @@
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
/**
* Widget host that interacts with AppWidget service and host to bind and provide info for widgets
@@ -38,11 +47,12 @@
class CommunalWidgetHost
@Inject
constructor(
+ @Background private val bgScope: CoroutineScope,
private val appWidgetManager: Optional<AppWidgetManager>,
private val appWidgetHost: CommunalAppWidgetHost,
private val selectedUserInteractor: SelectedUserInteractor,
@CommunalLog logBuffer: LogBuffer,
-) {
+) : CommunalAppWidgetHost.Observer {
companion object {
private const val TAG = "CommunalWidgetHost"
@@ -60,6 +70,19 @@
private val logger = Logger(logBuffer, TAG)
+ private val _appWidgetProviders = MutableStateFlow(emptyMap<Int, AppWidgetProviderInfo?>())
+
+ /**
+ * A flow of mappings between an appWidgetId and its corresponding [AppWidgetProviderInfo].
+ * These [AppWidgetProviderInfo]s represent app widgets that are actively bound to the
+ * [CommunalAppWidgetHost].
+ *
+ * The [AppWidgetProviderInfo] may be null in the case that the widget is bound but its provider
+ * is unavailable. For example, its package is not installed.
+ */
+ val appWidgetProviders: StateFlow<Map<Int, AppWidgetProviderInfo?>> =
+ _appWidgetProviders.asStateFlow()
+
/**
* Allocate an app widget id and binds the widget with the provider and associated user.
*
@@ -77,6 +100,7 @@
)
) {
logger.d("Successfully bound the widget $provider")
+ onProviderInfoUpdated(id, getAppWidgetInfo(id))
return id
}
appWidgetHost.deleteAppWidgetId(id)
@@ -100,7 +124,83 @@
return false
}
+ @WorkerThread
fun getAppWidgetInfo(widgetId: Int): AppWidgetProviderInfo? {
return appWidgetManager.getOrNull()?.getAppWidgetInfo(widgetId)
}
+
+ fun startObservingHost() {
+ appWidgetHost.addObserver(this@CommunalWidgetHost)
+ }
+
+ fun stopObservingHost() {
+ appWidgetHost.removeObserver(this@CommunalWidgetHost)
+ }
+
+ fun refreshProviders() {
+ bgScope.launch {
+ val newProviders = mutableMapOf<Int, AppWidgetProviderInfo?>()
+ appWidgetHost.appWidgetIds.forEach { appWidgetId ->
+ // Listen for updates from each bound widget
+ addListener(appWidgetId)
+
+ // Fetch provider info of the widget
+ newProviders[appWidgetId] = getAppWidgetInfo(appWidgetId)
+ }
+
+ _appWidgetProviders.value = newProviders.toMap()
+ }
+ }
+
+ override fun onHostStartListening() {
+ refreshProviders()
+ }
+
+ override fun onHostStopListening() {
+ // Remove listeners
+ _appWidgetProviders.value.keys.forEach { appWidgetId ->
+ appWidgetHost.removeListener(appWidgetId)
+ }
+
+ // Clear providers
+ _appWidgetProviders.value = emptyMap()
+ }
+
+ override fun onAllocateAppWidgetId(appWidgetId: Int) {
+ addListener(appWidgetId)
+ }
+
+ override fun onDeleteAppWidgetId(appWidgetId: Int) {
+ appWidgetHost.removeListener(appWidgetId)
+ _appWidgetProviders.value =
+ _appWidgetProviders.value.toMutableMap().also { it.remove(appWidgetId) }
+ }
+
+ private fun addListener(appWidgetId: Int) {
+ appWidgetHost.setListener(
+ appWidgetId,
+ CommunalAppWidgetHostListener(appWidgetId, this::onProviderInfoUpdated),
+ )
+ }
+
+ private fun onProviderInfoUpdated(appWidgetId: Int, providerInfo: AppWidgetProviderInfo?) {
+ bgScope.launch {
+ _appWidgetProviders.value =
+ _appWidgetProviders.value.toMutableMap().also { it[appWidgetId] = providerInfo }
+ }
+ }
+
+ /** A [AppWidgetHostListener] for [appWidgetId]. */
+ private class CommunalAppWidgetHostListener(
+ private val appWidgetId: Int,
+ private val onUpdateProviderInfo: (Int, AppWidgetProviderInfo?) -> Unit,
+ ) : AppWidgetHostListener {
+ override fun onUpdateProviderInfo(providerInfo: AppWidgetProviderInfo?) {
+ onUpdateProviderInfo(appWidgetId, providerInfo)
+ }
+
+ override fun onViewDataChanged(viewId: Int) {}
+
+ override fun updateAppWidget(remoteViews: RemoteViews?) {}
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
index aa6516d..2000f96 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
@@ -69,16 +69,18 @@
@SysUISingleton
@Provides
fun provideCommunalWidgetHost(
+ @Application applicationScope: CoroutineScope,
appWidgetManager: Optional<AppWidgetManager>,
appWidgetHost: CommunalAppWidgetHost,
selectedUserInteractor: SelectedUserInteractor,
@CommunalLog logBuffer: LogBuffer,
): CommunalWidgetHost {
return CommunalWidgetHost(
+ applicationScope,
appWidgetManager,
appWidgetHost,
selectedUserInteractor,
- logBuffer
+ logBuffer,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index 7c2dae3..060a331 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -25,10 +25,10 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
+import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
class AuthorizedPanelsRepositoryImpl
@Inject
@@ -40,7 +40,7 @@
override fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>> {
val prefs = instantiateSharedPrefs(user)
- return prefs.observe(KEY).onStart { emit(Unit) }.map { getAuthorizedPanelsInternal(prefs) }
+ return prefs.observe().emitOnStart().map { getAuthorizedPanelsInternal(prefs) }
}
override fun getAuthorizedPanels(): Set<String> {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
index 9be04940..691ec76 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -26,12 +26,12 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
+import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
@SysUISingleton
@@ -63,8 +63,8 @@
): Flow<SelectedComponentRepository.SelectedComponent?> {
val prefs = getSharedPreferencesForUser(userHandle.identifier)
return prefs
- .observe(PREF_COMPONENT)
- .onStart { emit(Unit) }
+ .observe()
+ .emitOnStart()
.map { getSelectedComponent(userHandle) }
.flowOn(bgDispatcher)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index ef3f10f..11e6f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -49,6 +49,7 @@
import android.content.om.OverlayManager;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
import android.content.res.AssetManager;
@@ -224,6 +225,13 @@
@Provides
@Singleton
+ static UserScopedService<ColorDisplayManager> provideScopedColorDisplayManager(
+ Context context) {
+ return new UserScopedServiceImpl<>(context, ColorDisplayManager.class);
+ }
+
+ @Provides
+ @Singleton
static CrossWindowBlurListeners provideCrossWindowBlurListeners() {
return CrossWindowBlurListeners.getInstance();
}
@@ -483,6 +491,12 @@
@Provides
@Singleton
+ static PackageInstaller providePackageInstaller(PackageManager packageManager) {
+ return packageManager.getPackageInstaller();
+ }
+
+ @Provides
+ @Singleton
static PackageManagerWrapper providePackageManagerWrapper() {
return PackageManagerWrapper.getInstance();
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index 0fd6887..8c3de4b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -89,8 +89,8 @@
dozeFalsingManagerAdapter,
dozeTriggers,
dozeUi,
- dozeScreenState,
dozeScreenBrightness,
+ dozeScreenState,
dozeWallpaperState,
dozeDockHandler,
dozeAuthRemover,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 8c0a73c..6e04339 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -198,7 +198,6 @@
mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
mBouncerlessScrimController = bouncerlessScrimController;
- mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
@@ -234,6 +233,7 @@
mJitterStartTimeMillis = System.currentTimeMillis();
mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
+ mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
final Region emptyRegion = Region.obtain();
mView.getRootSurfaceControl().setTouchableRegion(emptyRegion);
emptyRegion.recycle();
@@ -255,8 +255,9 @@
@Override
protected void onViewDetached() {
- mHandler.removeCallbacks(this::updateBurnInOffsets);
+ mHandler.removeCallbacksAndMessages(null);
mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
+ mBouncerlessScrimController.removeCallback(mBouncerlessExpansionCallback);
mDreamOverlayAnimationsController.cancelAnimations();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
index 74452d1..2034138 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
@@ -21,7 +21,6 @@
import android.content.ComponentName
import android.os.PowerManager
import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.domain.interactor.PackageChangeInteractor
import com.android.systemui.common.shared.model.PackageChangeModel
import com.android.systemui.controls.ControlsServiceInfo
@@ -36,6 +35,7 @@
import com.android.systemui.util.kotlin.pairwiseBy
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.time.SystemClock
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
import kotlin.math.abs
import kotlin.time.Duration.Companion.milliseconds
@@ -132,7 +132,7 @@
?: panels.firstOrNull()
item?.panelActivity
}
- .stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
+ .stateIn(bgScope, SharingStarted.Eagerly, null)
private val taskFragmentFinished =
MutableSharedFlow<Long>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 95bc514..49be03c 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -2739,12 +2739,10 @@
protected final void setRotationSuggestionsEnabled(boolean enabled) {
try {
final int userId = Binder.getCallingUserHandle().getIdentifier();
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- if (enabled) {
- info.setRotationSuggestionDisabled(true);
- }
- mStatusBarService.disableForUser(info, mToken, mContext.getPackageName(), userId,
- "setRotationSuggestionsEnabled");
+ final int what = enabled
+ ? StatusBarManager.DISABLE2_NONE
+ : StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
+ mStatusBarService.disable2ForUser(what, mToken, mContext.getPackageName(), userId);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d6fd354..2cda728 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -16,7 +16,6 @@
package com.android.systemui.keyguard;
-import static android.app.StatusBarManager.DISABLE2_NONE;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.provider.Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT;
import static android.provider.Settings.System.LOCKSCREEN_SOUNDS_ENABLED;
@@ -3440,12 +3439,9 @@
// unless disable is called to show un-hide it once first
if (forceClearFlags) {
try {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(flags,
- DISABLE2_NONE);
- mStatusBarService.disableForUser(info, mStatusBarDisableToken,
+ mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
mContext.getPackageName(),
- mSelectedUserInteractor.getSelectedUserId(true),
- "adjustStatusBarLocked - force clear flags");
+ mSelectedUserInteractor.getSelectedUserId(true));
} catch (RemoteException e) {
Log.d(TAG, "Failed to force clear flags", e);
}
@@ -3471,11 +3467,9 @@
}
try {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(flags,
- DISABLE2_NONE);
- mStatusBarService.disableForUser(info, mStatusBarDisableToken,
- mContext.getPackageName(), mSelectedUserInteractor.getSelectedUserId(true),
- "adjustStatusBarLocked - set disable flags");
+ mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
+ mContext.getPackageName(),
+ mSelectedUserInteractor.getSelectedUserId(true));
} catch (RemoteException e) {
Log.d(TAG, "Failed to set disable flags: " + flags, e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index e32bfcf..7f3274c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -134,7 +134,7 @@
TransitionInfo(
ownerName = "",
from = KeyguardState.OFF,
- to = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OFF,
animator = null
)
)
@@ -266,6 +266,14 @@
}
override suspend fun emitInitialStepsFromOff(to: KeyguardState) {
+ _currentTransitionInfo.value =
+ TransitionInfo(
+ ownerName = "KeyguardTransitionRepository(boot)",
+ from = KeyguardState.OFF,
+ to = to,
+ animator = null
+ )
+
emitTransition(
TransitionStep(
KeyguardState.OFF,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index e51ba83..2a7178f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -18,6 +18,7 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
+import com.android.systemui.Flags.restartDreamOnUnocclude
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -90,12 +91,15 @@
.sample(
communalInteractor.isIdleOnCommunal,
communalInteractor.showCommunalFromOccluded,
+ communalInteractor.dreamFromOccluded
)
- .collect { (_, isIdleOnCommunal, showCommunalFromOccluded) ->
+ .collect { (_, isIdleOnCommunal, showCommunalFromOccluded, dreamFromOccluded) ->
// Occlusion signals come from the framework, and should interrupt any
// existing transition
val to =
- if (isIdleOnCommunal || showCommunalFromOccluded) {
+ if (restartDreamOnUnocclude() && dreamFromOccluded) {
+ KeyguardState.DREAMING
+ } else if (isIdleOnCommunal || showCommunalFromOccluded) {
KeyguardState.GLANCEABLE_HUB
} else {
KeyguardState.LOCKSCREEN
@@ -110,15 +114,19 @@
keyguardInteractor.isKeyguardShowing,
communalInteractor.isIdleOnCommunal,
communalInteractor.showCommunalFromOccluded,
+ communalInteractor.dreamFromOccluded,
)
- .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _) ->
+ .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _, _) ->
!isOccluded && isShowing
}
- .collect { (_, _, isIdleOnCommunal, showCommunalFromOccluded) ->
+ .collect { (_, _, isIdleOnCommunal, showCommunalFromOccluded, dreamFromOccluded)
+ ->
// Occlusion signals come from the framework, and should interrupt any
// existing transition
val to =
- if (isIdleOnCommunal || showCommunalFromOccluded) {
+ if (restartDreamOnUnocclude() && dreamFromOccluded) {
+ KeyguardState.DREAMING
+ } else if (isIdleOnCommunal || showCommunalFromOccluded) {
KeyguardState.GLANCEABLE_HUB
} else {
KeyguardState.LOCKSCREEN
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 88367f4..2d7b737 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -55,7 +55,6 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -63,10 +62,10 @@
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -179,12 +178,7 @@
isDreaming && isDozeOff(dozeTransitionModel.to)
}
.sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake }
- .flatMapLatest { isAbleToDream ->
- flow {
- delay(50)
- emit(isAbleToDream)
- }
- }
+ .debounce(50L)
.distinctUntilChanged()
/** Whether the keyguard is showing or not. */
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 4f00495..e2b66c5 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
@@ -76,7 +76,7 @@
view,
HapticFeedbackConstants.CONFIRM,
)
- applicationScope.launch { viewModel.onLongPress() }
+ applicationScope.launch { viewModel.onUserInteraction() }
}
}
@@ -116,6 +116,17 @@
launch("$TAG#viewModel.accessibilityDelegateHint") {
viewModel.accessibilityDelegateHint.collect { hint ->
view.accessibilityHintType = hint
+ if (hint != DeviceEntryIconView.AccessibilityHintType.NONE) {
+ view.setOnClickListener {
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.CONFIRM,
+ )
+ applicationScope.launch { viewModel.onUserInteraction() }
+ }
+ } else {
+ view.setOnClickListener(null)
+ }
}
}
launch("$TAG#viewModel.useBackgroundProtection") {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
index 35b2598..200d30c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -65,12 +65,12 @@
object : AccessibilityDelegate() {
private val accessibilityAuthenticateHint =
AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfoCompat.ACTION_LONG_CLICK,
+ AccessibilityNodeInfoCompat.ACTION_CLICK,
resources.getString(R.string.accessibility_authenticate_hint)
)
private val accessibilityEnterHint =
AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfoCompat.ACTION_LONG_CLICK,
+ AccessibilityNodeInfoCompat.ACTION_CLICK,
resources.getString(R.string.accessibility_enter_hint)
)
override fun onInitializeAccessibilityNodeInfo(
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 da2fcc4..53b2697 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,6 +19,7 @@
import android.animation.FloatEvaluator
import android.animation.IntEvaluator
import com.android.keyguard.KeyguardViewController
+import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -68,6 +69,7 @@
private val keyguardViewController: Lazy<KeyguardViewController>,
private val deviceEntryInteractor: DeviceEntryInteractor,
private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
+ private val accessibilityInteractor: AccessibilityInteractor,
@Application private val scope: CoroutineScope,
) {
val isUdfpsSupported: StateFlow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
@@ -232,7 +234,8 @@
}
}
val isVisible: Flow<Boolean> = deviceEntryViewAlpha.map { it > 0f }.distinctUntilChanged()
- val isLongPressEnabled: Flow<Boolean> =
+
+ private val isInteractive: Flow<Boolean> =
combine(
iconType,
isUdfpsSupported,
@@ -244,17 +247,24 @@
DeviceEntryIconView.IconType.NONE -> false
}
}
-
val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
- combine(iconType, isLongPressEnabled) { deviceEntryStatus, longPressEnabled ->
- if (longPressEnabled) {
- deviceEntryStatus.toAccessibilityHintType()
+ accessibilityInteractor.isEnabled.flatMapLatest { touchExplorationEnabled ->
+ if (touchExplorationEnabled) {
+ combine(iconType, isInteractive) { iconType, isInteractive ->
+ if (isInteractive) {
+ iconType.toAccessibilityHintType()
+ } else {
+ DeviceEntryIconView.AccessibilityHintType.NONE
+ }
+ }
} else {
- DeviceEntryIconView.AccessibilityHintType.NONE
+ flowOf(DeviceEntryIconView.AccessibilityHintType.NONE)
}
}
- suspend fun onLongPress() {
+ val isLongPressEnabled: Flow<Boolean> = isInteractive
+
+ suspend fun onUserInteraction() {
if (SceneContainerFlag.isEnabled) {
deviceEntryInteractor.attemptDeviceEntry()
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 933065b..295b293 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -442,7 +442,7 @@
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.GET_SHARED_LIBRARY_FILES));
int resId = resources.getIdentifier(
- "gesture_blocking_activities", "array", recentsPackageName);
+ "back_gesture_blocking_activities", "array", recentsPackageName);
if (resId == 0) {
Log.e(TAG, "No resource found for gesture-blocking activities");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index 6fb5174..5720f76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -125,7 +125,10 @@
public void setNumPages(int numPages) {
setVisibility(numPages > 1 ? View.VISIBLE : View.GONE);
- if (numPages == getChildCount()) {
+ int childCount = getChildCount();
+ // We're checking if the width needs to be updated as it's possible that the number of pages
+ // was changed while the page indicator was not visible, automatically skipping onMeasure.
+ if (numPages == childCount && calculateWidth(childCount) == getMeasuredWidth()) {
return;
}
if (mAnimating) {
@@ -295,6 +298,10 @@
}
}
+ private int calculateWidth(int numPages) {
+ return (mPageIndicatorWidth - mPageDotWidth) * (numPages - 1) + mPageDotWidth;
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int N = getChildCount();
@@ -309,7 +316,7 @@
for (int i = 0; i < N; i++) {
getChildAt(i).measure(widthChildSpec, heightChildSpec);
}
- int width = (mPageIndicatorWidth - mPageDotWidth) * (N - 1) + mPageDotWidth;
+ int width = calculateWidth(N);
setMeasuredDimension(width, mPageIndicatorHeight);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 2a726c2..24b7a01 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -19,6 +19,8 @@
import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT;
+import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix;
+
import android.app.ActivityManager;
import android.app.compat.CompatChanges;
import android.content.BroadcastReceiver;
@@ -88,6 +90,7 @@
private static final int MSG_ON_REMOVED = 1;
private static final int MSG_ON_CLICK = 2;
private static final int MSG_ON_UNLOCK_COMPLETE = 3;
+ private static final int MSG_ON_STOP_LISTENING = 4;
// Bind retry control.
private static final int MAX_BIND_RETRIES = 5;
@@ -368,6 +371,16 @@
onUnlockComplete();
}
}
+ if (qsCustomTileClickGuaranteedBugFix()) {
+ if (queue.contains(MSG_ON_STOP_LISTENING)) {
+ if (mDebug) Log.d(TAG, "Handling pending onStopListening " + getComponent());
+ if (mListening) {
+ onStopListening();
+ } else {
+ Log.w(TAG, "Trying to stop listening when not listening " + getComponent());
+ }
+ }
+ }
if (queue.contains(MSG_ON_REMOVED)) {
if (mDebug) Log.d(TAG, "Handling pending onRemoved " + getComponent());
if (mListening) {
@@ -586,10 +599,15 @@
@Override
public void onStopListening() {
- if (mDebug) Log.d(TAG, "onStopListening " + getComponent());
- mListening = false;
- if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) {
- handleDeath();
+ if (qsCustomTileClickGuaranteedBugFix() && hasPendingClick()) {
+ Log.d(TAG, "Enqueue stop listening");
+ queueMessage(MSG_ON_STOP_LISTENING);
+ } else {
+ if (mDebug) Log.d(TAG, "onStopListening " + getComponent());
+ mListening = false;
+ if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) {
+ handleDeath();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index f8bf0a6..6bc5095 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.qs.external;
+import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix;
+
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -37,6 +39,7 @@
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Manages the priority which lets {@link TileServices} make decisions about which tiles
@@ -72,6 +75,8 @@
private boolean mPendingBind = true;
private boolean mStarted = false;
+ private final AtomicBoolean mListeningFromRequest = new AtomicBoolean(false);
+
TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
UserTracker userTracker, TileLifecycleManager.Factory tileLifecycleManagerFactory,
CustomTileAddedRepository customTileAddedRepository) {
@@ -159,15 +164,30 @@
}
}
+ void onStartListeningFromRequest() {
+ mListeningFromRequest.set(true);
+ mStateManager.onStartListening();
+ }
+
public void setLastUpdate(long lastUpdate) {
mLastUpdate = lastUpdate;
if (mBound && isActiveTile()) {
- mStateManager.onStopListening();
- setBindRequested(false);
+ if (qsCustomTileClickGuaranteedBugFix()) {
+ if (mListeningFromRequest.compareAndSet(true, false)) {
+ stopListeningAndUnbind();
+ }
+ } else {
+ stopListeningAndUnbind();
+ }
}
mServices.recalculateBindAllowance();
}
+ private void stopListeningAndUnbind() {
+ mStateManager.onStopListening();
+ setBindRequested(false);
+ }
+
public void handleDestroy() {
setBindAllowed(false);
mServices.getContext().unregisterReceiver(mUninstallReceiver);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 8278c79..d457e88 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.qs.external;
+import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix;
+
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -222,9 +224,13 @@
return;
}
service.setBindRequested(true);
- try {
- service.getTileService().onStartListening();
- } catch (RemoteException e) {
+ if (qsCustomTileClickGuaranteedBugFix()) {
+ service.onStartListeningFromRequest();
+ } else {
+ try {
+ service.getTileService().onStartListening();
+ } catch (RemoteException e) {
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index b515ce0..278352c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -28,6 +28,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
import com.android.systemui.log.core.LogLevel.VERBOSE
import com.android.systemui.log.dagger.QSConfigLog
import com.android.systemui.log.dagger.QSLog
@@ -56,6 +57,9 @@
fun d(@CompileTimeConstant msg: String, arg: Any) {
buffer.log(TAG, DEBUG, { str1 = arg.toString() }, { "$msg: $str1" })
}
+ fun i(@CompileTimeConstant msg: String, arg: Any) {
+ buffer.log(TAG, INFO, { str1 = arg.toString() }, { "$msg: $str1" })
+ }
fun logTileAdded(tileSpec: String) {
buffer.log(TAG, DEBUG, { str1 = tileSpec }, { "[$str1] Tile added" })
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index e3ba36f..0696fbe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -16,8 +16,17 @@
package com.android.systemui.qs.panels.dagger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
+import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepositoryImpl
import com.android.systemui.qs.panels.data.repository.IconTilesRepository
import com.android.systemui.qs.panels.data.repository.IconTilesRepositoryImpl
+import com.android.systemui.qs.panels.domain.interactor.GridTypeConsistencyInteractor
+import com.android.systemui.qs.panels.domain.interactor.InfiniteGridConsistencyInteractor
+import com.android.systemui.qs.panels.domain.interactor.NoopGridConsistencyInteractor
+import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
import com.android.systemui.qs.panels.ui.compose.GridLayout
@@ -31,8 +40,23 @@
interface PanelsModule {
@Binds fun bindIconTilesRepository(impl: IconTilesRepositoryImpl): IconTilesRepository
+ @Binds
+ fun bindGridLayoutTypeRepository(impl: GridLayoutTypeRepositoryImpl): GridLayoutTypeRepository
+
+ @Binds
+ fun bindDefaultGridConsistencyInteractor(
+ impl: NoopGridConsistencyInteractor
+ ): GridTypeConsistencyInteractor
+
companion object {
@Provides
+ @SysUISingleton
+ @GridConsistencyLog
+ fun providesGridConsistencyLog(factory: LogBufferFactory): LogBuffer {
+ return factory.create("GridConsistencyLog", 50)
+ }
+
+ @Provides
@IntoSet
fun provideGridLayout(gridLayout: InfiniteGridLayout): Pair<GridLayoutType, GridLayout> {
return Pair(InfiniteGridLayoutType, gridLayout)
@@ -44,5 +68,20 @@
): Map<GridLayoutType, GridLayout> {
return entries.toMap()
}
+
+ @Provides
+ @IntoSet
+ fun provideGridConsistencyInteractor(
+ consistencyInteractor: InfiniteGridConsistencyInteractor
+ ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
+ return Pair(InfiniteGridLayoutType, consistencyInteractor)
+ }
+
+ @Provides
+ fun provideGridConsistencyInteractorMap(
+ entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridTypeConsistencyInteractor>>
+ ): Map<GridLayoutType, GridTypeConsistencyInteractor> {
+ return entries.toMap()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
index 02dd33e..542d0cb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
@@ -20,10 +20,16 @@
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+interface GridLayoutTypeRepository {
+ val layout: StateFlow<GridLayoutType>
+}
@SysUISingleton
-class GridLayoutTypeRepository @Inject constructor() {
- val layout: Flow<GridLayoutType> = flowOf(InfiniteGridLayoutType)
+class GridLayoutTypeRepositoryImpl @Inject constructor() : GridLayoutTypeRepository {
+ private val _layout: MutableStateFlow<GridLayoutType> = MutableStateFlow(InfiniteGridLayoutType)
+ override val layout = _layout.asStateFlow()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
index 92f87e7..e581bfc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
@@ -19,20 +19,20 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
/** Repository for retrieving the list of [TileSpec] to be displayed as icons. */
interface IconTilesRepository {
- val iconTilesSpecs: Flow<Set<TileSpec>>
+ val iconTilesSpecs: StateFlow<Set<TileSpec>>
}
@SysUISingleton
class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository {
- /** Set of toggleable tiles that are suitable for being shown as an icon. */
- override val iconTilesSpecs: Flow<Set<TileSpec>> =
- flowOf(
+ private val _iconTilesSpecs =
+ MutableStateFlow(
setOf(
TileSpec.create("airplane"),
TileSpec.create("battery"),
@@ -50,4 +50,7 @@
TileSpec.create("rotation")
)
)
+
+ /** Set of toggleable tiles that are suitable for being shown as an icon. */
+ override val iconTilesSpecs: StateFlow<Set<TileSpec>> = _iconTilesSpecs.asStateFlow()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt
new file mode 100644
index 0000000..43ccdf66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class InfiniteGridSizeRepository @Inject constructor() {
+ // Number of columns in the narrowest state for consistency
+ private val _columns = MutableStateFlow(4)
+ val columns: StateFlow<Int> = _columns.asStateFlow()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
new file mode 100644
index 0000000..7732092
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
+import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class GridConsistencyInteractor
+@Inject
+constructor(
+ private val gridLayoutTypeInteractor: GridLayoutTypeInteractor,
+ private val currentTilesInteractor: CurrentTilesInteractor,
+ private val consistencyInteractors:
+ Map<GridLayoutType, @JvmSuppressWildcards GridTypeConsistencyInteractor>,
+ private val defaultConsistencyInteractor: GridTypeConsistencyInteractor,
+ @GridConsistencyLog private val logBuffer: LogBuffer,
+ @Application private val applicationScope: CoroutineScope,
+) {
+ fun start() {
+ applicationScope.launch {
+ gridLayoutTypeInteractor.layout.collectLatest { type ->
+ val consistencyInteractor =
+ consistencyInteractors[type] ?: defaultConsistencyInteractor
+ currentTilesInteractor.currentTiles
+ .map { tiles -> tiles.map { it.spec } }
+ .collectLatest { tiles ->
+ val newTiles = consistencyInteractor.reconcileTiles(tiles)
+ if (newTiles != tiles) {
+ currentTilesInteractor.setTiles(newTiles)
+ logChange(newTiles)
+ }
+ }
+ }
+ }
+ }
+
+ private fun logChange(tiles: List<TileSpec>) {
+ logBuffer.log(
+ LOG_BUFFER_CURRENT_TILES_CHANGE_TAG,
+ LogLevel.DEBUG,
+ { str1 = tiles.toString() },
+ { "Tiles reordered: $str1" }
+ )
+ }
+
+ private companion object {
+ const val LOG_BUFFER_CURRENT_TILES_CHANGE_TAG = "GridConsistencyTilesChange"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt
new file mode 100644
index 0000000..4cdabae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+interface GridTypeConsistencyInteractor {
+ /**
+ * Given a list of tiles, return the best list of the same tiles (preserving as much order as
+ * possible, such that it's consistent with the current layout.
+ */
+ fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index 1aec193..ccc1c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -20,10 +20,10 @@
import com.android.systemui.qs.panels.data.repository.IconTilesRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/** Interactor for retrieving the list of [TileSpec] to be displayed as icons. */
@SysUISingleton
class IconTilesInteractor @Inject constructor(repo: IconTilesRepository) {
- val iconTilesSpecs: Flow<Set<TileSpec>> = repo.iconTilesSpecs
+ val iconTilesSpecs: StateFlow<Set<TileSpec>> = repo.iconTilesSpecs
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
new file mode 100644
index 0000000..74e906c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+@SysUISingleton
+class InfiniteGridConsistencyInteractor
+@Inject
+constructor(
+ private val iconTilesInteractor: IconTilesInteractor,
+ private val gridSizeInteractor: InfiniteGridSizeInteractor
+) : GridTypeConsistencyInteractor {
+
+ /**
+ * Tries to fill in every columns of all rows (except the last row), potentially reordering
+ * tiles.
+ */
+ override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> {
+ val newTiles: MutableList<TileSpec> = mutableListOf()
+ val row = TileRow(columns = gridSizeInteractor.columns.value)
+ val iconTilesSet = iconTilesInteractor.iconTilesSpecs.value
+ val tilesQueue =
+ ArrayDeque(
+ tiles.map {
+ SizedTile(
+ it,
+ width =
+ if (iconTilesSet.contains(it)) {
+ 1
+ } else {
+ 2
+ }
+ )
+ }
+ )
+
+ while (tilesQueue.isNotEmpty()) {
+ if (row.isFull()) {
+ newTiles.addAll(row.tileSpecs())
+ row.clear()
+ }
+
+ val tile = tilesQueue.removeFirst()
+
+ // If the tile fits in the row, add it.
+ if (!row.maybeAddTile(tile)) {
+ // If the tile does not fit the row, find an icon tile to move.
+ // We'll try to either add an icon tile from the queue to complete the row, or
+ // remove an icon tile from the current row to free up space.
+
+ val iconTile: SizedTile? = tilesQueue.firstOrNull { it.width == 1 }
+ if (iconTile != null) {
+ tilesQueue.remove(iconTile)
+ tilesQueue.addFirst(tile)
+ row.maybeAddTile(iconTile)
+ } else {
+ val tileToRemove: SizedTile? = row.findLastIconTile()
+ if (tileToRemove != null) {
+ row.removeTile(tileToRemove)
+ row.maybeAddTile(tile)
+
+ // Moving the icon tile to the end because there's no other
+ // icon tiles in the queue.
+ tilesQueue.addLast(tileToRemove)
+ } else {
+ // If the row does not have an icon tile, add the incomplete row.
+ // Note: this shouldn't happen because an icon tile is guaranteed to be in a
+ // row that doesn't have enough space for a large tile.
+ val tileSpecs = row.tileSpecs()
+ Log.wtf(TAG, "Uneven row does not have an icon tile to remove: $tileSpecs")
+ newTiles.addAll(tileSpecs)
+ row.clear()
+ tilesQueue.addFirst(tile)
+ }
+ }
+ }
+ }
+
+ // Add last row that might be incomplete
+ newTiles.addAll(row.tileSpecs())
+
+ return newTiles.toList()
+ }
+
+ /** Tile with a width representing the number of columns it should take. */
+ private data class SizedTile(val spec: TileSpec, val width: Int)
+
+ private class TileRow(private val columns: Int) {
+ private var availableColumns = columns
+ private val tiles: MutableList<SizedTile> = mutableListOf()
+
+ fun tileSpecs(): List<TileSpec> {
+ return tiles.map { it.spec }
+ }
+
+ fun maybeAddTile(tile: SizedTile): Boolean {
+ if (availableColumns - tile.width >= 0) {
+ tiles.add(tile)
+ availableColumns -= tile.width
+ return true
+ }
+ return false
+ }
+
+ fun findLastIconTile(): SizedTile? {
+ return tiles.findLast { it.width == 1 }
+ }
+
+ fun removeTile(tile: SizedTile) {
+ tiles.remove(tile)
+ availableColumns += tile.width
+ }
+
+ fun clear() {
+ tiles.clear()
+ availableColumns = columns
+ }
+
+ fun isFull(): Boolean = availableColumns == 0
+ }
+
+ private companion object {
+ const val TAG = "InfiniteGridConsistencyInteractor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt
new file mode 100644
index 0000000..13c6072
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.data.repository.InfiniteGridSizeRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+@SysUISingleton
+class InfiniteGridSizeInteractor @Inject constructor(repo: InfiniteGridSizeRepository) {
+ val columns: StateFlow<Int> = repo.columns
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt
new file mode 100644
index 0000000..0386a6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+/** [GridTypeConsistencyInteractor] implementation that doesn't do any changes to tiles. */
+@SysUISingleton
+class NoopGridConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor {
+ override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> = tiles
+}
diff --git a/core/java/android/app/StatusBarManager.aidl b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
similarity index 62%
copy from core/java/android/app/StatusBarManager.aidl
copy to packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
index 687678c..884cde3 100644
--- a/core/java/android/app/StatusBarManager.aidl
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,6 +14,11 @@
* limitations under the License.
*/
-package android.app;
+package com.android.systemui.qs.panels.shared.model
-parcelable StatusBarManager.DisableInfo;
\ No newline at end of file
+import javax.inject.Qualifier
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class GridConsistencyLog()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index 6539cf3..dc43091 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -23,10 +23,12 @@
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Box
@@ -64,20 +66,20 @@
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.integerResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.unit.dp
-import com.android.compose.modifiers.background
+import com.android.compose.animation.Expandable
import com.android.compose.theme.colorAttr
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.load
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
+import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
import com.android.systemui.qs.panels.ui.viewmodel.ActiveTileColorAttributes
import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
@@ -95,8 +97,12 @@
import kotlinx.coroutines.flow.mapLatest
@SysUISingleton
-class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: IconTilesInteractor) :
- GridLayout {
+class InfiniteGridLayout
+@Inject
+constructor(
+ private val iconTilesInteractor: IconTilesInteractor,
+ private val gridSizeInteractor: InfiniteGridSizeInteractor
+) : GridLayout {
private object TileType
@@ -110,10 +116,10 @@
tiles.forEach { it.startListening(token) }
onDispose { tiles.forEach { it.stopListening(token) } }
}
- val iconTilesSpecs by
- iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet())
+ val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsState()
+ val columns by gridSizeInteractor.columns.collectAsState()
- TileLazyGrid(modifier) {
+ TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
items(
tiles.size,
span = { index ->
@@ -134,7 +140,7 @@
}
}
- @OptIn(ExperimentalCoroutinesApi::class)
+ @OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
@Composable
private fun Tile(
tile: TileViewModel,
@@ -147,28 +153,39 @@
.collectAsState(initial = tile.currentState.toUiState())
val context = LocalContext.current
- Row(
- modifier = modifier.clickable { tile.onClick(null) }.tileModifier(state.colors),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = tileHorizontalArrangement(iconOnly)
+ Expandable(
+ color = colorAttr(state.colors.background),
+ shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)),
) {
- val icon =
- remember(state.icon) {
- state.icon.get().let {
- if (it is QSTileImpl.ResourceIcon) {
- Icon.Resource(it.resId, null)
- } else {
- Icon.Loaded(it.getDrawable(context), null)
+ Row(
+ modifier =
+ modifier
+ .combinedClickable(
+ onClick = { tile.onClick(it) },
+ onLongClick = { tile.onLongClick(it) }
+ )
+ .tileModifier(state.colors),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = tileHorizontalArrangement(iconOnly),
+ ) {
+ val icon =
+ remember(state.icon) {
+ state.icon.get().let {
+ if (it is QSTileImpl.ResourceIcon) {
+ Icon.Resource(it.resId, null)
+ } else {
+ Icon.Loaded(it.getDrawable(context), null)
+ }
}
}
- }
- TileContent(
- label = state.label.toString(),
- secondaryLabel = state.secondaryLabel.toString(),
- icon = icon,
- colors = state.colors,
- iconOnly = iconOnly
- )
+ TileContent(
+ label = state.label.toString(),
+ secondaryLabel = state.secondaryLabel?.toString(),
+ icon = icon,
+ colors = state.colors,
+ iconOnly = iconOnly
+ )
+ }
}
}
@@ -187,8 +204,9 @@
val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet())
val isIconOnly: (TileSpec) -> Boolean =
remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
+ val columns by gridSizeInteractor.columns.collectAsState()
- TileLazyGrid(modifier = modifier) {
+ TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
// These Text are just placeholders to see the different sections. Not final UI.
item(span = { GridItemSpan(maxLineSpan) }) {
Text("Current tiles", color = Color.White)
@@ -372,11 +390,11 @@
@Composable
private fun TileLazyGrid(
modifier: Modifier = Modifier,
+ columns: GridCells,
content: LazyGridScope.() -> Unit,
) {
LazyVerticalGrid(
- columns =
- GridCells.Fixed(integerResource(R.integer.quick_settings_infinite_grid_num_columns)),
+ columns = columns,
verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
modifier = modifier,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
index af1d195..c8fbeb5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -18,6 +18,8 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.flags.NewQsUI
+import com.android.systemui.qs.panels.domain.interactor.GridConsistencyInteractor
import com.android.systemui.qs.pipeline.domain.interactor.AccessibilityTilesInteractor
import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
@@ -34,6 +36,7 @@
private val autoAddInteractor: AutoAddInteractor,
private val featureFlags: QSPipelineFlagsRepository,
private val restoreReconciliationInteractor: RestoreReconciliationInteractor,
+ private val gridConsistencyInteractor: GridConsistencyInteractor,
) : CoreStartable {
override fun start() {
@@ -41,6 +44,10 @@
accessibilityTilesInteractor.init(currentTilesInteractor)
autoAddInteractor.init(currentTilesInteractor)
restoreReconciliationInteractor.start()
+
+ if (NewQsUI.isEnabled) {
+ gridConsistencyInteractor.start()
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index c24113f1..56588ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -55,6 +55,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
+import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.Expandable;
@@ -632,12 +633,23 @@
}
public static class DrawableIcon extends Icon {
+
protected final Drawable mDrawable;
protected final Drawable mInvisibleDrawable;
+ private static final String TAG = "QSTileImpl";
public DrawableIcon(Drawable drawable) {
mDrawable = drawable;
- mInvisibleDrawable = drawable.getConstantState().newDrawable();
+ Drawable.ConstantState nullableConstantState = drawable.getConstantState();
+ if (nullableConstantState == null) {
+ if (!(drawable instanceof SignalDrawable)) {
+ Log.w(TAG, "DrawableIcon: drawable has null ConstantState"
+ + " and is not a SignalDrawable");
+ }
+ mInvisibleDrawable = drawable;
+ } else {
+ mInvisibleDrawable = nullableConstantState.newDrawable();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
index 065e89f..f0d7206 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -175,6 +175,26 @@
)
}
+ /** Log with level [LogLevel.WARNING] */
+ fun logWarning(
+ tileSpec: TileSpec,
+ message: String,
+ ) {
+ tileSpec
+ .getLogBuffer()
+ .log(tileSpec.getLogTag(), LogLevel.WARNING, { str1 = message }, { str1!! })
+ }
+
+ /** Log with level [LogLevel.INFO] */
+ fun logInfo(
+ tileSpec: TileSpec,
+ message: String,
+ ) {
+ tileSpec
+ .getLogBuffer()
+ .log(tileSpec.getLogTag(), LogLevel.INFO, { str1 = message }, { str1!! })
+ }
+
fun logCustomTileUserActionDelivered(tileSpec: TileSpec) {
tileSpec
.getLogBuffer()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
index ffa3b54..4c21080 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
@@ -25,10 +25,13 @@
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent
+import com.android.systemui.qs.tiles.impl.custom.di.QSTileConfigModule
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
import com.android.systemui.qs.tiles.impl.di.QSTileComponent
-import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -47,7 +50,7 @@
* binding them together. This achieves a DI scope that lives along the instance of
* [QSTileViewModelImpl].
*/
- class Component<T>
+ class Component
@Inject
constructor(
private val disabledByPolicyInteractor: DisabledByPolicyInteractor,
@@ -58,7 +61,8 @@
private val qsTileConfigProvider: QSTileConfigProvider,
private val systemClock: SystemClock,
@Background private val backgroundDispatcher: CoroutineDispatcher,
- ) : QSTileViewModelFactory<T> {
+ private val customTileComponentBuilder: CustomTileComponent.Builder,
+ ) : QSTileViewModelFactory<CustomTileDataModel> {
/**
* Creates [QSTileViewModelImpl] based on the interactors obtained from [QSTileComponent].
@@ -66,10 +70,10 @@
*/
fun create(
tileSpec: TileSpec,
- componentFactory: (config: QSTileConfig) -> QSTileComponent<T>
- ): QSTileViewModelImpl<T> {
+ ): QSTileViewModel {
val config = qsTileConfigProvider.getConfig(tileSpec.spec)
- val component = componentFactory(config)
+ val component =
+ customTileComponentBuilder.qsTileConfigModule(QSTileConfigModule(config)).build()
return QSTileViewModelImpl(
config,
component::userActionInteractor,
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 5122e1f..f65fdb5 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
@@ -22,9 +22,6 @@
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
-import com.android.systemui.qs.tiles.impl.custom.di.QSTileConfigModule
-import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter
@@ -41,8 +38,7 @@
private val adapterFactory: QSTileViewModelAdapter.Factory,
private val tileMap:
Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>,
- private val customTileComponentBuilder: CustomTileComponent.Builder,
- private val customTileViewModelFactory: QSTileViewModelFactory.Component<CustomTileDataModel>,
+ private val customTileViewModelFactory: QSTileViewModelFactory.Component,
) : QSFactory {
init {
@@ -68,7 +64,5 @@
}
private fun createCustomTileViewModel(spec: TileSpec.CustomTileSpec): QSTileViewModel =
- customTileViewModelFactory.create(spec) { config ->
- customTileComponentBuilder.qsTileConfigModule(QSTileConfigModule(config)).build()
- }
+ customTileViewModelFactory.create(spec)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 60469c0..b057476 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs.tiles.dialog;
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
+
import static com.android.settingslib.mobile.MobileMappings.getIconKey;
import static com.android.settingslib.mobile.MobileMappings.mapIconSets;
import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource;
@@ -190,7 +192,7 @@
private DialogTransitionAnimator mDialogTransitionAnimator;
private boolean mHasWifiEntries;
private WifiStateWorker mWifiStateWorker;
- private boolean mHasActiveSubId;
+ private boolean mHasActiveSubIdOnDds;
@VisibleForTesting
static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
@@ -298,7 +300,7 @@
mExecutor);
// Listen the subscription changes
mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener();
- refreshHasActiveSubId();
+ refreshHasActiveSubIdOnDds();
mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
mOnSubscriptionsChangedListener);
mDefaultDataSubId = getDefaultDataSubscriptionId();
@@ -428,7 +430,7 @@
}
boolean isActiveOnNonDds = getActiveAutoSwitchNonDdsSubId() != SubscriptionManager
.INVALID_SUBSCRIPTION_ID;
- if (!hasActiveSubId() || (!isVoiceStateInService(mDefaultDataSubId)
+ if (!hasActiveSubIdOnDds() || (!isVoiceStateInService(mDefaultDataSubId)
&& !isDataStateInService(mDefaultDataSubId) && !isActiveOnNonDds)) {
if (DEBUG) {
Log.d(TAG, "No carrier or service is out of service.");
@@ -901,23 +903,42 @@
/**
* @return whether there is the carrier item in the slice.
*/
- boolean hasActiveSubId() {
+ boolean hasActiveSubIdOnDds() {
if (isAirplaneModeEnabled() || mTelephonyManager == null) {
return false;
}
- return mHasActiveSubId;
+ return mHasActiveSubIdOnDds;
}
- private void refreshHasActiveSubId() {
+ private static boolean isEmbeddedSubscriptionVisible(@NonNull SubscriptionInfo subInfo) {
+ if (subInfo.isEmbedded() && subInfo.getProfileClass() == PROFILE_CLASS_PROVISIONING) {
+ return false;
+ }
+ return true;
+ }
+
+ private void refreshHasActiveSubIdOnDds() {
if (mSubscriptionManager == null) {
- mHasActiveSubId = false;
+ mHasActiveSubIdOnDds = false;
Log.e(TAG, "SubscriptionManager is null, set mHasActiveSubId = false");
return;
}
+ int dds = getDefaultDataSubscriptionId();
+ if (dds == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ mHasActiveSubIdOnDds = false;
+ Log.d(TAG, "DDS is INVALID_SUBSCRIPTION_ID");
+ return;
+ }
+ SubscriptionInfo ddsSubInfo = mSubscriptionManager.getActiveSubscriptionInfo(dds);
+ if (ddsSubInfo == null) {
+ mHasActiveSubIdOnDds = false;
+ Log.e(TAG, "Can't get DDS subscriptionInfo");
+ return;
+ }
- mHasActiveSubId = mSubscriptionManager.getActiveSubscriptionIdList().length > 0;
- Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubId);
+ mHasActiveSubIdOnDds = isEmbeddedSubscriptionVisible(ddsSubInfo);
+ Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubIdOnDds);
}
/**
@@ -1209,7 +1230,7 @@
@Override
public void onSubscriptionsChanged() {
- refreshHasActiveSubId();
+ refreshHasActiveSubIdOnDds();
updateListener();
}
}
@@ -1306,6 +1327,7 @@
Log.d(TAG, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED");
}
mConfig = MobileMappings.Config.readConfig(context);
+ refreshHasActiveSubIdOnDds();
updateListener();
} else if (WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION.equals(action)) {
updateListener();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index 1a881b6..c9c4443 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -429,7 +429,7 @@
}
boolean isWifiEnabled = mInternetDialogController.isWifiEnabled();
- if (!mInternetDialogController.hasActiveSubId()
+ if (!mInternetDialogController.hasActiveSubIdOnDds()
&& (!isWifiEnabled || !isCarrierNetworkActive)) {
mMobileNetworkLayout.setVisibility(View.GONE);
if (mSecondaryMobileNetworkLayout != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt
index d1c8030..bd2f2c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt
@@ -17,15 +17,15 @@
package com.android.systemui.qs.tiles.impl.location.domain.interactor
import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
import com.android.systemui.statusbar.policy.LocationController
+import com.android.systemui.util.kotlin.isLocationEnabledFlow
import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
/** Observes location state changes providing the [LocationTileModel]. */
class LocationTileDataInteractor
@@ -38,19 +38,7 @@
user: UserHandle,
triggers: Flow<DataUpdateTrigger>
): Flow<LocationTileModel> =
- ConflatedCallbackFlow.conflatedCallbackFlow {
- val initialValue = locationController.isLocationEnabled
- trySend(LocationTileModel(initialValue))
-
- val callback =
- object : LocationController.LocationChangeCallback {
- override fun onLocationSettingsChanged(locationEnabled: Boolean) {
- trySend(LocationTileModel(locationEnabled))
- }
- }
- locationController.addCallback(callback)
- awaitClose { locationController.removeCallback(callback) }
- }
+ locationController.isLocationEnabledFlow().map { LocationTileModel(it) }
override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt
new file mode 100644
index 0000000..88bd224
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.domain.interactor
+
+import android.content.Context
+import android.hardware.display.ColorDisplayManager
+import android.os.UserHandle
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.util.time.DateFormatUtil
+import java.time.LocalTime
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Observes screen record state changes providing the [NightDisplayTileModel]. */
+class NightDisplayTileDataInteractor
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val dateFormatUtil: DateFormatUtil,
+ private val nightDisplayRepository: NightDisplayRepository,
+) : QSTileDataInteractor<NightDisplayTileModel> {
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<NightDisplayTileModel> =
+ nightDisplayRepository.nightDisplayState(user).map {
+ generateModel(
+ it.autoMode,
+ it.isActivated,
+ it.startTime,
+ it.endTime,
+ it.shouldForceAutoMode,
+ it.locationEnabled
+ )
+ }
+
+ /** This checks resources and there fore does not make a binder call. */
+ override fun availability(user: UserHandle): Flow<Boolean> =
+ flowOf(ColorDisplayManager.isNightDisplayAvailable(context))
+
+ private fun generateModel(
+ autoMode: Int,
+ isNightDisplayActivated: Boolean,
+ customStartTime: LocalTime?,
+ customEndTime: LocalTime?,
+ shouldForceAutoMode: Boolean,
+ locationEnabled: Boolean,
+ ): NightDisplayTileModel {
+ if (autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) {
+ return NightDisplayTileModel.AutoModeTwilight(
+ isNightDisplayActivated,
+ shouldForceAutoMode,
+ locationEnabled,
+ )
+ } else if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) {
+ return NightDisplayTileModel.AutoModeCustom(
+ isNightDisplayActivated,
+ shouldForceAutoMode,
+ customStartTime,
+ customEndTime,
+ dateFormatUtil.is24HourFormat,
+ )
+ } else { // auto mode off
+ return NightDisplayTileModel.AutoModeOff(isNightDisplayActivated, shouldForceAutoMode)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt
new file mode 100644
index 0000000..5cee8c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.domain.interactor
+
+import android.content.Intent
+import android.hardware.display.ColorDisplayManager.AUTO_MODE_CUSTOM_TIME
+import android.provider.Settings
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles night display tile clicks. */
+class NightDisplayTileUserActionInteractor
+@Inject
+constructor(
+ private val nightDisplayRepository: NightDisplayRepository,
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val qsLogger: QSTileLogger,
+) : QSTileUserActionInteractor<NightDisplayTileModel> {
+ override suspend fun handleInput(input: QSTileInput<NightDisplayTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ // Enroll in forced auto mode if eligible.
+ if (data.isEnrolledInForcedNightDisplayAutoMode) {
+ nightDisplayRepository.setNightDisplayAutoMode(AUTO_MODE_CUSTOM_TIME, user)
+ qsLogger.logInfo(spec, "Enrolled in forced night display auto mode")
+ }
+ nightDisplayRepository.setNightDisplayActivated(!data.isActivated, user)
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.expandable,
+ Intent(Settings.ACTION_NIGHT_DISPLAY_SETTINGS)
+ )
+ }
+ }
+ }
+
+ companion object {
+ val spec = TileSpec.create(QSAccessibilityModule.NIGHT_DISPLAY_TILE_SPEC)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt
new file mode 100644
index 0000000..6b1bd5b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.domain.model
+
+import java.time.LocalTime
+
+/** Data model for night display tile */
+sealed interface NightDisplayTileModel {
+ val isActivated: Boolean
+ val isEnrolledInForcedNightDisplayAutoMode: Boolean
+ data class AutoModeTwilight(
+ override val isActivated: Boolean,
+ override val isEnrolledInForcedNightDisplayAutoMode: Boolean,
+ val isLocationEnabled: Boolean
+ ) : NightDisplayTileModel
+ data class AutoModeCustom(
+ override val isActivated: Boolean,
+ override val isEnrolledInForcedNightDisplayAutoMode: Boolean,
+ val startTime: LocalTime?,
+ val endTime: LocalTime?,
+ val is24HourFormat: Boolean
+ ) : NightDisplayTileModel
+ data class AutoModeOff(
+ override val isActivated: Boolean,
+ override val isEnrolledInForcedNightDisplayAutoMode: Boolean
+ ) : NightDisplayTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
new file mode 100644
index 0000000..5c2dcfc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.ui
+
+import android.content.res.Resources
+import android.service.quicksettings.Tile
+import android.text.TextUtils
+import androidx.annotation.StringRes
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import java.time.DateTimeException
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import javax.inject.Inject
+
+/** Maps [NightDisplayTileModel] to [QSTileState]. */
+class NightDisplayTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+ private val logger: QSTileLogger,
+) : QSTileDataToStateMapper<NightDisplayTileModel> {
+ override fun map(config: QSTileConfig, data: NightDisplayTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = resources.getString(R.string.quick_settings_night_display_label)
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ sideViewIcon = QSTileState.SideViewIcon.None
+
+ if (data.isActivated) {
+ activationState = QSTileState.ActivationState.ACTIVE
+ val loadedIcon =
+ Icon.Loaded(
+ resources.getDrawable(R.drawable.qs_nightlight_icon_on, theme),
+ contentDescription = null
+ )
+ icon = { loadedIcon }
+ } else {
+ activationState = QSTileState.ActivationState.INACTIVE
+ val loadedIcon =
+ Icon.Loaded(
+ resources.getDrawable(R.drawable.qs_nightlight_icon_off, theme),
+ contentDescription = null
+ )
+ icon = { loadedIcon }
+ }
+
+ secondaryLabel = getSecondaryLabel(data, resources)
+
+ contentDescription =
+ if (TextUtils.isEmpty(secondaryLabel)) label
+ else TextUtils.concat(label, ", ", secondaryLabel)
+ }
+
+ private fun getSecondaryLabel(
+ data: NightDisplayTileModel,
+ resources: Resources
+ ): CharSequence? {
+ when (data) {
+ is NightDisplayTileModel.AutoModeTwilight -> {
+ if (!data.isLocationEnabled) {
+ return null
+ } else {
+ return resources.getString(
+ if (data.isActivated)
+ R.string.quick_settings_night_secondary_label_until_sunrise
+ else R.string.quick_settings_night_secondary_label_on_at_sunset
+ )
+ }
+ }
+ is NightDisplayTileModel.AutoModeOff -> {
+ val subtitleArray = resources.getStringArray(R.array.tile_states_night)
+ return subtitleArray[
+ if (data.isActivated) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE]
+ }
+ is NightDisplayTileModel.AutoModeCustom -> {
+ // User-specified time, approximated to the nearest hour.
+ @StringRes val toggleTimeStringRes: Int
+ val toggleTime: LocalTime
+ if (data.isActivated) {
+ toggleTime = data.endTime ?: return null
+ toggleTimeStringRes = R.string.quick_settings_secondary_label_until
+ } else {
+ toggleTime = data.startTime ?: return null
+ toggleTimeStringRes = R.string.quick_settings_night_secondary_label_on_at
+ }
+
+ try {
+ val formatter = if (data.is24HourFormat) formatter24Hour else formatter12Hour
+ val formatArg = formatter.format(toggleTime)
+ return resources.getString(toggleTimeStringRes, formatArg)
+ } catch (exception: DateTimeException) {
+ logger.logWarning(spec, exception.message.toString())
+ return null
+ }
+ }
+ }
+ }
+
+ private companion object {
+ val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
+ val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
+ val spec = TileSpec.create(QSAccessibilityModule.NIGHT_DISPLAY_TILE_SPEC)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index b88c1e5..5346b23 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -201,6 +201,7 @@
qsTileViewModel.currentState?.let { mapState(context, it, qsTileViewModel.config) }
override fun getInstanceId(): InstanceId = qsTileViewModel.config.instanceId
+
override fun getTileLabel(): CharSequence =
with(qsTileViewModel.config.uiConfig) {
when (this) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
index b91dd04..0603d21 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
@@ -24,6 +24,8 @@
* These are the subset of transitions that can be referenced by key when asking for a scene change.
*/
object TransitionKeys {
+ /** Reference to the gone to shade transition with split shade enabled. */
+ val GoneToSplitShade = TransitionKey("GoneToSplitShade")
/** Reference to a scene transition that can collapse the shade scene instantly. */
val CollapseShadeInstantly = TransitionKey("CollapseShadeInstantly")
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index 259a8bf..b971781 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -56,9 +56,8 @@
}
// TODO(b/298525212): remove once Compose exposes window inset bounds.
- override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
- val insets = super.onApplyWindowInsets(windowInsets)
- this.windowInsets.value = insets
- return insets
+ override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets {
+ this.windowInsets.value = windowInsets
+ return windowInsets
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 78704e1..c20d577 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -198,7 +198,7 @@
private fun getDisplayWidth(context: Context): Dp {
val point = Point()
checkNotNull(context.display).getRealSize(point)
- return point.x.dp
+ return point.x.toDp(context)
}
// TODO(b/298525212): remove once Compose exposes window inset bounds.
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
index b0af7f9..016fe57 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
@@ -24,6 +24,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.GoneToSplitShade
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
@@ -70,10 +71,11 @@
)] = UserActionResult(Scenes.QuickSettingsShade)
}
+ val downSceneKey =
+ if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade
+ val downTransitionKey = GoneToSplitShade.takeIf { shadeMode is ShadeMode.Split }
this[Swipe(direction = SwipeDirection.Down)] =
- UserActionResult(
- if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade
- )
+ UserActionResult(downSceneKey, downTransitionKey)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
index 412b089..1e66cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
@@ -86,7 +86,8 @@
viewModel,
LayoutInflater.from(context),
onDismissalRequested = { event, velocity -> requestDismissal(event, velocity) },
- onDismissalCancelled = { animationController.getSwipeReturnAnimation().start() }
+ onDismissalCancelled = { animationController.getSwipeReturnAnimation().start() },
+ onUserInteraction = { callbacks?.onUserInteraction() }
)
view.updateInsets(windowManager.currentWindowMetrics.windowInsets)
addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
index 916d50f..bd93226 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
@@ -30,7 +30,6 @@
import android.widget.ImageView
import com.android.systemui.res.R
import com.android.systemui.screenshot.FloatingWindowUtil
-import kotlin.math.max
class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :
FrameLayout(context, attrs) {
@@ -39,11 +38,20 @@
private lateinit var screenshotStatic: ViewGroup
var onTouchInterceptListener: ((MotionEvent) -> Boolean)? = null
+ var userInteractionCallback: (() -> Unit)? = null
+
private val displayMetrics = context.resources.displayMetrics
private val tmpRect = Rect()
private lateinit var actionsContainerBackground: View
private lateinit var dismissButton: View
+ init {
+ setOnTouchListener({ _: View, _: MotionEvent ->
+ userInteractionCallback?.invoke()
+ true
+ })
+ }
+
override fun onFinishInflate() {
super.onFinishInflate()
// Get focus so that the key events go to the layout.
@@ -79,6 +87,18 @@
val inPortrait = orientation == Configuration.ORIENTATION_PORTRAIT
val cutout = insets.displayCutout
val navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars())
+
+ // When honoring the navbar or other obstacle offsets, include some extra padding above
+ // the inset itself.
+ val verticalPadding =
+ mContext.resources.getDimensionPixelOffset(R.dimen.screenshot_shelf_vertical_margin)
+
+ // Minimum bottom padding to always enforce (e.g. if there's no nav bar)
+ val minimumBottomPadding =
+ context.resources.getDimensionPixelOffset(
+ R.dimen.overlay_action_container_minimum_edge_spacing
+ )
+
if (cutout == null) {
screenshotStatic.setPadding(0, 0, 0, navBarInsets.bottom)
} else {
@@ -86,25 +106,41 @@
if (inPortrait) {
screenshotStatic.setPadding(
waterfall.left,
- max(cutout.safeInsetTop.toDouble(), waterfall.top.toDouble()).toInt(),
+ max(cutout.safeInsetTop, waterfall.top),
waterfall.right,
max(
- cutout.safeInsetBottom.toDouble(),
- max(navBarInsets.bottom.toDouble(), waterfall.bottom.toDouble())
- )
- .toInt()
+ navBarInsets.bottom + verticalPadding,
+ cutout.safeInsetBottom + verticalPadding,
+ waterfall.bottom + verticalPadding,
+ minimumBottomPadding,
+ )
)
} else {
screenshotStatic.setPadding(
- max(cutout.safeInsetLeft.toDouble(), waterfall.left.toDouble()).toInt(),
+ max(cutout.safeInsetLeft, waterfall.left),
waterfall.top,
- max(cutout.safeInsetRight.toDouble(), waterfall.right.toDouble()).toInt(),
- max(navBarInsets.bottom.toDouble(), waterfall.bottom.toDouble()).toInt()
+ max(cutout.safeInsetRight, waterfall.right),
+ max(
+ navBarInsets.bottom + verticalPadding,
+ waterfall.bottom + verticalPadding,
+ minimumBottomPadding,
+ )
)
}
}
}
+ // Max function for two or more params.
+ private fun max(first: Int, second: Int, vararg items: Int): Int {
+ var largest = if (first > second) first else second
+ for (item in items) {
+ if (item > largest) {
+ largest = item
+ }
+ }
+ return largest
+ }
+
private fun getSwipeRegion(): Region {
val swipeRegion = Region()
val padding = FloatingWindowUtil.dpToPx(displayMetrics, -1 * TOUCH_PADDING_DP).toInt()
@@ -127,7 +163,14 @@
private const val TOUCH_PADDING_DP = 12f
}
+ override fun onInterceptHoverEvent(event: MotionEvent): Boolean {
+ userInteractionCallback?.invoke()
+ return super.onInterceptHoverEvent(event)
+ }
+
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
+ userInteractionCallback?.invoke()
+
if (onTouchInterceptListener?.invoke(ev) == true) {
return true
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
index 750bd53..2243ade 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
@@ -37,6 +37,14 @@
// models would remove/create separate views.
drawable?.setIcon(viewModel.appearance.icon)
textView.text = viewModel.appearance.label
+
+ viewModel.appearance.customBackground?.also {
+ if (it.canApplyTheme()) {
+ it.applyTheme(view.rootView.context.theme)
+ }
+ view.background = it
+ }
+
setMargins(iconView, textView, viewModel.appearance.label?.isNotEmpty() ?: false)
if (viewModel.onClicked != null) {
view.setOnClickListener { viewModel.onClicked.invoke() }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index 43c0107..89f904a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -45,6 +45,7 @@
layoutInflater: LayoutInflater,
onDismissalRequested: (event: ScreenshotEvent, velocity: Float?) -> Unit,
onDismissalCancelled: () -> Unit,
+ onUserInteraction: () -> Unit
) {
val swipeGestureListener =
SwipeGestureListener(
@@ -55,6 +56,7 @@
onCancel = onDismissalCancelled
)
view.onTouchInterceptListener = { swipeGestureListener.onMotionEvent(it) }
+ view.userInteractionCallback = onUserInteraction
val previewView: ImageView = view.requireViewById(R.id.screenshot_preview)
val previewViewBlur: ImageView = view.requireViewById(R.id.screenshot_preview_blur)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
index 55a2ad2..2982ea0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
@@ -19,8 +19,11 @@
import android.graphics.drawable.Drawable
/** Data describing how an action should be shown to the user. */
-data class ActionButtonAppearance(
+data class ActionButtonAppearance
+@JvmOverloads
+constructor(
val icon: Drawable?,
val label: CharSequence?,
val description: CharSequence,
+ val customBackground: Drawable? = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 5e1f9c9..f509ef5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -33,6 +33,7 @@
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.GoneToSplitShade
import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
@@ -149,11 +150,13 @@
else -> Scenes.Lockscreen
}
+ val upTransitionKey = GoneToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+
val down = Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single }
return buildMap {
if (!isCustomizing) {
- this[Swipe(SwipeDirection.Up)] = UserActionResult(up)
+ this[Swipe(SwipeDirection.Up)] = UserActionResult(up, upTransitionKey)
} // TODO(b/330200163) Add an else to be able to collapse the shade while customizing
down?.let { this[Swipe(SwipeDirection.Down)] = UserActionResult(down) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 7983db1..2446473 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -199,12 +199,14 @@
protected boolean mPowerPluggedInWired;
protected boolean mPowerPluggedInWireless;
protected boolean mPowerPluggedInDock;
+ protected int mChargingSpeed;
private boolean mPowerCharged;
+ /** Whether the battery defender is triggered. */
private boolean mBatteryDefender;
+ /** Whether the battery defender is triggered with the device plugged. */
private boolean mEnableBatteryDefender;
private boolean mIncompatibleCharger;
- protected int mChargingSpeed;
private int mChargingWattage;
private int mBatteryLevel;
private boolean mBatteryPresent = true;
@@ -1244,7 +1246,7 @@
mChargingSpeed = status.getChargingSpeed(mContext);
mBatteryLevel = status.level;
mBatteryPresent = status.present;
- mBatteryDefender = status.isBatteryDefender();
+ mBatteryDefender = isBatteryDefender(status);
// when the battery is overheated, device doesn't charge so only guard on pluggedIn:
mEnableBatteryDefender = mBatteryDefender && status.isPluggedIn();
mIncompatibleCharger = status.incompatibleCharger.orElse(false);
@@ -1516,6 +1518,11 @@
return mPowerPluggedIn;
}
+ /** Return true if the device is under the battery defender mode. */
+ protected boolean isBatteryDefender(BatteryStatus status) {
+ return status.isBatteryDefender();
+ }
+
private boolean isCurrentUser(int userId) {
return getCurrentUser() == userId;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 70632d5..79218ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -18,6 +18,7 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE;
import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
import android.animation.Animator;
@@ -49,6 +50,7 @@
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus;
import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
@@ -108,6 +110,7 @@
private final UiEventLogger mUiEventLogger;
private final Lazy<InteractionJankMonitor> mInteractionJankMonitorLazy;
private final JavaAdapter mJavaAdapter;
+ private final Lazy<KeyguardTransitionInteractor> mKeyguardTransitionInteractorLazy;
private final Lazy<ShadeInteractor> mShadeInteractorLazy;
private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
private final Lazy<SceneInteractor> mSceneInteractorLazy;
@@ -175,6 +178,7 @@
UiEventLogger uiEventLogger,
Lazy<InteractionJankMonitor> interactionJankMonitorLazy,
JavaAdapter javaAdapter,
+ Lazy<KeyguardTransitionInteractor> keyguardTransitionInteractor,
Lazy<ShadeInteractor> shadeInteractorLazy,
Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy,
Lazy<SceneInteractor> sceneInteractorLazy,
@@ -182,6 +186,7 @@
mUiEventLogger = uiEventLogger;
mInteractionJankMonitorLazy = interactionJankMonitorLazy;
mJavaAdapter = javaAdapter;
+ mKeyguardTransitionInteractorLazy = keyguardTransitionInteractor;
mShadeInteractorLazy = shadeInteractorLazy;
mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy;
mSceneInteractorLazy = sceneInteractorLazy;
@@ -193,6 +198,14 @@
@Override
public void start() {
+ mJavaAdapter.alwaysCollectFlow(
+ mKeyguardTransitionInteractorLazy.get().isFinishedInState(GONE),
+ (Boolean isFinishedInState) -> {
+ if (isFinishedInState) {
+ setLeaveOpenOnKeyguardHide(false);
+ }
+ });
+
mJavaAdapter.alwaysCollectFlow(mShadeInteractorLazy.get().isAnyExpanded(),
this::onShadeOrQsExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index c17da4b..0524589 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -32,6 +32,7 @@
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
@@ -56,10 +57,10 @@
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
-import com.android.systemui.statusbar.phone.ui.StatusBarIconList;
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
import com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl;
+import com.android.systemui.statusbar.phone.ui.StatusBarIconList;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import dagger.Binds;
@@ -209,14 +210,16 @@
/** */
@Provides
@SysUISingleton
- static ActivityTransitionAnimator provideActivityTransitionAnimator() {
- return new ActivityTransitionAnimator();
+ static ActivityTransitionAnimator provideActivityTransitionAnimator(
+ @Main Executor mainExecutor) {
+ return new ActivityTransitionAnimator(mainExecutor);
}
/** */
@Provides
@SysUISingleton
- static DialogTransitionAnimator provideDialogTransitionAnimator(IDreamManager dreamManager,
+ static DialogTransitionAnimator provideDialogTransitionAnimator(@Main Executor mainExecutor,
+ IDreamManager dreamManager,
KeyguardStateController keyguardStateController,
Lazy<AlternateBouncerInteractor> alternateBouncerInteractor,
InteractionJankMonitor interactionJankMonitor,
@@ -243,7 +246,7 @@
}
};
return new DialogTransitionAnimator(
- callback, interactionJankMonitor, animationFeatureFlags);
+ mainExecutor, callback, interactionJankMonitor, animationFeatureFlags);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 6a38f8d..d2d0aaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.row;
import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
+import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.BOTTOM;
+import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.TOP;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -43,6 +45,7 @@
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -354,12 +357,13 @@
@Override
public long performRemoveAnimation(long duration, long delay, float translationDirection,
boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener) {
+ AnimatorListenerAdapter animationListener, ClipSide clipSide) {
enableAppearDrawing(true);
mIsHeadsUpAnimation = isHeadsUpAnimation;
if (mDrawingAppearAnimation) {
startAppearAnimation(false /* isAppearing */, translationDirection,
- delay, duration, onStartedRunnable, onFinishedRunnable, animationListener);
+ delay, duration, onStartedRunnable, onFinishedRunnable, animationListener,
+ clipSide);
} else {
if (onStartedRunnable != null) {
onStartedRunnable.run();
@@ -378,13 +382,13 @@
mIsHeadsUpAnimation = isHeadsUpAppear;
if (mDrawingAppearAnimation) {
startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay,
- duration, null, null, null);
+ duration, null, null, null, ClipSide.BOTTOM);
}
}
private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay,
long duration, final Runnable onStartedRunnable, final Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener) {
+ AnimatorListenerAdapter animationListener, ClipSide clipSide) {
mAnimationTranslationY = translationDirection * getActualHeight();
cancelAppearAnimation();
if (mAppearAnimationFraction == -1.0f) {
@@ -406,9 +410,16 @@
mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE;
targetValue = 0.0f;
}
+
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ // TODO(b/316404716): add avalanche filtering
+ mCurrentAppearInterpolator = Interpolators.LINEAR;
+ }
+
mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
targetValue);
- if (NotificationsImprovedHunAnimation.isEnabled()) {
+ if (NotificationsImprovedHunAnimation.isEnabled()
+ || NotificationHeadsUpCycling.isEnabled()) {
mAppearAnimator.setInterpolator(mCurrentAppearInterpolator);
} else {
mAppearAnimator.setInterpolator(Interpolators.LINEAR);
@@ -418,7 +429,12 @@
mAppearAnimator.addUpdateListener(animation -> {
mAppearAnimationFraction = (float) animation.getAnimatedValue();
updateAppearAnimationAlpha();
- updateAppearRect();
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ // For cycling out, we want the HUN to be clipped from the top.
+ updateAppearRect(clipSide);
+ } else {
+ updateAppearRect();
+ }
invalidate();
});
if (animationListener != null) {
@@ -426,7 +442,11 @@
}
// we need to apply the initial state already to avoid drawn frames in the wrong state
updateAppearAnimationAlpha();
- updateAppearRect();
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ updateAppearRect(clipSide);
+ } else {
+ updateAppearRect();
+ }
mAppearAnimator.addListener(new AnimatorListenerAdapter() {
private boolean mRunWithoutInterruptions;
@@ -508,14 +528,18 @@
enableAppearDrawing(false);
}
- private void updateAppearRect() {
+ /**
+ * Update the View's Rect clipping to fit the appear animation
+ * @param clipSide Which side if view we want to clip from
+ */
+ private void updateAppearRect(ClipSide clipSide) {
float interpolatedFraction =
- NotificationsImprovedHunAnimation.isEnabled() ? mAppearAnimationFraction
+ NotificationsImprovedHunAnimation.isEnabled()
+ || NotificationHeadsUpCycling.isEnabled() ? mAppearAnimationFraction
: mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY;
- final int actualHeight = getActualHeight();
- float bottom = actualHeight * interpolatedFraction;
-
+ final int fullHeight = getActualHeight();
+ float height = fullHeight * interpolatedFraction;
if (mTargetPoint != null) {
int width = getWidth();
float fraction = 1 - mAppearAnimationFraction;
@@ -524,13 +548,26 @@
mAnimationTranslationY
+ (mAnimationTranslationY - mTargetPoint.y) * fraction,
width - (width - mTargetPoint.x) * fraction,
- actualHeight - (actualHeight - mTargetPoint.y) * fraction);
+ fullHeight - (fullHeight - mTargetPoint.y) * fraction);
} else {
- setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
- bottom + mAppearAnimationTranslation);
+ if (clipSide == TOP) {
+ setOutlineRect(
+ 0,
+ /* top= */ fullHeight - height,
+ getWidth(),
+ /* bottom= */ fullHeight
+ );
+ } else if (clipSide == BOTTOM) {
+ setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
+ height + mAppearAnimationTranslation);
+ }
}
}
+ private void updateAppearRect() {
+ updateAppearRect(ClipSide.BOTTOM);
+ }
+
private float getInterpolatedAppearAnimationFraction() {
if (mAppearAnimationFraction >= 0) {
@@ -540,11 +577,36 @@
}
private void updateAppearAnimationAlpha() {
- float contentAlphaProgress = MathUtils.constrain(mAppearAnimationFraction,
- ALPHA_APPEAR_START_FRACTION, ALPHA_APPEAR_END_FRACTION);
- float range = ALPHA_APPEAR_END_FRACTION - ALPHA_APPEAR_START_FRACTION;
- float alpha = (contentAlphaProgress - ALPHA_APPEAR_START_FRACTION) / range;
- setContentAlpha(Interpolators.ALPHA_IN.getInterpolation(alpha));
+ updateAppearAnimationContentAlpha(
+ mAppearAnimationFraction,
+ ALPHA_APPEAR_START_FRACTION,
+ ALPHA_APPEAR_END_FRACTION,
+ Interpolators.ALPHA_IN
+ );
+ }
+
+ /**
+ * Update the alpha value of the content view during the appear animation. We suppose that the
+ * content alpha changes from 0 to 1 during some part of the appear animation.
+ * @param appearFraction the current appearFraction, should be in the range of [0, 1], where
+ * 1 represents fully appeared
+ * @param startFraction the appear fraction when the content view should be
+ * * fully transparent
+ * @param endFraction the appear fraction when the content view should be
+ * fully in-transparent, should be greater or equals to startFraction
+ * @param interpolator the interpolator to update the alpha
+ */
+ private void updateAppearAnimationContentAlpha(
+ float appearFraction,
+ float startFraction,
+ float endFraction,
+ Interpolator interpolator
+ ) {
+ float contentAlphaProgress = MathUtils.constrain(appearFraction, startFraction,
+ endFraction);
+ float range = endFraction - startFraction;
+ float alpha = (contentAlphaProgress - startFraction) / range;
+ setContentAlpha(interpolator.getInterpolation(alpha));
}
private void setContentAlpha(float contentAlpha) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 23c0a0d..747cb3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -3076,7 +3076,7 @@
boolean isHeadsUpAnimation,
Runnable onStartedRunnable,
Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener) {
+ AnimatorListenerAdapter animationListener, ClipSide clipSide) {
if (mMenuRow != null && mMenuRow.isMenuVisible()) {
Animator anim = getTranslateViewAnimator(0f, null /* listener */);
if (anim != null) {
@@ -3092,7 +3092,7 @@
public void onAnimationEnd(Animator animation) {
ExpandableNotificationRow.super.performRemoveAnimation(
duration, delay, translationDirection, isHeadsUpAnimation,
- null, onFinishedRunnable, animationListener);
+ null, onFinishedRunnable, animationListener, ClipSide.BOTTOM);
}
});
anim.start();
@@ -3100,7 +3100,8 @@
}
}
return super.performRemoveAnimation(duration, delay, translationDirection,
- isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener);
+ isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener,
+ clipSide);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 05e8717..2af119f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -362,17 +362,17 @@
/**
* Perform a remove animation on this view.
- * @param duration The duration of the remove animation.
- * @param delay The delay of the animation
+ *
+ * @param duration The duration of the remove animation.
+ * @param delay The delay of the animation
* @param translationDirection The direction value from [-1 ... 1] indicating in which the
* animation should be performed. A value of -1 means that The
* remove animation should be performed upwards,
* such that the child appears to be going away to the top. 1
* Should mean the opposite.
- * @param isHeadsUpAnimation Is this a headsUp animation.
- * @param onFinishedRunnable A runnable which should be run when the animation is finished.
- * @param animationListener An animation listener to add to the animation.
- *
+ * @param isHeadsUpAnimation Is this a headsUp animation.
+ * @param onFinishedRunnable A runnable which should be run when the animation is finished.
+ * @param animationListener An animation listener to add to the animation.
* @return The additional delay, in milliseconds, that this view needs to add before the
* animation starts.
*/
@@ -380,7 +380,12 @@
long delay, float translationDirection, boolean isHeadsUpAnimation,
Runnable onStartedRunnable,
Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener);
+ AnimatorListenerAdapter animationListener, ClipSide clipSide);
+
+ public enum ClipSide {
+ TOP,
+ BOTTOM
+ }
public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
performAddAnimation(delay, duration, isHeadsUpAppear, null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
index 816e5c1..db3cf5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.row
import android.app.Flags
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import javax.inject.Inject
/**
@@ -27,11 +28,14 @@
fun shouldApplyCompactStyle(): Boolean
}
-class HeadsUpStyleProviderImpl @Inject constructor() : HeadsUpStyleProvider {
+class HeadsUpStyleProviderImpl
+@Inject
+constructor(private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore) :
+ HeadsUpStyleProvider {
- /**
- * TODO(b/270709257) This feature is under development. This method returns Compact when the
- * flag is enabled for fish fooding purpose.
- */
- override fun shouldApplyCompactStyle(): Boolean = Flags.compactHeadsUpNotification()
+ override fun shouldApplyCompactStyle(): Boolean {
+ // Use compact HUN for immersive mode.
+ return Flags.compactHeadsUpNotification() &&
+ statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 162e8af..291dc13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -252,7 +252,7 @@
float translationDirection, boolean isHeadsUpAnimation,
Runnable onStartedRunnable,
Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener) {
+ AnimatorListenerAdapter animationListener, ClipSide clipSide) {
// TODO: Use duration
if (onStartedRunnable != null) {
onStartedRunnable.run();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
index 0344b32..d4f8ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
@@ -33,7 +33,12 @@
/** Is the heads-up cycling animation enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.notificationContentAlphaOptimization()
+ get() = Flags.notificationHeadsUpCycling()
+
+ /** Whether to animate the bottom line when transiting from a tall HUN to a short HUN */
+ @JvmStatic
+ inline val animateTallToShort
+ get() = false
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index e520957..5f4e832 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -293,6 +293,8 @@
}
String getAvalancheShowingHunKey() {
+ // If we don't have a previous showing hun, we don't consider the showing hun as avalanche
+ if (isNullAvalancheKey(getAvalanchePreviousHunKey())) return "";
return mAvalancheController.getShowingHunKey();
}
@@ -300,6 +302,11 @@
return mAvalancheController.getPreviousHunKey();
}
+ boolean isNullAvalancheKey(String key) {
+ if (key == null || key.isEmpty()) return true;
+ return key.equals("HeadsUpEntry null") || key.equals("HeadsUpEntry.mEntry null");
+ }
+
void setOverExpansion(float overExpansion) {
mOverExpansion = overExpansion;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
index 5551ab4..bd7bd59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -70,13 +70,14 @@
}
override fun performRemoveAnimation(
- duration: Long,
- delay: Long,
- translationDirection: Float,
- isHeadsUpAnimation: Boolean,
- onStartedRunnable: Runnable?,
- onFinishedRunnable: Runnable?,
- animationListener: AnimatorListenerAdapter?
+ duration: Long,
+ delay: Long,
+ translationDirection: Float,
+ isHeadsUpAnimation: Boolean,
+ onStartedRunnable: Runnable?,
+ onFinishedRunnable: Runnable?,
+ animationListener: AnimatorListenerAdapter?,
+ clipSide: ClipSide
): Long {
return 0
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 773a6bf..1ef9c6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -112,6 +112,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
@@ -127,6 +128,7 @@
import com.android.systemui.util.Assert;
import com.android.systemui.util.ColorUtilKt;
import com.android.systemui.util.DumpUtilsKt;
+import com.android.systemui.util.ListenerSet;
import com.google.errorprone.annotations.CompileTimeConstant;
@@ -151,7 +153,6 @@
public class NotificationStackScrollLayout
extends ViewGroup
implements Dumpable, NotificationScrollView {
-
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
private static final String TAG = "StackScroller";
private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE);
@@ -254,6 +255,7 @@
* The raw amount of the overScroll on the bottom, which is not rubber-banded.
*/
private float mOverScrolledBottomPixels;
+ private ListenerSet<Runnable> mStackHeightChangedListeners = new ListenerSet<>();
private NotificationLogger.OnChildLocationsChangedListener mListener;
private OnNotificationLocationsChangedListener mLocationsChangedListener;
private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
@@ -316,7 +318,7 @@
= new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
- if (SceneContainerFlag.isEnabled() && !mChildrenUpdateRequested) {
+ if (SceneContainerFlag.isEnabled()) {
getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
@@ -1083,6 +1085,10 @@
for (int i = 0; i < size; i++) {
measureChild(getChildAt(i), childWidthSpec, childHeightSpec);
}
+ if (SceneContainerFlag.isEnabled()) {
+ setMaxLayoutHeight(getMeasuredHeight());
+ updateContentHeight();
+ }
Trace.endSection();
}
@@ -1092,6 +1098,22 @@
super.requestLayout();
}
+ private void notifyStackHeightChangedListeners() {
+ for (Runnable listener : mStackHeightChangedListeners) {
+ listener.run();
+ }
+ }
+
+ @Override
+ public void addStackHeightChangedListener(@NonNull Runnable runnable) {
+ mStackHeightChangedListeners.addIfAbsent(runnable);
+ }
+
+ @Override
+ public void removeStackHeightChangedListener(@NonNull Runnable runnable) {
+ mStackHeightChangedListeners.remove(runnable);
+ }
+
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!mSuppressChildrenMeasureAndLayout) {
@@ -1110,8 +1132,10 @@
(int) height);
}
}
- setMaxLayoutHeight(getHeight());
- updateContentHeight();
+ if (!SceneContainerFlag.isEnabled()) {
+ setMaxLayoutHeight(getHeight());
+ updateContentHeight();
+ }
clampScrollPosition();
requestChildrenUpdate();
updateFirstAndLastBackgroundViews();
@@ -1177,11 +1201,6 @@
}
@Override
- public void setStackHeightConsumer(@Nullable Consumer<Float> consumer) {
- mScrollViewFields.setStackHeightConsumer(consumer);
- }
-
- @Override
public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) {
mScrollViewFields.setHeadsUpHeightConsumer(consumer);
}
@@ -1469,9 +1488,10 @@
public void setExpandedHeight(float height) {
final boolean skipHeightUpdate = shouldSkipHeightUpdate();
- // when scene framework is enabled, updateStackPosition is already called by
- // updateTopPadding every time the stack moves, so skip it here to avoid flickering.
- if (!SceneContainerFlag.isEnabled()) {
+ // when scene framework is enabled and in single shade, updateStackPosition is already
+ // called by updateTopPadding every time the stack moves, so skip it here to avoid
+ // flickering.
+ if (!SceneContainerFlag.isEnabled() || mShouldUseSplitNotificationShade) {
updateStackPosition();
}
@@ -2403,16 +2423,25 @@
/* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications,
shelfIntrinsicHeight);
mIntrinsicContentHeight = height;
- mScrollViewFields.sendStackHeight(height + footerIntrinsicHeight);
// The topPadding can be bigger than the regular padding when qs is expanded, in that
// state the maxPanelHeight and the contentHeight should be bigger
mContentHeight =
(int) (height + Math.max(mIntrinsicPadding, getTopPadding()) + mBottomPadding);
+ mScrollViewFields.setIntrinsicStackHeight(
+ (int) (mIntrinsicPadding + mIntrinsicContentHeight + footerIntrinsicHeight
+ + mBottomPadding));
updateScrollability();
clampScrollPosition();
updateStackPosition();
mAmbientState.setContentHeight(mContentHeight);
+
+ notifyStackHeightChangedListeners();
+ }
+
+ @Override
+ public int getIntrinsicStackHeight() {
+ return mScrollViewFields.getIntrinsicStackHeight();
}
/**
@@ -3143,6 +3172,11 @@
type = row.wasJustClicked()
? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
: AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ if (mStackScrollAlgorithm.isCyclingOut(row, mAmbientState)) {
+ type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT;
+ }
+ }
if (row.isChildInGroup()) {
// We can otherwise get stuck in there if it was just isolated
row.setHeadsUpAnimatingAway(false);
@@ -3163,6 +3197,11 @@
if (pinnedAndClosed || shouldHunAppearFromTheBottom) {
// Our custom add animation
type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ if (mStackScrollAlgorithm.isCyclingIn(row, mAmbientState)) {
+ type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN;
+ }
+ }
} else {
// Normal add animation
type = AnimationEvent.ANIMATION_TYPE_ADD;
@@ -6134,6 +6173,22 @@
.animateTopInset()
.animateY()
.animateZ(),
+
+ // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT
+ new AnimationFilter()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ .hasDelays(),
+
+ // ANIMATION_TYPE_HEADS_UP_CYCLING_IN
+ new AnimationFilter()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ .hasDelays(),
};
static int[] LENGTHS = new int[]{
@@ -6185,6 +6240,12 @@
// ANIMATION_TYPE_EVERYTHING
StackStateAnimator.ANIMATION_DURATION_STANDARD,
+
+ // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT
+ StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING,
+
+ // ANIMATION_TYPE_HEADS_UP_CYCLING_IN
+ StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING,
};
static final int ANIMATION_TYPE_ADD = 0;
@@ -6203,6 +6264,8 @@
static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 13;
static final int ANIMATION_TYPE_HEADS_UP_OTHER = 14;
static final int ANIMATION_TYPE_EVERYTHING = 15;
+ static final int ANIMATION_TYPE_HEADS_UP_CYCLING_OUT = 16;
+ static final int ANIMATION_TYPE_HEADS_UP_CYCLING_IN = 17;
final long eventStartTime;
final ExpandableView mChangingView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
index a3827c1..6afcf37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
@@ -45,6 +45,12 @@
var isScrolledToTop: Boolean = true
/**
+ * Height in view pixels at which the Notification Stack would like to be laid out, including
+ * Notification rows, paddings the Shelf and the Footer.
+ */
+ var intrinsicStackHeight: Int = 0
+
+ /**
* When internal NSSL expansion requires the stack to be scrolled (e.g. to keep an expanding
* notification in view), that scroll amount can be sent here and it will be handled by the
* placeholder
@@ -56,11 +62,6 @@
*/
var currentGestureOverscrollConsumer: Consumer<Boolean>? = null
/**
- * Any time the stack height is recalculated, it should be updated here to be used by the
- * placeholder
- */
- var stackHeightConsumer: Consumer<Float>? = null
- /**
* Any time the heads up height is recalculated, it should be updated here to be used by the
* placeholder
*/
@@ -72,8 +73,6 @@
/** send [isCurrentGestureOverscroll] to the [currentGestureOverscrollConsumer], if present. */
fun sendCurrentGestureOverscroll(isCurrentGestureOverscroll: Boolean) =
currentGestureOverscrollConsumer?.accept(isCurrentGestureOverscroll)
- /** send the [stackHeight] to the [stackHeightConsumer], if present. */
- fun sendStackHeight(stackHeight: Float) = stackHeightConsumer?.accept(stackHeight)
/** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */
fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index d0cebae..0fcfc4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -38,6 +38,7 @@
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import java.util.ArrayList;
@@ -75,6 +76,7 @@
private float mSmallCornerRadius;
private float mLargeCornerRadius;
private int mHeadsUpAppearHeightBottom;
+ private int mHeadsUpCyclingPadding;
public StackScrollAlgorithm(
Context context,
@@ -99,6 +101,8 @@
R.dimen.heads_up_status_bar_padding);
mHeadsUpAppearStartAboveScreen = res.getDimensionPixelSize(
R.dimen.heads_up_appear_y_above_screen);
+ mHeadsUpCyclingPadding = context.getResources()
+ .getDimensionPixelSize(R.dimen.heads_up_cycling_padding);
mPinnedZTranslationExtra = res.getDimensionPixelSize(
R.dimen.heads_up_pinned_elevation);
mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
@@ -348,7 +352,8 @@
&& !firstHeadsUp
&& (isHeadsUp || child.isHeadsUpAnimatingAway())
&& newNotificationEnd > firstHeadsUpEnd
- && !ambientState.isShadeExpanded()) {
+ && !ambientState.isShadeExpanded()
+ && !skipClipBottomForCycling(child, ambientState)) {
// The bottom of this view is peeking out from under the previous view.
// Clip the part that is peeking out.
float overlapAmount = newNotificationEnd - firstHeadsUpEnd;
@@ -370,6 +375,44 @@
}
}
+ /**
+ * @return Should we skip clipping the bottom clipping when new hun has lower bottom line for
+ * the hun cycling animation.
+ */
+ private boolean skipClipBottomForCycling(ExpandableView view, AmbientState ambientState) {
+ if (!NotificationHeadsUpCycling.isEnabled()) return false;
+ if (!isCyclingOut(view, ambientState)) return false;
+ // skip bottom clipping if we animate the bottom line
+ return NotificationHeadsUpCycling.getAnimateTallToShort();
+ }
+
+ /**
+ * Whether the view is the hun that is cycling out by the notification avalanche.
+ */
+ public boolean isCyclingOut(ExpandableView view, AmbientState ambientState) {
+ if (!NotificationHeadsUpCycling.isEnabled()) return false;
+ if (!(view instanceof ExpandableNotificationRow)) return false;
+ return isCyclingOut((ExpandableNotificationRow) view, ambientState);
+ }
+
+ /**
+ * Whether the row is the hun that is cycling out by the notification avalanche.
+ */
+ public boolean isCyclingOut(ExpandableNotificationRow row, AmbientState ambientState) {
+ if (!NotificationHeadsUpCycling.isEnabled()) return false;
+ String cyclingOutKey = ambientState.getAvalanchePreviousHunKey();
+ return row.getEntry().getKey().equals(cyclingOutKey);
+ }
+
+ /**
+ * Whether the row is the hun that is cycling in by the notification avalanche.
+ */
+ public boolean isCyclingIn(ExpandableNotificationRow row, AmbientState ambientState) {
+ if (!NotificationHeadsUpCycling.isEnabled()) return false;
+ String cyclingInKey = ambientState.getAvalancheShowingHunKey();
+ return row.getEntry().getKey().equals(cyclingInKey);
+ }
+
/** Updates the dimmed and hiding sensitive states of the children. */
private void updateDimmedAndHideSensitive(AmbientState ambientState,
StackScrollAlgorithmState algorithmState) {
@@ -799,6 +842,7 @@
}
ExpandableNotificationRow topHeadsUpEntry = null;
+ int cyclingInHunHeight = -1;
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
if (!(child instanceof ExpandableNotificationRow row)) {
@@ -839,6 +883,13 @@
childState.setYTranslation(
Math.max(childState.getYTranslation(), headsUpTranslation));
childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ if (isCyclingIn(row, ambientState)) {
+ if (cyclingInHunHeight == -1) {
+ cyclingInHunHeight = childState.height;
+ }
+ }
+ }
childState.hidden = false;
ExpandableViewState topState =
topHeadsUpEntry == null ? null : topHeadsUpEntry.getViewState();
@@ -860,6 +911,26 @@
}
}
if (row.isHeadsUpAnimatingAway()) {
+ if (NotificationHeadsUpCycling.isEnabled() && isCyclingOut(row, ambientState)) {
+ // If the two HUNs in the cycling animation have different heights, we need
+ // an extra y translation to align the animation.
+ int extraTranslation;
+ if (NotificationHeadsUpCycling.getAnimateTallToShort()) {
+ if (cyclingInHunHeight > 0) {
+ extraTranslation = cyclingInHunHeight - childState.height;
+ } else {
+ extraTranslation = 0;
+ }
+ } else {
+ extraTranslation = cyclingInHunHeight >= childState.height
+ ? cyclingInHunHeight - childState.height : 0;
+ }
+ extraTranslation += mHeadsUpCyclingPadding;
+ float inSpaceTranslation = Math.max(childState.getYTranslation(),
+ headsUpTranslation);
+ childState.setYTranslation(inSpaceTranslation + extraTranslation);
+ cyclingInHunHeight = -1;
+ } else
if (NotificationsImprovedHunAnimation.isEnabled() && !ambientState.isDozing()) {
if (shouldHunAppearFromBottom(ambientState, childState)) {
// move to the bottom of the screen
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 5963d35..5dc5449 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.stack;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK;
@@ -57,6 +59,7 @@
public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
+ public static final int ANIMATION_DURATION_HEADS_UP_CYCLING = 400;
public static final int ANIMATION_DURATION_FOLD_TO_AOD =
AnimatableClockView.ANIMATION_DURATION_FOLD_TO_AOD;
public static final int ANIMATION_DURATION_PRIORITY_CHANGE = 500;
@@ -68,6 +71,8 @@
@VisibleForTesting int mGoToFullShadeAppearingTranslation;
@VisibleForTesting float mHeadsUpAppearStartAboveScreen;
+ // Padding between the old and new heads up notifications for the hun cycling animation
+ private float mHeadsUpCyclingPadding;
private final ExpandableViewState mTmpState = new ExpandableViewState();
private final AnimationProperties mAnimationProperties;
public NotificationStackScrollLayout mHostLayout;
@@ -125,6 +130,8 @@
R.dimen.go_to_full_shade_appearing_translation);
mHeadsUpAppearStartAboveScreen = context.getResources()
.getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
+ mHeadsUpCyclingPadding = context.getResources()
+ .getDimensionPixelSize(R.dimen.heads_up_cycling_padding);
}
protected void setLogger(StackStateLogger logger) {
@@ -449,7 +456,8 @@
}
changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
- startAnimation, postAnimation, getGlobalAnimationFinishedListener());
+ startAnimation, postAnimation, getGlobalAnimationFinishedListener(),
+ ExpandableView.ClipSide.BOTTOM);
needsCustomAnimation = true;
} else if (event.animationType ==
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
@@ -464,6 +472,27 @@
.AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
ExpandableNotificationRow row = (ExpandableNotificationRow) event.mChangingView;
row.prepareExpansionChanged();
+ } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_IN) {
+ mHeadsUpAppearChildren.add(changingView);
+
+ mTmpState.copyFrom(changingView.getViewState());
+ mTmpState.setYTranslation(changingView.getViewState().getYTranslation()
+ + getHeadsUpCyclingInYTranslationStart(event.headsUpFromBottom));
+ mTmpState.applyToView(changingView);
+
+ // TODO(b/339519404): use a different interpolator
+ Runnable onAnimationEnd = null;
+ if (loggable) {
+ // This only captures HEADS_UP_APPEAR animations, but HUNs can appear with
+ // normal ADD animations, which would not be logged here.
+ String finalKey = key;
+ mLogger.logHUNViewAppearing(key);
+ onAnimationEnd = () -> {
+ mLogger.appearAnimationEnded(finalKey);
+ };
+ }
+ changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_CYCLING,
+ /* isHeadsUpAppear= */ true, onAnimationEnd);
} else if (NotificationsImprovedHunAnimation.isEnabled()
&& (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR)) {
mHeadsUpAppearChildren.add(changingView);
@@ -486,6 +515,87 @@
}
changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR,
/* isHeadsUpAppear= */ true, onAnimationEnd);
+ } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_OUT) {
+ mHeadsUpDisappearChildren.add(changingView);
+ Runnable endRunnable = null;
+ mTmpState.copyFrom(changingView.getViewState());
+
+ if (changingView.getParent() == null) {
+ // This notification was actually removed, so we need to add it
+ // transiently
+ mHostLayout.addTransientView(changingView, 0);
+ changingView.setTransientContainer(mHostLayout);
+ // TODO(b/316404716): remove the hard-coded height
+ // StackScrollAlgorithm cannot find this view because it has been removed
+ // from the NSSL. To correctly translate the view to the top or bottom of
+ // the screen (where it animated from), we need to update its translation.
+ mTmpState.setYTranslation(
+ mTmpState.getYTranslation() + 10
+ );
+ endRunnable = changingView::removeFromTransientContainer;
+ }
+
+ boolean needsAnimation = true;
+ if (changingView instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row =
+ (ExpandableNotificationRow) changingView;
+ if (row.isDismissed()) {
+ needsAnimation = false;
+ }
+ }
+ if (needsAnimation) {
+ // We need to add the global animation listener, since once no animations are
+ // running anymore, the panel will instantly hide itself. We need to wait until
+ // the animation is fully finished for this though.
+ final Runnable tmpEndRunnable = endRunnable;
+ Runnable postAnimation;
+ Runnable startAnimation;
+ if (loggable) {
+ String finalKey1 = key;
+ final boolean finalIsHeadsUp = isHeadsUp;
+ final String type = "ANIMATION_TYPE_HEADS_UP_CYCLING_OUT";
+ startAnimation = () -> {
+ mLogger.animationStart(finalKey1, type, finalIsHeadsUp);
+ changingView.setInRemovalAnimation(true);
+ };
+ postAnimation = () -> {
+ mLogger.animationEnd(finalKey1, type, finalIsHeadsUp);
+ changingView.setInRemovalAnimation(false);
+ if (tmpEndRunnable != null) {
+ tmpEndRunnable.run();
+ }
+
+ };
+ } else {
+ postAnimation = () -> {
+ changingView.setInRemovalAnimation(false);
+ if (tmpEndRunnable != null) {
+ tmpEndRunnable.run();
+ }
+ };
+ startAnimation = () -> {
+ changingView.setInRemovalAnimation(true);
+ };
+ }
+ long removeAnimationDelay = changingView.performRemoveAnimation(
+ ANIMATION_DURATION_HEADS_UP_CYCLING,
+ /* delay= */ 0,
+ // It's a shame that translationDirection isn't where we do the y
+ // translation, the actual translation is in StackScrollAlgorithm.
+ /* translationDirection= */ 0.0f,
+ /* isHeadsUpAnimation= */ true,
+ startAnimation, postAnimation,
+ getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.TOP);
+ mAnimationProperties.delay += removeAnimationDelay;
+ mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_CYCLING;
+ mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
+ Interpolators.LINEAR);
+ mAnimationProperties.getAnimationFilter().animateY = true;
+ mTmpState.animateTo(changingView, mAnimationProperties);
+ } else if (endRunnable != null) {
+ endRunnable.run();
+ }
+ needsCustomAnimation |= needsAnimation;
} else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) {
NotificationsImprovedHunAnimation.assertInLegacyMode();
// This item is added, initialize its properties.
@@ -565,21 +675,21 @@
}
};
} else {
+ startAnimation = () -> {
+ changingView.setInRemovalAnimation(true);
+ };
postAnimation = () -> {
changingView.setInRemovalAnimation(false);
if (tmpEndRunnable != null) {
tmpEndRunnable.run();
}
};
- startAnimation = () -> {
- changingView.setInRemovalAnimation(true);
- };
}
long removeAnimationDelay = changingView.performRemoveAnimation(
ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
0, 0.0f, true /* isHeadsUpAppear */,
startAnimation, postAnimation,
- getGlobalAnimationFinishedListener());
+ getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.BOTTOM);
mAnimationProperties.delay += removeAnimationDelay;
if (NotificationsImprovedHunAnimation.isEnabled()) {
mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
@@ -607,6 +717,38 @@
return -mStackTopMargin - mHeadsUpAppearStartAboveScreen;
}
+ /**
+ * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen
+ * @return The start y translation of the HUN cycling in animation
+ */
+ private float getHeadsUpCyclingInYTranslationStart(boolean headsUpFromBottom) {
+ if (headsUpFromBottom) {
+ // start from the bottom of the screen
+ return mHeadsUpAppearHeightBottom + mHeadsUpCyclingPadding;
+ }
+ // start from the top of the screen
+ return -mHeadsUpCyclingPadding;
+ }
+
+ /**
+ * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen
+ * @param oldHunHeight Height of the old HUN
+ * @param newHunHeight Height of the new HUN
+ * @return The y translation target value of the HUN cycling out animation
+ */
+ private float getHeadsUpCyclingOutYTranslation(
+ boolean headsUpFromBottom,
+ int oldHunHeight,
+ int newHunHeight
+ ) {
+ final float translationDistance = mHeadsUpCyclingPadding + newHunHeight - oldHunHeight;
+ if (headsUpFromBottom) {
+ // start from the bottom of the screen
+ return mHeadsUpAppearHeightBottom - translationDistance;
+ }
+ return translationDistance;
+ }
+
public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
final boolean isRubberbanded) {
final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt
index 920c9c2..463c631 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt
@@ -27,13 +27,6 @@
@SysUISingleton
class NotificationViewHeightRepository @Inject constructor() {
- /**
- * The height in px of the contents of notification stack. Depending on the number of
- * notifications, this can exceed the space available on screen to show notifications, at which
- * point the notification stack should become scrollable.
- */
- val stackHeight = MutableStateFlow(0f)
-
/** The height in px of the current heads up notification. */
val headsUpHeight = MutableStateFlow(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index b94da38..365ead6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -65,13 +65,6 @@
}
.distinctUntilChanged()
- /**
- * The height in px of the contents of notification stack. Depending on the number of
- * notifications, this can exceed the space available on screen to show notifications, at which
- * point the notification stack should become scrollable.
- */
- val stackHeight: StateFlow<Float> = viewHeightRepository.stackHeight.asStateFlow()
-
/** The height in px of the contents of the HUN. */
val headsUpHeight: StateFlow<Float> = viewHeightRepository.headsUpHeight.asStateFlow()
@@ -123,11 +116,6 @@
placeholderRepository.shadeScrimBounds.value = bounds
}
- /** Sets the height of the contents of the notification stack. */
- fun setStackHeight(height: Float) {
- viewHeightRepository.stackHeight.value = height
- }
-
/** Sets the height of heads up notification. */
fun setHeadsUpHeight(height: Float) {
viewHeightRepository.headsUpHeight.value = height
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index 2c88845..14b882f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -25,6 +25,13 @@
* notification stack, but is otherwise agnostic to the content.
*/
interface NotificationScrollView {
+
+ /**
+ * Height in view pixels at which the Notification Stack would like to be laid out, including
+ * Notification rows, paddings the Shelf and the Footer.
+ */
+ val intrinsicStackHeight: Int
+
/**
* Since this is an interface rather than a literal View, this provides cast-like access to the
* underlying view.
@@ -53,8 +60,6 @@
fun setSyntheticScrollConsumer(consumer: Consumer<Float>?)
/** Set a consumer for current gesture overscroll events */
fun setCurrentGestureOverscrollConsumer(consumer: Consumer<Boolean>?)
- /** Set a consumer for stack height changed events */
- fun setStackHeightConsumer(consumer: Consumer<Float>?)
/** Set a consumer for heads up height changed events */
fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?)
@@ -66,4 +71,10 @@
/** Sets whether the view is displayed in doze mode. */
fun setDozing(dozing: Boolean)
+
+ /** Sets a listener to be notified, when the stack height might have changed. */
+ fun addStackHeightChangedListener(runnable: Runnable)
+
+ /** @see addStackHeightChangedListener */
+ fun removeStackHeightChangedListener(runnable: Runnable)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 26f7ad7..3c44713 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -90,12 +90,10 @@
launchAndDispose {
view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer)
- view.setStackHeightConsumer(viewModel.stackHeightConsumer)
view.setHeadsUpHeightConsumer(viewModel.headsUpHeightConsumer)
DisposableHandle {
view.setSyntheticScrollConsumer(null)
view.setCurrentGestureOverscrollConsumer(null)
- view.setStackHeightConsumer(null)
view.setHeadsUpHeightConsumer(null)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index b2184db..082f6b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -151,8 +151,6 @@
*/
val currentGestureOverscrollConsumer: (Boolean) -> Unit =
stackAppearanceInteractor::setCurrentGestureOverscroll
- /** Receives the height of the contents of the notification stack. */
- val stackHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setStackHeight
/** Receives the height of the heads up notification. */
val headsUpHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setHeadsUpHeight
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 11eaf54..736058a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -87,13 +87,6 @@
val shadeScrimRounding: Flow<ShadeScrimRounding> =
interactor.shadeScrimRounding.dumpWhileCollecting("shadeScrimRounding")
- /**
- * The height in px of the contents of notification stack. Depending on the number of
- * notifications, this can exceed the space available on screen to show notifications, at which
- * point the notification stack should become scrollable.
- */
- val stackHeight: StateFlow<Float> = interactor.stackHeight.dumpValue("stackHeight")
-
/** The height in px of the contents of the HUN. */
val headsUpHeight: StateFlow<Float> = interactor.headsUpHeight.dumpValue("headsUpHeight")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index be6bef7..aa55f37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static android.app.StatusBarManager.DISABLE_HOME;
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.WindowVisibleState;
@@ -1006,14 +1007,8 @@
// this handling this post-init task. We force an update in this case, and use a new
// token to not conflict with any other disabled flags already requested by SysUI
Binder token = new Binder();
- int userId = mContext.getUserId();
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- info.setNavigationHomeDisabled(true);
- mBarService.disableForUser(info, token, mContext.getPackageName(),
- userId, "set the initial view visibility");
-
- mBarService.disableForUser(new StatusBarManager.DisableInfo(), token,
- mContext.getPackageName(), userId, "set the initial view visibility");
+ mBarService.disable(DISABLE_HOME, token, mContext.getPackageName());
+ mBarService.disable(0, token, mContext.getPackageName());
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
@@ -2184,7 +2179,9 @@
}
if (mStatusBarStateController.leaveOpenOnKeyguardHide()) {
if (!mStatusBarStateController.isKeyguardRequested()) {
- mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
+ if (!MigrateClocksToBlueprint.isEnabled()) {
+ mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
+ }
}
long delay = mKeyguardStateController.calculateGoingToFullShadeDelay();
mLockscreenShadeTransitionController.onHideKeyguard(delay, previousState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
index d9d909a..fc54f14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
@@ -33,6 +33,18 @@
*/
val isOpportunistic: Boolean = false,
+ /**
+ * True if this subscription **only** supports non-terrestrial networks (NTN) and false
+ * otherwise. (non-terrestrial == satellite)
+ *
+ * Note that we intend to filter these subscriptions out, because these connections are actually
+ * supported by
+ * [com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository]. See
+ * [com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor] for
+ * the filtering.
+ */
+ val isExclusivelyNonTerrestrial: Boolean = false,
+
/** Subscriptions in the same group may be filtered or treated as a single subscription */
val groupUuid: ParcelUuid? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 2278597..425c58b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -23,6 +23,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.StateFlow
@@ -76,7 +77,17 @@
*/
val isInService: StateFlow<Boolean>
- /** Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork] */
+ /**
+ * True if this subscription is actively connected to a non-terrestrial network and false
+ * otherwise. Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork].
+ *
+ * Notably: This value reflects that this subscription is **currently** using a non-terrestrial
+ * network, because some subscriptions can switch between terrestrial and non-terrestrial
+ * networks. [SubscriptionModel.isExclusivelyNonTerrestrial] reflects whether a subscription is
+ * configured to exclusively connect to non-terrestrial networks. [isNonTerrestrial] can change
+ * during the lifetime of a subscription but [SubscriptionModel.isExclusivelyNonTerrestrial]
+ * will stay constant.
+ */
val isNonTerrestrial: StateFlow<Boolean>
/** True if [android.telephony.SignalStrength] told us that this connection is using GSM */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 5d91ef3..0073e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -424,6 +424,7 @@
SubscriptionModel(
subscriptionId = subscriptionId,
isOpportunistic = isOpportunistic,
+ isExclusivelyNonTerrestrial = isOnlyNonTerrestrialNetwork,
groupUuid = groupUuid,
carrierName = carrierName.toString(),
profileClass = profileClass,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index d555c47..91d7ca6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -172,21 +172,33 @@
private val unfilteredSubscriptions: Flow<List<SubscriptionModel>> =
mobileConnectionsRepo.subscriptions
- /**
- * Any filtering that we can do based purely on the info of each subscription. Currently this
- * only applies the ProfileClass-based filter, but if we need other they can go here
- */
+ /** Any filtering that we can do based purely on the info of each subscription individually. */
private val subscriptionsBasedFilteredSubs =
- unfilteredSubscriptions.map { subs -> applyProvisioningFilter(subs) }.distinctUntilChanged()
+ unfilteredSubscriptions
+ .map { it.filterBasedOnProvisioning().filterBasedOnNtn() }
+ .distinctUntilChanged()
- private fun applyProvisioningFilter(subs: List<SubscriptionModel>): List<SubscriptionModel> =
+ private fun List<SubscriptionModel>.filterBasedOnProvisioning(): List<SubscriptionModel> =
if (!featureFlagsClassic.isEnabled(FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS)) {
- subs
+ this
} else {
- subs.filter { it.profileClass != PROFILE_CLASS_PROVISIONING }
+ this.filter { it.profileClass != PROFILE_CLASS_PROVISIONING }
}
/**
+ * Subscriptions that exclusively support non-terrestrial networks should **never** directly
+ * show any iconography in the status bar. These subscriptions only exist to provide a backing
+ * for the device-based satellite connections, and the iconography for those connections are
+ * already being handled in
+ * [com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository]. We
+ * need to filter out those subscriptions here so we guarantee the subscription never turns into
+ * an icon. See b/336881301.
+ */
+ private fun List<SubscriptionModel>.filterBasedOnNtn(): List<SubscriptionModel> {
+ return this.filter { !it.isExclusivelyNonTerrestrial }
+ }
+
+ /**
* Generally, SystemUI wants to show iconography for each subscription that is listed by
* [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only
* show a single representation of the pair of subscriptions. The docs define opportunistic as:
@@ -204,12 +216,8 @@
subscriptionsBasedFilteredSubs,
mobileConnectionsRepo.activeMobileDataSubscriptionId,
connectivityRepository.vcnSubId,
- ) { unfilteredSubs, activeId, vcnSubId ->
- filterSubsBasedOnOpportunistic(
- unfilteredSubs,
- activeId,
- vcnSubId,
- )
+ ) { preFilteredSubs, activeId, vcnSubId ->
+ filterSubsBasedOnOpportunistic(preFilteredSubs, activeId, vcnSubId)
}
.distinctUntilChanged()
.logDiffsForTable(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 2670a95..fa8a7d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -253,6 +253,7 @@
if (nextList.isEmpty()) {
log { "NO MORE TO SHOW" }
+ previousHunKey = ""
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/LocationControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/LocationControllerExt.kt
new file mode 100644
index 0000000..ee1b565
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/LocationControllerExt.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import com.android.systemui.statusbar.policy.LocationController
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onStart
+
+fun LocationController.isLocationEnabledFlow(): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val locationCallback =
+ object : LocationController.LocationChangeCallback {
+ override fun onLocationSettingsChanged(locationEnabled: Boolean) {
+ trySend(locationEnabled)
+ }
+ }
+ addCallback(locationCallback)
+ awaitClose { removeCallback(locationCallback) }
+ }
+ .onStart { emit(isLocationEnabled) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
index ab6a37b..d9e19d8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
@@ -17,23 +17,15 @@
package com.android.systemui.util.kotlin
import android.content.SharedPreferences
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.mapNotNull
object SharedPreferencesExt {
- /**
- * Returns a flow of [Unit] that is invoked each time shared preference is updated.
- *
- * @param key Optional key to limit updates to a particular key.
- */
- fun SharedPreferences.observe(key: String? = null): Flow<Unit> =
- conflatedCallbackFlow {
- val listener =
- SharedPreferences.OnSharedPreferenceChangeListener { _, key -> trySend(key) }
- registerOnSharedPreferenceChangeListener(listener)
- awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
- }
- .mapNotNull { changedKey -> if ((key ?: changedKey) == changedKey) Unit else null }
+ /** Returns a flow of [Unit] that is invoked each time shared preference is updated. */
+ fun SharedPreferences.observe(): Flow<Unit> = conflatedCallbackFlow {
+ val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) }
+ registerOnSharedPreferenceChangeListener(listener)
+ awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index c69fb66..b436eb9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -174,9 +174,6 @@
private static final String TYPE_DISMISS = "dismiss";
/** Volume dialog slider animation. */
private static final String TYPE_UPDATE = "update";
- static final int PROGRESS_HAPTICS_DISABLED = 0;
- static final int PROGRESS_HAPTICS_EAGER = 1;
- static final int PROGRESS_HAPTICS_ANIMATED = 2;
/**
* TODO(b/290612381): remove lingering animations or tolerate them
@@ -2112,7 +2109,7 @@
row.anim.setIntValues(progress, newProgress);
// The animator can't keep up with the volume changes so haptics need to be
// triggered here. This happens when the volume keys are continuously pressed.
- row.deliverOnProgressChangedHaptics(false, newProgress, PROGRESS_HAPTICS_EAGER);
+ row.deliverOnProgressChangedHaptics(false, newProgress);
}
row.animTargetProgress = newProgress;
row.anim.setDuration(UPDATE_ANIMATION_DURATION);
@@ -2127,13 +2124,14 @@
}
}
- @VisibleForTesting int progressHapticsForStream(int stream) {
+ @VisibleForTesting
+ boolean canDeliverProgressHapticsToStream(int stream, boolean fromUser, int progress) {
for (VolumeRow row: mRows) {
if (row.stream == stream) {
- return row.mProgressHapticsType;
+ return row.deliverOnProgressChangedHaptics(fromUser, progress);
}
}
- return PROGRESS_HAPTICS_DISABLED;
+ return false;
}
private void recheckH(VolumeRow row) {
@@ -2527,8 +2525,7 @@
if (fromUser || mRow.animTargetProgress == progress) {
// Deliver user-generated slider haptics immediately, or when the animation
// completes
- mRow.deliverOnProgressChangedHaptics(
- fromUser, progress, PROGRESS_HAPTICS_ANIMATED);
+ mRow.deliverOnProgressChangedHaptics(fromUser, progress);
}
}
if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
@@ -2641,7 +2638,6 @@
private int animTargetProgress;
private int lastAudibleLevel = 1;
private SeekbarHapticPlugin mHapticPlugin;
- private int mProgressHapticsType = PROGRESS_HAPTICS_DISABLED;
void setIcon(int iconRes, Resources.Theme theme) {
if (icon != null) {
@@ -2683,15 +2679,23 @@
slider.setOnTouchListener(null);
}
- void deliverOnProgressChangedHaptics(boolean fromUser, int progress, int hapticsType) {
- if (mHapticPlugin == null) return;
+ /**
+ * Deliver haptics when the progress of the slider has changed.
+ *
+ * @param fromUser True if the progress changed was caused by the user.
+ * @param progress The progress value of the slider.
+ * @return True if haptics were successfully delivered. False otherwise. This will happen
+ * if mHapticPlugin is null
+ */
+ boolean deliverOnProgressChangedHaptics(boolean fromUser, int progress) {
+ if (mHapticPlugin == null) return false;
mHapticPlugin.onProgressChanged(slider, progress, fromUser);
if (!fromUser) {
// Consider a change from program as the volume key being continuously pressed
mHapticPlugin.onKeyDown();
}
- mProgressHapticsType = hapticsType;
+ return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 0386338..c08cd64 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -49,6 +49,11 @@
private val uiEventLogger: UiEventLogger,
) : SliderViewModel {
+ private val streamsAffectedByRing =
+ setOf(
+ AudioManager.STREAM_RING,
+ AudioManager.STREAM_NOTIFICATION,
+ )
private val audioStream = audioStreamWrapper.audioStream
private val iconsByStream =
mapOf(
@@ -125,15 +130,42 @@
isEnabled: Boolean,
ringerMode: RingerMode,
): State {
+ val label =
+ labelsByStream[audioStream]?.let(context::getString)
+ ?: error("No label for the stream: $audioStream")
return State(
value = volume.toFloat(),
valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
icon = getIcon(ringerMode),
- label = labelsByStream[audioStream]?.let(context::getString)
- ?: error("No label for the stream: $audioStream"),
+ label = label,
disabledMessage = disabledTextByStream[audioStream]?.let(context::getString),
isEnabled = isEnabled,
a11yStep = volumeRange.step,
+ a11yClickDescription =
+ context.getString(
+ if (isMuted) {
+ R.string.volume_panel_hint_unmute
+ } else {
+ R.string.volume_panel_hint_mute
+ },
+ label,
+ ),
+ a11yStateDescription =
+ if (volume == volumeRange.first) {
+ context.getString(
+ if (audioStream.value in streamsAffectedByRing) {
+ if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+ R.string.volume_panel_hint_vibrate
+ } else {
+ R.string.volume_panel_hint_muted
+ }
+ } else {
+ R.string.volume_panel_hint_muted
+ }
+ )
+ } else {
+ null
+ },
audioStreamModel = this,
isMutable = audioVolumeInteractor.isAffectedByMute(audioStream),
)
@@ -143,27 +175,14 @@
val isMutedOrNoVolume = isMuted || volume == minVolume
val iconRes =
if (isMutedOrNoVolume) {
- when (audioStream.value) {
- AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off
- AudioManager.STREAM_BLUETOOTH_SCO -> R.drawable.ic_volume_off
- AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off
- AudioManager.STREAM_RING ->
- if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
- R.drawable.ic_volume_ringer_vibrate
- } else {
- R.drawable.ic_volume_off
- }
- AudioManager.STREAM_NOTIFICATION ->
- if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
- R.drawable.ic_volume_ringer_vibrate
- } else {
- R.drawable.ic_volume_off
- }
- AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off
- else -> {
- Log.wtf(TAG, "No icon for the stream: $audioStream")
+ if (audioStream.value in streamsAffectedByRing) {
+ if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+ R.drawable.ic_volume_ringer_vibrate
+ } else {
R.drawable.ic_volume_off
}
+ } else {
+ R.drawable.ic_volume_off
}
} else {
iconsByStream[audioStream]
@@ -186,6 +205,8 @@
override val disabledMessage: String?,
override val isEnabled: Boolean,
override val a11yStep: Int,
+ override val a11yClickDescription: String?,
+ override val a11yStateDescription: String?,
override val isMutable: Boolean,
val audioStreamModel: AudioStreamModel,
) : SliderState
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index 956ab66..10714d1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -68,7 +68,7 @@
icon = Icon.Resource(R.drawable.ic_cast, null),
label = context.getString(R.string.media_device_cast),
isEnabled = true,
- a11yStep = 1
+ a11yStep = 1,
)
}
@@ -85,6 +85,12 @@
override val isMutable: Boolean
get() = false
+
+ override val a11yClickDescription: String?
+ get() = null
+
+ override val a11yStateDescription: String?
+ get() = null
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
index d71a9d8..c951928 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
@@ -34,6 +34,8 @@
* enough to trigger rounding to the correct value.
*/
val a11yStep: Int
+ val a11yClickDescription: String?
+ val a11yStateDescription: String?
val disabledMessage: String?
val isMutable: Boolean
@@ -44,6 +46,8 @@
override val label: String = ""
override val disabledMessage: String? = null
override val a11yStep: Int = 0
+ override val a11yClickDescription: String? = null
+ override val a11yStateDescription: String? = null
override val isEnabled: Boolean = true
override val isMutable: Boolean = false
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index e72027a..6f550ba 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -371,7 +371,7 @@
}
@Test
- fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToOne() =
+ fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToZero() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN))
@@ -434,6 +434,27 @@
}
@Test
+ fun listenForAnyStateToDozingTransition_UpdatesClockDozeAmountToOne() =
+ runBlocking(IMMEDIATE) {
+ val transitionStep = MutableStateFlow(TransitionStep())
+ whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.DOZING))
+ .thenReturn(transitionStep)
+
+ val job = underTest.listenForAnyStateToDozingTransition(this)
+ transitionStep.value =
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ transitionState = TransitionState.STARTED,
+ )
+ yield()
+
+ verify(animations, times(2)).doze(1f)
+
+ job.cancel()
+ }
+
+ @Test
fun unregisterListeners_validate() =
runBlocking(IMMEDIATE) {
underTest.unregisterListeners()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 25e5470..3164f8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility;
+import static com.android.systemui.accessibility.Magnification.DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -23,11 +25,17 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -39,6 +47,7 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
@@ -47,6 +56,7 @@
import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -58,9 +68,12 @@
*/
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class IMagnificationConnectionTest extends SysuiTestCase {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
@Mock
private AccessibilityManager mAccessibilityManager;
@@ -90,6 +103,7 @@
private IMagnificationConnection mIMagnificationConnection;
private Magnification mMagnification;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
+ private TestableLooper mTestableLooper;
@Before
public void setUp() throws Exception {
@@ -100,8 +114,10 @@
return null;
}).when(mAccessibilityManager).setMagnificationConnection(
any(IMagnificationConnection.class));
+ mTestableLooper = TestableLooper.get(this);
+ assertNotNull(mTestableLooper);
mMagnification = new Magnification(getContext(),
- getContext().getMainThreadHandler(), getContext().getMainExecutor(), mCommandQueue,
+ mTestableLooper.getLooper(), getContext().getMainExecutor(), mCommandQueue,
mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger);
mMagnification.mWindowMagnificationControllerSupplier =
@@ -122,7 +138,7 @@
public void enableWindowMagnification_passThrough() throws RemoteException {
mIMagnificationConnection.enableWindowMagnification(TEST_DISPLAY, 3.0f, Float.NaN,
Float.NaN, 0f, 0f, mAnimationCallback);
- waitForIdleSync();
+ processAllPendingMessages();
verify(mWindowMagnificationController).enableWindowMagnification(eq(3.0f),
eq(Float.NaN), eq(Float.NaN), eq(0f), eq(0f), eq(mAnimationCallback));
@@ -131,7 +147,7 @@
@Test
public void onFullscreenMagnificationActivationChanged_passThrough() throws RemoteException {
mIMagnificationConnection.onFullscreenMagnificationActivationChanged(TEST_DISPLAY, true);
- waitForIdleSync();
+ processAllPendingMessages();
verify(mFullscreenMagnificationController)
.onFullscreenMagnificationActivationChanged(eq(true));
@@ -141,7 +157,7 @@
public void disableWindowMagnification_deleteWindowMagnification() throws RemoteException {
mIMagnificationConnection.disableWindowMagnification(TEST_DISPLAY,
mAnimationCallback);
- waitForIdleSync();
+ processAllPendingMessages();
verify(mWindowMagnificationController).deleteWindowMagnification(
mAnimationCallback);
@@ -150,7 +166,7 @@
@Test
public void setScaleForWindowMagnification() throws RemoteException {
mIMagnificationConnection.setScaleForWindowMagnification(TEST_DISPLAY, 3.0f);
- waitForIdleSync();
+ processAllPendingMessages();
verify(mWindowMagnificationController).setScale(3.0f);
}
@@ -158,7 +174,7 @@
@Test
public void moveWindowMagnifier() throws RemoteException {
mIMagnificationConnection.moveWindowMagnifier(TEST_DISPLAY, 100f, 200f);
- waitForIdleSync();
+ processAllPendingMessages();
verify(mWindowMagnificationController).moveWindowMagnifier(100f, 200f);
}
@@ -167,37 +183,102 @@
public void moveWindowMagnifierToPosition() throws RemoteException {
mIMagnificationConnection.moveWindowMagnifierToPosition(TEST_DISPLAY,
100f, 200f, mAnimationCallback);
- waitForIdleSync();
+ processAllPendingMessages();
verify(mWindowMagnificationController).moveWindowMagnifierToPosition(
eq(100f), eq(200f), any(IRemoteMagnificationAnimationCallback.class));
}
@Test
- public void showMagnificationButton() throws RemoteException {
+ @RequiresFlagsDisabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON)
+ public void showMagnificationButton_flagOff_directlyShowButton() throws RemoteException {
// magnification settings panel should not be showing
assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
- waitForIdleSync();
+ processAllPendingMessages();
verify(mModeSwitchesController).showButton(TEST_DISPLAY,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON)
+ public void showMagnificationButton_flagOn_delayedShowButton() throws RemoteException {
+ // magnification settings panel should not be showing
+ assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
+
+ mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ // This processAllPendingMessages lets the IMagnificationConnection to delegate the
+ // showMagnificationButton request to Magnification.
+ processAllPendingMessages();
+
+ // The delayed message would be processed after DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS.
+ // So call this processAllPendingMessages with a timeout to verify the showButton
+ // will be called.
+ int timeout = DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100;
+ processAllPendingMessages(timeout);
+ verify(mModeSwitchesController).showButton(TEST_DISPLAY,
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ }
+
+ @Test
+ public void showMagnificationButton_settingsPanelShowing_doNotShowButton()
+ throws RemoteException {
+ when(mMagnificationSettingsController.isMagnificationSettingsShowing()).thenReturn(true);
+
+ mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ // This processAllPendingMessages lets the IMagnificationConnection to delegate the
+ // showMagnificationButton request to Magnification.
+ processAllPendingMessages();
+
+ // If the flag is on, the isMagnificationSettingsShowing will be checked after timeout, so
+ // process all message after a timeout here to verify the showButton will not be called.
+ int timeout = Flags.delayShowMagnificationButton()
+ ? DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100
+ : 0;
+ processAllPendingMessages(timeout);
+ verify(mModeSwitchesController, never()).showButton(TEST_DISPLAY,
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ }
+
+ @Test
public void removeMagnificationButton() throws RemoteException {
mIMagnificationConnection.removeMagnificationButton(TEST_DISPLAY);
- waitForIdleSync();
+ processAllPendingMessages();
verify(mModeSwitchesController).removeButton(TEST_DISPLAY);
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON)
+ public void removeMagnificationButton_delayingShowButton_doNotShowButtonAfterTimeout()
+ throws RemoteException {
+ // magnification settings panel should not be showing
+ assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
+
+ mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ mIMagnificationConnection.removeMagnificationButton(TEST_DISPLAY);
+ // This processAllPendingMessages lets the IMagnificationConnection to delegate the
+ // requests to Magnification.
+ processAllPendingMessages();
+
+ // Call this processAllPendingMessages with a timeout to ensure the delayed show button
+ // message should be removed and thus the showButton will not be called after timeout.
+ int timeout = DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100;
+ processAllPendingMessages(/* timeForwardMs= */ timeout);
+ verify(mModeSwitchesController, never()).showButton(TEST_DISPLAY,
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ }
+
+ @Test
public void removeMagnificationSettingsPanel() throws RemoteException {
mIMagnificationConnection.removeMagnificationSettingsPanel(TEST_DISPLAY);
- waitForIdleSync();
+ processAllPendingMessages();
verify(mMagnificationSettingsController).closeMagnificationSettings();
}
@@ -208,7 +289,7 @@
final float testScale = 3.0f;
mIMagnificationConnection.onUserMagnificationScaleChanged(
testUserId, TEST_DISPLAY, testScale);
- waitForIdleSync();
+ processAllPendingMessages();
assertTrue(mMagnification.mUsersScales.contains(testUserId));
assertEquals(mMagnification.mUsersScales.get(testUserId).get(TEST_DISPLAY),
@@ -216,6 +297,17 @@
verify(mMagnificationSettingsController).setMagnificationScale(eq(testScale));
}
+ private void processAllPendingMessages() {
+ processAllPendingMessages(/* timeForwardMs=*/ 0);
+ }
+
+ private void processAllPendingMessages(int timeForwardMs) {
+ if (timeForwardMs > 0) {
+ mTestableLooper.moveTimeForward(timeForwardMs);
+ }
+ mTestableLooper.processAllMessages();
+ }
+
private class FakeWindowMagnificationControllerSupplier extends
DisplayIdIndexSupplier<WindowMagnificationController> {
@@ -229,7 +321,6 @@
}
}
-
private class FakeFullscreenMagnificationControllerSupplier extends
DisplayIdIndexSupplier<FullscreenMagnificationController> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index 41974f4..8e4c155 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -46,7 +46,8 @@
@RunWithLooper
class ActivityTransitionAnimatorTest : SysuiTestCase() {
private val transitionContainer = LinearLayout(mContext)
- private val testTransitionAnimator = fakeTransitionAnimator()
+ private val mainExecutor = context.mainExecutor
+ private val testTransitionAnimator = fakeTransitionAnimator(mainExecutor)
@Mock lateinit var callback: ActivityTransitionAnimator.Callback
@Mock lateinit var listener: ActivityTransitionAnimator.Listener
@Spy private val controller = TestTransitionAnimatorController(transitionContainer)
@@ -59,9 +60,10 @@
fun setup() {
activityTransitionAnimator =
ActivityTransitionAnimator(
+ mainExecutor,
testTransitionAnimator,
testTransitionAnimator,
- disableWmTimeout = true
+ disableWmTimeout = true,
)
activityTransitionAnimator.callback = callback
activityTransitionAnimator.addListener(listener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
index d84a578..e14762cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
@@ -156,6 +156,7 @@
fun testActivityLaunchWhenLockedWithoutAlternateAuth() {
val dialogTransitionAnimator =
fakeDialogTransitionAnimator(
+ mainExecutor = mContext.mainExecutor,
isUnlocked = false,
isShowingAlternateAuthOnUnlock = false,
interactionJankMonitor = kosmos.interactionJankMonitor)
@@ -166,6 +167,7 @@
@Test
fun testActivityLaunchWhenLockedWithAlternateAuth() {
val dialogTransitionAnimator = fakeDialogTransitionAnimator(
+ mainExecutor = mContext.mainExecutor,
isUnlocked = false,
isShowingAlternateAuthOnUnlock = true,
interactionJankMonitor = kosmos.interactionJankMonitor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index e64df90..259ece9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -25,6 +25,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.activity.EmptyTestActivity
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -58,9 +60,11 @@
)
}
+ private val kosmos = Kosmos()
private val pathManager = GoldenPathManager(context, GOLDENS_PATH, pathConfig = PathConfig())
private val transitionAnimator =
TransitionAnimator(
+ kosmos.fakeExecutor,
ActivityTransitionAnimator.TIMINGS,
ActivityTransitionAnimator.INTERPOLATORS
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index de3b741..7597e62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -649,7 +649,6 @@
lockPatternUtils,
interactionJankMonitor,
{ promptSelectorInteractor },
- { bpCredentialInteractor },
PromptViewModel(
displayStateInteractor,
promptSelectorInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index df0e5a7..5e4272f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -16,13 +16,8 @@
package com.android.systemui.biometrics.data.repository
-import android.hardware.biometrics.BiometricManager
-import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
-import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
import android.hardware.biometrics.PromptInfo
-import android.hardware.biometrics.PromptVerticalListContentView
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.PromptKind
@@ -139,83 +134,6 @@
}
@Test
- fun showBpWithoutIconForCredential_withVerticalListContentView() =
- testScope.runTest {
- mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP)
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- for (case in
- listOf(
- PromptKind.Biometric(),
- PromptKind.Pin,
- PromptKind.Password,
- PromptKind.Pattern
- )) {
- val hasCredentialViewShown = case !is PromptKind.Biometric
- val promptInfo =
- PromptInfo().apply {
- authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
- contentView = PromptVerticalListContentView.Builder().build()
- }
- repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME)
- repository.setShouldShowBpWithoutIconForCredential(promptInfo)
-
- assertThat(repository.showBpWithoutIconForCredential.value)
- .isEqualTo(!hasCredentialViewShown)
- }
- }
-
- @Test
- fun showBpWithoutIconForCredential_withContentViewWithMoreOptionsButton() =
- testScope.runTest {
- mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP)
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- val promptInfo =
- PromptInfo().apply {
- authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
- contentView =
- PromptContentViewWithMoreOptionsButton.Builder()
- .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> }
- .build()
- }
- for (case in
- listOf(
- PromptKind.Biometric(),
- PromptKind.Pin,
- PromptKind.Password,
- PromptKind.Pattern
- )) {
- repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME)
- repository.setShouldShowBpWithoutIconForCredential(promptInfo)
-
- assertThat(repository.showBpWithoutIconForCredential.value).isFalse()
- }
- }
-
- @Test
- fun showBpWithoutIconForCredential_withDescription() =
- testScope.runTest {
- mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP)
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- for (case in
- listOf(
- PromptKind.Biometric(),
- PromptKind.Pin,
- PromptKind.Password,
- PromptKind.Pattern
- )) {
- val promptInfo =
- PromptInfo().apply {
- authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
- description = "description"
- }
- repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME)
- repository.setShouldShowBpWithoutIconForCredential(promptInfo)
-
- assertThat(repository.showBpWithoutIconForCredential.value).isFalse()
- }
- }
-
- @Test
fun setsAndUnsetsPrompt() =
testScope.runTest {
val kind = PromptKind.Pin
@@ -223,7 +141,7 @@
repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind, OP_PACKAGE_NAME)
- assertThat(repository.kind.value).isEqualTo(kind)
+ assertThat(repository.promptKind.value).isEqualTo(kind)
assertThat(repository.userId.value).isEqualTo(USER_ID)
assertThat(repository.challenge.value).isEqualTo(CHALLENGE)
assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
index 2172bc5..8695c01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
@@ -5,12 +5,12 @@
import android.hardware.biometrics.PromptVerticalListContentView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.data.repository.FakePromptRepository
import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
import com.android.systemui.biometrics.promptInfo
import com.android.systemui.biometrics.shared.model.BiometricUserInfo
+import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -110,7 +110,7 @@
it.description = description
it.subtitle = subtitle
},
- kind = Utils.CREDENTIAL_PIN,
+ kind = PromptKind.Pin,
userId = USER_ID,
challenge = OPERATION_ID,
opPackageName = OP_PACKAGE_NAME
@@ -135,7 +135,7 @@
it.subtitle = subtitle
it.contentView = contentView
},
- kind = Utils.CREDENTIAL_PIN,
+ kind = PromptKind.Pin,
userId = USER_ID,
challenge = OPERATION_ID,
opPackageName = OP_PACKAGE_NAME
@@ -163,7 +163,7 @@
it.subtitle = subtitle
it.contentView = contentView
},
- kind = Utils.CREDENTIAL_PIN,
+ kind = PromptKind.Pin,
userId = USER_ID,
challenge = OPERATION_ID,
opPackageName = OP_PACKAGE_NAME
@@ -171,13 +171,13 @@
assertThat(showTitleOnly).isFalse()
}
- @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PIN)
+ @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(PromptKind.Pin)
- @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PASSWORD)
+ @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(PromptKind.Password)
- @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PATTERN)
+ @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(PromptKind.Pattern)
- private fun useCredentialForPrompt(kind: Int) =
+ private fun useCredentialForPrompt(kind: PromptKind) =
testScope.runTest {
val isStealth = false
credentialInteractor.stealthMode = isStealth
@@ -211,11 +211,10 @@
assertThat(prompt)
.isInstanceOf(
when (kind) {
- Utils.CREDENTIAL_PIN -> BiometricPromptRequest.Credential.Pin::class.java
- Utils.CREDENTIAL_PASSWORD ->
+ PromptKind.Pin -> BiometricPromptRequest.Credential.Pin::class.java
+ PromptKind.Password ->
BiometricPromptRequest.Credential.Password::class.java
- Utils.CREDENTIAL_PATTERN ->
- BiometricPromptRequest.Credential.Pattern::class.java
+ PromptKind.Pattern -> BiometricPromptRequest.Credential.Pattern::class.java
else -> throw Exception("wrong kind")
}
)
@@ -341,6 +340,28 @@
job.cancel()
}
+
+ /** Update the current request to use credential-based authentication instead of biometrics. */
+ private fun PromptCredentialInteractor.useCredentialsForAuthentication(
+ promptInfo: PromptInfo,
+ kind: PromptKind,
+ userId: Int,
+ challenge: Long,
+ opPackageName: String,
+ ) {
+ biometricPromptRepository.setPrompt(
+ promptInfo,
+ userId,
+ challenge,
+ kind,
+ opPackageName,
+ )
+ }
+
+ /** Unset the current authentication request. */
+ private fun PromptCredentialInteractor.resetPrompt() {
+ biometricPromptRepository.unsetPrompt()
+ }
}
private fun pinRequest(): BiometricPromptRequest.Credential.Pin =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index 2817780..e0324df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -18,11 +18,12 @@
import android.app.admin.DevicePolicyManager
import android.hardware.biometrics.BiometricManager.Authenticators
+import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
import android.hardware.biometrics.PromptInfo
+import android.hardware.biometrics.PromptVerticalListContentView
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FakePromptRepository
import com.android.systemui.biometrics.faceSensorPropertiesInternal
@@ -30,8 +31,10 @@
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -65,6 +68,7 @@
private val testScope = TestScope()
private val fingerprintRepository = FakeFingerprintPropertyRepository()
private val promptRepository = FakePromptRepository()
+ private val fakeExecutor = FakeExecutor(FakeSystemClock())
private lateinit var interactor: PromptSelectorInteractor
@@ -74,6 +78,23 @@
PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
}
+ private fun basicPromptInfo() =
+ PromptInfo().apply {
+ title = TITLE
+ subtitle = SUBTITLE
+ description = DESCRIPTION
+ negativeButtonText = NEGATIVE_TEXT
+ isConfirmationRequested = true
+ isDeviceCredentialAllowed = true
+ authenticators = Authenticators.BIOMETRIC_STRONG or Authenticators.DEVICE_CREDENTIAL
+ }
+
+ private val modalities =
+ BiometricModalities(
+ fingerprintProperties = fingerprintSensorPropertiesInternal().first(),
+ faceProperties = faceSensorPropertiesInternal().first(),
+ )
+
@Test
fun useBiometricsAndReset() =
testScope.runTest { useBiometricsAndReset(allowCredentialFallback = true) }
@@ -87,11 +108,7 @@
val confirmationRequired = true
val info =
- PromptInfo().apply {
- title = TITLE
- subtitle = SUBTITLE
- description = DESCRIPTION
- negativeButtonText = NEGATIVE_TEXT
+ basicPromptInfo().apply {
isConfirmationRequested = confirmationRequired
authenticators =
if (allowCredentialFallback) {
@@ -101,25 +118,22 @@
}
isDeviceCredentialAllowed = allowCredentialFallback
}
- val modalities =
- BiometricModalities(
- fingerprintProperties = fingerprintSensorPropertiesInternal().first(),
- faceProperties = faceSensorPropertiesInternal().first(),
- )
val currentPrompt by collectLastValue(interactor.prompt)
- val credentialKind by collectLastValue(interactor.credentialKind)
+ val promptKind by collectLastValue(interactor.promptKind)
val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed)
- val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequired)
+ val credentialKind by collectLastValue(interactor.credentialKind)
+ val isConfirmationRequired by collectLastValue(interactor.isConfirmationRequired)
assertThat(currentPrompt).isNull()
- interactor.useBiometricsForAuthentication(
+ interactor.setPrompt(
info,
USER_ID,
- CHALLENGE,
modalities,
- OP_PACKAGE_NAME
+ CHALLENGE,
+ OP_PACKAGE_NAME,
+ false /*onSwitchToCredential*/
)
assertThat(currentPrompt).isNotNull()
@@ -128,36 +142,173 @@
assertThat(currentPrompt?.subtitle).isEqualTo(SUBTITLE)
assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT)
assertThat(currentPrompt?.opPackageName).isEqualTo(OP_PACKAGE_NAME)
+ assertThat(promptKind!!.isBiometric()).isTrue()
if (allowCredentialFallback) {
assertThat(credentialKind).isSameInstanceAs(PromptKind.Password)
assertThat(isCredentialAllowed).isTrue()
} else {
- assertThat(credentialKind).isEqualTo(PromptKind.Biometric())
+ assertThat(credentialKind).isEqualTo(PromptKind.None)
assertThat(isCredentialAllowed).isFalse()
}
- assertThat(isExplicitConfirmationRequired).isEqualTo(confirmationRequired)
+ assertThat(isConfirmationRequired).isEqualTo(confirmationRequired)
interactor.resetPrompt()
verifyUnset()
}
@Test
- fun usePinCredentialAndReset() =
- testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PIN) }
+ fun usePinCredentialAndReset() = testScope.runTest { useCredentialAndReset(PromptKind.Pin) }
@Test
fun usePatternCredentialAndReset() =
- testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PATTERN) }
+ testScope.runTest { useCredentialAndReset(PromptKind.Pattern) }
@Test
fun usePasswordCredentialAndReset() =
- testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PASSWORD) }
+ testScope.runTest { useCredentialAndReset(PromptKind.Password) }
- private fun TestScope.useCredentialAndReset(@Utils.CredentialType kind: Int) {
+ @Test
+ fun promptKind_isBiometric_whenBiometricAllowed() =
+ testScope.runTest {
+ setUserCredentialType(isPassword = true)
+ val info = basicPromptInfo()
+
+ val promptKind by collectLastValue(interactor.promptKind)
+ assertThat(promptKind).isEqualTo(PromptKind.None)
+
+ interactor.setPrompt(
+ info,
+ USER_ID,
+ modalities,
+ CHALLENGE,
+ OP_PACKAGE_NAME,
+ false /*onSwitchToCredential*/
+ )
+
+ assertThat(promptKind?.isBiometric()).isTrue()
+
+ interactor.resetPrompt()
+ verifyUnset()
+ }
+
+ @Test
+ fun promptKind_isCredential_onSwitchToCredential() =
+ testScope.runTest {
+ setUserCredentialType(isPassword = true)
+ val info = basicPromptInfo()
+
+ val promptKind by collectLastValue(interactor.promptKind)
+ assertThat(promptKind).isEqualTo(PromptKind.None)
+
+ interactor.setPrompt(
+ info,
+ USER_ID,
+ modalities,
+ CHALLENGE,
+ OP_PACKAGE_NAME,
+ true /*onSwitchToCredential*/
+ )
+
+ assertThat(promptKind).isEqualTo(PromptKind.Password)
+
+ interactor.resetPrompt()
+ verifyUnset()
+ }
+
+ @Test
+ fun promptKind_isCredential_whenBiometricIsNotAllowed() =
+ testScope.runTest {
+ setUserCredentialType(isPassword = true)
+ val info =
+ basicPromptInfo().apply {
+ isDeviceCredentialAllowed = true
+ authenticators = Authenticators.DEVICE_CREDENTIAL
+ }
+
+ val promptKind by collectLastValue(interactor.promptKind)
+ assertThat(promptKind).isEqualTo(PromptKind.None)
+
+ interactor.setPrompt(
+ info,
+ USER_ID,
+ modalities,
+ CHALLENGE,
+ OP_PACKAGE_NAME,
+ false /*onSwitchToCredential*/
+ )
+
+ assertThat(promptKind).isEqualTo(PromptKind.Password)
+
+ interactor.resetPrompt()
+ verifyUnset()
+ }
+
+ @Test
+ fun promptKind_isCredential_whenBiometricIsNotAllowed_withMoreOptionsButton() =
+ testScope.runTest {
+ setUserCredentialType(isPassword = true)
+ val info =
+ basicPromptInfo().apply {
+ isDeviceCredentialAllowed = true
+ authenticators = Authenticators.DEVICE_CREDENTIAL
+ contentView =
+ PromptContentViewWithMoreOptionsButton.Builder()
+ .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> }
+ .build()
+ }
+
+ val promptKind by collectLastValue(interactor.promptKind)
+ assertThat(promptKind).isEqualTo(PromptKind.None)
+
+ interactor.setPrompt(
+ info,
+ USER_ID,
+ modalities,
+ CHALLENGE,
+ OP_PACKAGE_NAME,
+ false /*onSwitchToCredential*/
+ )
+
+ assertThat(promptKind).isEqualTo(PromptKind.Password)
+
+ interactor.resetPrompt()
+ verifyUnset()
+ }
+
+ @Test
+ fun promptKind_isBiometric_whenBiometricIsNotAllowed_withVerticalList() =
+ testScope.runTest {
+ setUserCredentialType(isPassword = true)
+ val info =
+ basicPromptInfo().apply {
+ isDeviceCredentialAllowed = true
+ authenticators = Authenticators.DEVICE_CREDENTIAL
+ contentView = PromptVerticalListContentView.Builder().build()
+ }
+
+ val promptKind by collectLastValue(interactor.promptKind)
+ assertThat(promptKind).isEqualTo(PromptKind.None)
+
+ interactor.setPrompt(
+ info,
+ USER_ID,
+ modalities,
+ CHALLENGE,
+ OP_PACKAGE_NAME,
+ false /*onSwitchToCredential*/
+ )
+
+ assertThat(promptKind?.isBiometric()).isTrue()
+
+ interactor.resetPrompt()
+ verifyUnset()
+ }
+
+ private fun TestScope.useCredentialAndReset(kind: PromptKind) {
setUserCredentialType(
- isPin = kind == Utils.CREDENTIAL_PIN,
- isPassword = kind == Utils.CREDENTIAL_PASSWORD,
+ isPin = kind == PromptKind.Pin,
+ isPassword = kind == PromptKind.Password,
)
val info =
@@ -175,11 +326,18 @@
assertThat(currentPrompt).isNull()
- interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE, OP_PACKAGE_NAME)
+ interactor.setPrompt(
+ info,
+ USER_ID,
+ BiometricModalities(),
+ CHALLENGE,
+ OP_PACKAGE_NAME,
+ false /*onSwitchToCredential*/
+ )
// not using biometrics, should be null with no fallback option
assertThat(currentPrompt).isNull()
- assertThat(credentialKind).isEqualTo(PromptKind.Biometric())
+ assertThat(credentialKind).isEqualTo(PromptKind.None)
interactor.resetPrompt()
verifyUnset()
@@ -187,13 +345,16 @@
private fun TestScope.verifyUnset() {
val currentPrompt by collectLastValue(interactor.prompt)
+ val promptKind by collectLastValue(interactor.promptKind)
+ val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed)
val credentialKind by collectLastValue(interactor.credentialKind)
+ val isConfirmationRequired by collectLastValue(interactor.isConfirmationRequired)
assertThat(currentPrompt).isNull()
-
- val kind = credentialKind as? PromptKind.Biometric
- assertThat(kind).isNotNull()
- assertThat(kind?.activeModalities?.isEmpty).isTrue()
+ assertThat(promptKind).isEqualTo(PromptKind.None)
+ assertThat(isCredentialAllowed).isFalse()
+ assertThat(credentialKind).isEqualTo(PromptKind.None)
+ assertThat(isConfirmationRequired).isFalse()
}
private fun setUserCredentialType(isPin: Boolean = false, isPassword: Boolean = false) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
index 5caa146..0d01472 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
@@ -16,97 +16,95 @@
package com.android.systemui.biometrics.ui.viewmodel
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.flags.parameterizeSceneContainerFlag
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.user.domain.UserDomainLayerModule
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.statusbar.phone.systemUIDialogManager
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
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.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
-class DefaultUdfpsTouchOverlayViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class DefaultUdfpsTouchOverlayViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }
+ }
+ private val testScope = kosmos.testScope
+
@Captor
private lateinit var sysuiDialogListenerCaptor: ArgumentCaptor<SystemUIDialogManager.Listener>
- private var systemUIDialogManager: SystemUIDialogManager = mock()
+ private var systemUIDialogManager = kosmos.systemUIDialogManager
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+ private lateinit var underTest: DefaultUdfpsTouchOverlayViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return parameterizeSceneContainerFlag()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ underTest =
+ DefaultUdfpsTouchOverlayViewModel(
+ kosmos.shadeInteractor,
+ systemUIDialogManager,
+ )
}
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- UserDomainLayerModule::class,
- BiometricsDomainLayerModule::class,
- ]
- )
- interface TestComponent : SysUITestComponent<DefaultUdfpsTouchOverlayViewModel> {
- val keyguardRepository: FakeKeyguardRepository
- val shadeRepository: FakeShadeRepository
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- featureFlags: FakeFeatureFlagsClassicModule,
- mocks: TestMocksModule,
- ): TestComponent
- }
- }
-
- private fun TestComponent.shadeExpanded(expanded: Boolean) {
+ private fun shadeExpanded(expanded: Boolean) {
if (expanded) {
- shadeRepository.setLegacyShadeExpansion(1f)
- shadeRepository.setLegacyShadeTracking(false)
- shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(true)
+ shadeTestUtil.setShadeExpansion(1f)
+ shadeTestUtil.setTracking(false)
+ shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(true)
} else {
keyguardRepository.setStatusBarState(StatusBarState.SHADE)
- shadeRepository.setLegacyShadeExpansion(0f)
- shadeRepository.setLegacyShadeTracking(false)
- shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(false)
+ shadeTestUtil.setShadeExpansion(0f)
+ shadeTestUtil.setTracking(false)
+ shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(false)
}
}
- private val testComponent: TestComponent =
- DaggerDefaultUdfpsTouchOverlayViewModelTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
- mocks = TestMocksModule(systemUIDialogManager = systemUIDialogManager),
- )
-
@Test
+ @BrokenWithSceneContainer(339465026)
fun shadeNotExpanded_noDialogShowing_shouldHandleTouchesTrue() =
- testComponent.runTest {
+ testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
runCurrent()
@@ -120,7 +118,7 @@
@Test
fun shadeNotExpanded_dialogShowing_shouldHandleTouchesFalse() =
- testComponent.runTest {
+ testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
runCurrent()
@@ -134,7 +132,7 @@
@Test
fun shadeExpanded_noDialogShowing_shouldHandleTouchesFalse() =
- testComponent.runTest {
+ testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index a732418..aae7ff6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -169,8 +169,11 @@
)
biometricStatusRepository = FakeBiometricStatusRepository()
biometricStatusInteractor =
- BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository,
- fingerprintRepository)
+ BiometricStatusInteractorImpl(
+ activityTaskManager,
+ biometricStatusRepository,
+ fingerprintRepository
+ )
selector =
PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
selector.resetPrompt()
@@ -183,7 +186,7 @@
promptContentViewWithMoreOptionsButton =
PromptContentViewWithMoreOptionsButton.Builder()
.setDescription("test")
- .setMoreOptionsButtonListener(fakeExecutor, { _, _ -> })
+ .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> }
.build()
viewModel =
@@ -1630,12 +1633,13 @@
isConfirmationRequested = requireConfirmation
}
- useBiometricsForAuthentication(
+ setPrompt(
info,
USER_ID,
- CHALLENGE,
BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
+ CHALLENGE,
packageName,
+ false /*onUseDeviceCredential*/
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index 28cbcb4..4bcd9a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -17,7 +17,7 @@
package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothDevice
-import android.content.pm.PackageInfo
+import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.media.AudioManager
import android.platform.test.annotations.DisableFlags
@@ -25,7 +25,6 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
-import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.flags.Flags
import com.android.systemui.SysuiTestCase
@@ -120,11 +119,10 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testSavedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
- val exclusiveManagerName =
- BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
`when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(exclusiveManagerName.toByteArray())
- `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo())
+ .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+ `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+ .thenReturn(ApplicationInfo())
`when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(cachedDevice.isConnected).thenReturn(false)
@@ -144,11 +142,11 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testSavedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() {
+ fun testSavedFactory_isFilterMatched_exclusiveManagerNotEnabled_returnsTrue() {
`when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray())
- `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0))
- .thenReturn(PackageInfo())
+ .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+ `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+ .thenReturn(ApplicationInfo().also { it.enabled = false })
`when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(cachedDevice.isConnected).thenReturn(false)
@@ -158,12 +156,10 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testSavedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() {
- val exclusiveManagerName =
- BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+ fun testSavedFactory_isFilterMatched_exclusiveManagerNotInstalled_returnsTrue() {
`when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(exclusiveManagerName.toByteArray())
- `when`(packageManager.getPackageInfo(exclusiveManagerName, 0))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+ `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
.thenThrow(PackageManager.NameNotFoundException("Test!"))
`when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(cachedDevice.isConnected).thenReturn(false)
@@ -228,11 +224,10 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testConnectedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
- val exclusiveManagerName =
- BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
`when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(exclusiveManagerName.toByteArray())
- `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo())
+ .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+ `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+ .thenReturn(ApplicationInfo())
`when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(bluetoothDevice.isConnected).thenReturn(true)
audioManager.setMode(AudioManager.MODE_NORMAL)
@@ -254,11 +249,11 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testConnectedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() {
+ fun testConnectedFactory_isFilterMatched_exclusiveManagerNotEnabled_returnsTrue() {
`when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray())
- `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0))
- .thenReturn(PackageInfo())
+ .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+ `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+ .thenReturn(ApplicationInfo().also { it.enabled = false })
`when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(bluetoothDevice.isConnected).thenReturn(true)
audioManager.setMode(AudioManager.MODE_NORMAL)
@@ -269,12 +264,10 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testConnectedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() {
- val exclusiveManagerName =
- BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+ fun testConnectedFactory_isFilterMatched_exclusiveManagerNotInstalled_returnsTrue() {
`when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(exclusiveManagerName.toByteArray())
- `when`(packageManager.getPackageInfo(exclusiveManagerName, 0))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+ `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
.thenThrow(PackageManager.NameNotFoundException("Test!"))
`when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(bluetoothDevice.isConnected).thenReturn(true)
@@ -317,7 +310,7 @@
companion object {
const val DEVICE_NAME = "DeviceName"
const val CONNECTION_SUMMARY = "ConnectionSummary"
- private const val FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name"
+ private const val TEST_EXCLUSIVE_MANAGER = "com.test.manager"
private const val DEVICE_ADDRESS = "04:52:C7:0B:D8:3C"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
index bed05ee..cde7a0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
@@ -30,7 +30,6 @@
import java.nio.charset.Charset
import org.junit.After
import org.junit.Before
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -102,12 +101,6 @@
assertThat(dataRead).isEqualTo(newDataToWrite)
}
- @Ignore("Ignored until we figure out why it is flaky b/336561027")
- @Test(expected = FileNotFoundException::class)
- fun read_fileNotFoundException() {
- underTest.readBytesFromDisk()
- }
-
@Test(expected = FileNotFoundException::class)
fun clear_returnsTrueWhenFileDeleted() {
// Write bytes to disk
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
index 6a0462b..c39c3fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.deviceentry.domain.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository
@@ -24,7 +24,9 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.data.ui.viewmodel.deviceEntryUdfpsAccessibilityOverlayViewModel
+import com.android.systemui.deviceentry.ui.viewmodel.DeviceEntryUdfpsAccessibilityOverlayViewModel
import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -34,19 +36,22 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition
import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@ExperimentalCoroutinesApi
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class UdfpsAccessibilityOverlayViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) }
@@ -59,8 +64,27 @@
private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
private val deviceEntryFingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
- private val shadeRepository = kosmos.fakeShadeRepository
- private val underTest = kosmos.deviceEntryUdfpsAccessibilityOverlayViewModel
+
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+ private lateinit var underTest: DeviceEntryUdfpsAccessibilityOverlayViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ @Before
+ fun setup() {
+ underTest = kosmos.deviceEntryUdfpsAccessibilityOverlayViewModel
+ }
@Test
fun visible() =
@@ -142,7 +166,7 @@
)
// Shade not expanded
- shadeRepository.qsExpansion.value = 0f
- shadeRepository.lockscreenShadeExpansion.value = 0f
+ shadeTestUtil.setQsExpansion(0f)
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 1dc58d1..c3a806b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -17,6 +17,8 @@
package com.android.systemui.keyguard.domain.interactor
import android.app.StatusBarManager
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.keyguard.KeyguardSecurityModel
@@ -29,7 +31,9 @@
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -46,7 +50,8 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.commandQueue
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
@@ -61,13 +66,14 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
/**
* Class for testing user journeys through the interactors. They will all be activated during setup,
@@ -75,8 +81,8 @@
*/
@ExperimentalCoroutinesApi
@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardTransitionScenariosTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
@@ -87,7 +93,7 @@
private val keyguardRepository = kosmos.fakeKeyguardRepository
private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
private var commandQueue = kosmos.fakeCommandQueue
- private val shadeRepository = kosmos.fakeShadeRepository
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
private lateinit var featureFlags: FakeFeatureFlags
@@ -112,6 +118,18 @@
private val communalInteractor = kosmos.communalInteractor
private val dockManager = kosmos.fakeDockManager
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -119,9 +137,11 @@
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
- mSetFlagsRule.disableFlags(
- Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
- )
+ if (!SceneContainerFlag.isEnabled) {
+ mSetFlagsRule.disableFlags(
+ Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ )
+ }
featureFlags = FakeFeatureFlags()
fromLockscreenTransitionInteractor.start()
@@ -210,6 +230,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun lockscreenToDreaming() =
testScope.runTest {
// GIVEN a device that is not dreaming or dozing
@@ -238,6 +259,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun lockscreenToDreamingLockscreenHosted() =
testScope.runTest {
// GIVEN a device that is not dreaming or dozing
@@ -527,6 +549,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun dozingToGoneWithUnlock() =
testScope.runTest {
// GIVEN a prior transition has run to DOZING
@@ -706,6 +729,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun goneToLockscreen() =
testScope.runTest {
// GIVEN a prior transition has run to GONE
@@ -755,6 +779,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun goneToGlanceableHub() =
testScope.runTest {
// GIVEN a prior transition has run to GONE
@@ -897,6 +922,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun alternateBouncerToGone() =
testScope.runTest {
// GIVEN a prior transition has run to ALTERNATE_BOUNCER
@@ -1135,6 +1161,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun occludedToGone() =
testScope.runTest {
// GIVEN a device on lockscreen
@@ -1165,6 +1192,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun occludedToLockscreen() =
testScope.runTest {
// GIVEN a device on lockscreen
@@ -1193,6 +1221,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun occludedToGlanceableHub() =
testScope.runTest {
// GIVEN a device on lockscreen
@@ -1229,6 +1258,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun occludedToGlanceableHubWhenInitiallyOnHub() =
testScope.runTest {
// GIVEN a device on lockscreen and communal is available
@@ -1314,6 +1344,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun primaryBouncerToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -1339,6 +1370,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun dozingToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to DOZING
@@ -1364,6 +1396,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun dreamingToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to DREAMING
@@ -1392,6 +1425,39 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
+ @EnableFlags(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE)
+ fun dreamingToOccludedToDreaming() =
+ testScope.runTest {
+ // GIVEN a device on lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // Given a device that is dreaming
+ keyguardRepository.setDreaming(true)
+
+ // GIVEN a prior transition has run to OCCLUDED
+ runTransitionAndSetWakefulness(KeyguardState.DREAMING, KeyguardState.OCCLUDED)
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ // WHEN occlusion ends
+ keyguardRepository.setKeyguardOccluded(false)
+ runCurrent()
+
+ // THEN a transition to GLANCEABLE_HUB should occur
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromOccludedTransitionInteractor::class.simpleName,
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.DREAMING,
+ animatorAssertion = { it.isNotNull() },
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun dreamingToPrimaryBouncer() =
testScope.runTest {
// GIVEN a prior transition has run to DREAMING
@@ -1484,6 +1550,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun lockscreenToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to LOCKSCREEN
@@ -1507,6 +1574,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun aodToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to AOD
@@ -1553,6 +1621,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun lockscreenToOccluded_fromCameraGesture() =
testScope.runTest {
// GIVEN a prior transition has run to LOCKSCREEN
@@ -1586,6 +1655,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun lockscreenToPrimaryBouncerDragging() =
testScope.runTest {
// GIVEN a prior transition has run to LOCKSCREEN
@@ -1595,8 +1665,8 @@
// GIVEN the keyguard is showing locked
keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
runCurrent()
- shadeRepository.setLegacyShadeTracking(true)
- shadeRepository.setLegacyShadeExpansion(.9f)
+ shadeTestUtil.setTracking(true)
+ shadeTestUtil.setShadeExpansion(.9f)
runCurrent()
// THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
@@ -1613,8 +1683,8 @@
// WHEN the user stops dragging and shade is back to expanded
clearInvocations(transitionRepository)
runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
- shadeRepository.setLegacyShadeTracking(false)
- shadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setTracking(false)
+ shadeTestUtil.setShadeExpansion(1f)
runCurrent()
// THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
@@ -1803,6 +1873,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun glanceableHubToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to GLANCEABLE_HUB
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
index 0bca367..f61ddeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
@@ -19,6 +19,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.coroutines.collectLastValue
@@ -150,6 +151,51 @@
assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.NONE)
}
+ fun accessibilityDelegateHint_accessibilityNotEnabled() =
+ testScope.runTest {
+ val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint)
+ kosmos.fakeAccessibilityRepository.isEnabled.value = false
+ assertThat(accessibilityDelegateHint)
+ .isEqualTo(DeviceEntryIconView.AccessibilityHintType.NONE)
+ }
+
+ @Test
+ fun accessibilityDelegateHint_accessibilityEnabled_locked() =
+ testScope.runTest {
+ val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint)
+ kosmos.fakeAccessibilityRepository.isEnabled.value = true
+
+ // interactive lock icon
+ keyguardRepository.setKeyguardDismissible(false)
+ fingerprintPropertyRepository.supportsUdfps()
+
+ assertThat(accessibilityDelegateHint)
+ .isEqualTo(DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE)
+
+ // non-interactive lock icon
+ keyguardRepository.setKeyguardDismissible(false)
+ fingerprintPropertyRepository.supportsRearFps()
+
+ assertThat(accessibilityDelegateHint)
+ .isEqualTo(DeviceEntryIconView.AccessibilityHintType.NONE)
+ }
+
+ @Test
+ fun accessibilityDelegateHint_accessibilityEnabled_unlocked() =
+ testScope.runTest {
+ val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint)
+ kosmos.fakeAccessibilityRepository.isEnabled.value = true
+
+ // interactive unlock icon
+ keyguardRepository.setKeyguardDismissible(true)
+ fingerprintPropertyRepository.supportsUdfps()
+ advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
+ runCurrent()
+
+ assertThat(accessibilityDelegateHint)
+ .isEqualTo(DeviceEntryIconView.AccessibilityHintType.ENTER)
+ }
+
private fun deviceEntryIconTransitionAlpha(alpha: Float) {
deviceEntryIconTransition.setDeviceEntryParentViewAlpha(alpha)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 1881a9e..16421a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -88,7 +88,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var expandable: Expandable
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
@@ -115,7 +115,7 @@
private val kosmos = testKosmos()
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 0c98cff..768d446 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -53,7 +53,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardClockViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
val underTest = kosmos.keyguardClockViewModel
@@ -67,7 +67,7 @@
var faceConfig = ClockFaceConfig()
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index f57f040..68307b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -16,10 +16,12 @@
package com.android.systemui.qs.external;
import static android.os.PowerExemptionManager.REASON_TILE_ONCLICK;
+import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf;
import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -55,13 +57,15 @@
import android.os.HandlerThread;
import android.os.IDeviceIdleController;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.service.quicksettings.IQSService;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.TileService;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -73,12 +77,24 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
import org.mockito.MockitoSession;
+import java.util.List;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
public class TileLifecycleManagerTest extends SysuiTestCase {
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX);
+ }
+
private final PackageManagerAdapter mMockPackageManagerAdapter =
mock(PackageManagerAdapter.class);
private final BroadcastDispatcher mMockBroadcastDispatcher =
@@ -98,6 +114,11 @@
private TestContextWrapper mWrappedContext;
private MockitoSession mMockitoSession;
+ public TileLifecycleManagerTest(FlagsParameterization flags) {
+ super();
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
setPackageEnabled(true);
@@ -263,7 +284,8 @@
}
@Test
- public void testNoClickOfNotListeningAnymore() throws Exception {
+ @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void testNoClickIfNotListeningAnymore() throws Exception {
mStateManager.onTileAdded();
mStateManager.onStartListening();
mStateManager.onClick(null);
@@ -279,6 +301,42 @@
}
@Test
+ @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void testNoClickIfNotListeningBeforeClick() throws Exception {
+ mStateManager.onTileAdded();
+ mStateManager.onStartListening();
+ mStateManager.onStopListening();
+ mStateManager.onClick(null);
+ mStateManager.executeSetBindService(true);
+ mExecutor.runAllReady();
+
+ verifyBind(1);
+ mStateManager.executeSetBindService(false);
+ mExecutor.runAllReady();
+ assertFalse(mContext.isBound(mTileServiceComponentName));
+ verify(mMockTileService, never()).onClick(null);
+ }
+
+ @Test
+ @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void testClickIfStopListeningBeforeProcessedClick() throws Exception {
+ mStateManager.onTileAdded();
+ mStateManager.onStartListening();
+ mStateManager.onClick(null);
+ mStateManager.onStopListening();
+ mStateManager.executeSetBindService(true);
+ mExecutor.runAllReady();
+
+ verifyBind(1);
+ mStateManager.executeSetBindService(false);
+ mExecutor.runAllReady();
+ assertFalse(mContext.isBound(mTileServiceComponentName));
+ InOrder inOrder = Mockito.inOrder(mMockTileService);
+ inOrder.verify(mMockTileService).onClick(null);
+ inOrder.verify(mMockTileService).onStopListening();
+ }
+
+ @Test
public void testComponentEnabling() throws Exception {
mStateManager.onTileAdded();
mStateManager.onStartListening();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 0ff29db..1c86638 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -15,12 +15,18 @@
*/
package com.android.systemui.qs.external;
+import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf;
+
+import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX;
+import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -32,16 +38,19 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.After;
import org.junit.Before;
@@ -51,10 +60,20 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
public class TileServiceManagerTest extends SysuiTestCase {
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX);
+ }
+
@Mock
private TileServices mTileServices;
@Mock
@@ -68,17 +87,22 @@
@Mock
private CustomTileAddedRepository mCustomTileAddedRepository;
- private HandlerThread mThread;
- private Handler mHandler;
+ private FakeExecutor mFakeExecutor;
+
private TileServiceManager mTileServiceManager;
private ComponentName mComponentName;
+ public TileServiceManagerTest(FlagsParameterization flags) {
+ super();
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mThread = new HandlerThread("TestThread");
- mThread.start();
- mHandler = Handler.createAsync(mThread.getLooper());
+ mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+ Handler handler = mockExecutorHandler(mFakeExecutor);
+
when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
@@ -90,13 +114,12 @@
mComponentName = new ComponentName(mContext, TileServiceManagerTest.class);
when(mTileLifecycle.getComponent()).thenReturn(mComponentName);
- mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker,
+ mTileServiceManager = new TileServiceManager(mTileServices, handler, mUserTracker,
mCustomTileAddedRepository, mTileLifecycle);
}
@After
public void tearDown() throws Exception {
- mThread.quit();
mTileServiceManager.handleDestroy();
}
@@ -201,4 +224,59 @@
verify(mTileLifecycle, times(2)).executeSetBindService(captor.capture());
assertFalse((boolean) captor.getValue());
}
+
+ @Test
+ @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void testStopListeningAndUnbindImmediatelyAfterUpdate() {
+ when(mTileLifecycle.isActiveTile()).thenReturn(true);
+ mTileServiceManager.startLifecycleManagerAndAddTile();
+ mTileServiceManager.setBindAllowed(true);
+ clearInvocations(mTileLifecycle);
+
+ mTileServiceManager.setBindRequested(true);
+ verify(mTileLifecycle).executeSetBindService(true);
+
+ mTileServiceManager.setLastUpdate(0);
+ mFakeExecutor.advanceClockToLast();
+ mFakeExecutor.runAllReady();
+ verify(mTileLifecycle).onStopListening();
+ verify(mTileLifecycle).executeSetBindService(false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void testStopListeningAndUnbindImmediatelyAfterUpdate_ifRequestedFromTileService() {
+ when(mTileLifecycle.isActiveTile()).thenReturn(true);
+ mTileServiceManager.startLifecycleManagerAndAddTile();
+ mTileServiceManager.setBindAllowed(true);
+ clearInvocations(mTileLifecycle);
+
+ mTileServiceManager.setBindRequested(true);
+ mTileServiceManager.onStartListeningFromRequest();
+ verify(mTileLifecycle).onStartListening();
+
+ mTileServiceManager.setLastUpdate(0);
+ mFakeExecutor.advanceClockToLast();
+ mFakeExecutor.runAllReady();
+ verify(mTileLifecycle).onStopListening();
+ verify(mTileLifecycle).executeSetBindService(false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void testNotUnbindImmediatelyAfterUpdate_ifRequestedFromSystemUI() {
+ when(mTileLifecycle.isActiveTile()).thenReturn(true);
+ mTileServiceManager.startLifecycleManagerAndAddTile();
+ mTileServiceManager.setBindAllowed(true);
+ clearInvocations(mTileLifecycle);
+
+ mTileServiceManager.setBindRequested(true);
+ // The tile requests startListening (because a click happened)
+
+ mTileServiceManager.setLastUpdate(0);
+ mFakeExecutor.advanceClockToLast();
+ mFakeExecutor.runAllReady();
+ verify(mTileLifecycle, never()).onStopListening();
+ verify(mTileLifecycle, never()).executeSetBindService(false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index b62d59d..bcff88a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -15,6 +15,10 @@
*/
package com.android.systemui.qs.external;
+import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf;
+
+import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
@@ -33,8 +37,10 @@
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.service.quicksettings.IQSTileService;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -64,13 +70,23 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.List;
import javax.inject.Provider;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@RunWithLooper
public class TileServicesTest extends SysuiTestCase {
+
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX);
+ }
+
private static int NUM_FAKES = TileServices.DEFAULT_MAX_BOUND * 2;
private static final ComponentName TEST_COMPONENT =
@@ -106,6 +122,11 @@
@Mock
private CustomTileAddedRepository mCustomTileAddedRepository;
+ public TileServicesTest(FlagsParameterization flags) {
+ super();
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -194,6 +215,7 @@
}
@Test
+ @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
public void testRequestListeningStatusCommand() throws RemoteException {
ArgumentCaptor<CommandQueue.Callbacks> captor =
ArgumentCaptor.forClass(CommandQueue.Callbacks.class);
@@ -213,6 +235,26 @@
}
@Test
+ @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void testRequestListeningStatusCommand_onStartListeningFromRequest() {
+ ArgumentCaptor<CommandQueue.Callbacks> captor =
+ ArgumentCaptor.forClass(CommandQueue.Callbacks.class);
+ verify(mCommandQueue).addCallback(captor.capture());
+
+ CustomTile mockTile = mock(CustomTile.class);
+ when(mockTile.getComponent()).thenReturn(TEST_COMPONENT);
+
+ TileServiceManager manager = mTileService.getTileWrapper(mockTile);
+ when(manager.isActiveTile()).thenReturn(true);
+ when(manager.getTileService()).thenReturn(mock(IQSTileService.class));
+
+ captor.getValue().requestTileServiceListeningState(TEST_COMPONENT);
+ mTestableLooper.processAllMessages();
+ verify(manager).setBindRequested(true);
+ verify(manager).onStartListeningFromRequest();
+ }
+
+ @Test
public void testValidCustomTileStartsActivity() {
CustomTile tile = mock(CustomTile.class);
PendingIntent pi = mock(PendingIntent.class);
@@ -263,6 +305,7 @@
}
@Test
+ @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
public void tileFreedForCorrectUser() throws RemoteException {
verify(mCommandQueue).addCallback(mCallbacksArgumentCaptor.capture());
@@ -297,6 +340,42 @@
verify(manager1.getTileService()).onStartListening();
}
+ @Test
+ @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void tileFreedForCorrectUser_onStartListeningFromRequest() throws RemoteException {
+ verify(mCommandQueue).addCallback(mCallbacksArgumentCaptor.capture());
+
+ ComponentName componentName = new ComponentName("pkg", "cls");
+ CustomTile tileUser0 = mock(CustomTile.class);
+ CustomTile tileUser1 = mock(CustomTile.class);
+
+ when(tileUser0.getComponent()).thenReturn(componentName);
+ when(tileUser1.getComponent()).thenReturn(componentName);
+ when(tileUser0.getUser()).thenReturn(0);
+ when(tileUser1.getUser()).thenReturn(1);
+
+ // Create a tile for user 0
+ TileServiceManager manager0 = mTileService.getTileWrapper(tileUser0);
+ when(manager0.isActiveTile()).thenReturn(true);
+ // Then create a tile for user 1
+ TileServiceManager manager1 = mTileService.getTileWrapper(tileUser1);
+ when(manager1.isActiveTile()).thenReturn(true);
+
+ // When the tile for user 0 gets freed
+ mTileService.freeService(tileUser0, manager0);
+ // and the user is 1
+ when(mUserTracker.getUserId()).thenReturn(1);
+
+ // a call to requestListeningState
+ mCallbacksArgumentCaptor.getValue().requestTileServiceListeningState(componentName);
+ mTestableLooper.processAllMessages();
+
+ // will call in the correct tile
+ verify(manager1).setBindRequested(true);
+ // and set it to listening
+ verify(manager1).onStartListeningFromRequest();
+ }
+
private class TestTileServices extends TileServices {
TestTileServices(QSHost host, Provider<Handler> handlerProvider,
BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
new file mode 100644
index 0000000..db752dd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
+import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
+import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class GridConsistencyInteractorTest : SysuiTestCase() {
+
+ data object TestGridLayoutType : GridLayoutType
+
+ private val gridLayout: MutableStateFlow<GridLayoutType> =
+ MutableStateFlow(InfiniteGridLayoutType)
+
+ private val iconOnlyTiles =
+ MutableStateFlow(
+ setOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("smallC"),
+ TileSpec.create("smallD"),
+ TileSpec.create("smallE"),
+ )
+ )
+
+ private val kosmos =
+ testKosmos().apply {
+ iconTilesRepository =
+ object : IconTilesRepository {
+ override val iconTilesSpecs: StateFlow<Set<TileSpec>>
+ get() = iconOnlyTiles.asStateFlow()
+ }
+ gridConsistencyInteractorsMap =
+ mapOf(
+ Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor),
+ Pair(TestGridLayoutType, noopGridConsistencyInteractor)
+ )
+ gridLayoutTypeRepository =
+ object : GridLayoutTypeRepository {
+ override val layout: StateFlow<GridLayoutType> = gridLayout.asStateFlow()
+ }
+ }
+
+ private val underTest = with(kosmos) { gridConsistencyInteractor }
+
+ @Before
+ fun setUp() {
+ gridLayout.value = InfiniteGridLayoutType
+ underTest.start()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun changeLayoutType_usesCorrectGridConsistencyInteractor() =
+ with(kosmos) {
+ testScope.runTest {
+ // Using the no-op grid consistency interactor
+ gridLayout.value = TestGridLayoutType
+
+ // Setting an invalid layout with holes
+ // [ Large A ] [ sa ]
+ // [ Large B ] [ Large C ]
+ // [ sb ] [ Large D ]
+ val newTiles =
+ listOf(
+ TileSpec.create("largeA"),
+ TileSpec.create("smallA"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("smallB"),
+ TileSpec.create("largeD"),
+ )
+ tileSpecRepository.setTiles(0, newTiles)
+
+ runCurrent()
+
+ val tiles = currentTilesInteractor.currentTiles.value
+ val tileSpecs = tiles.map { it.spec }
+
+ // Saved tiles should be unchanged
+ assertThat(tileSpecs).isEqualTo(newTiles)
+ }
+ }
+
+ @Test
+ fun validTilesWithInfiniteGridConsistencyInteractor_unchangedList() =
+ with(kosmos) {
+ testScope.runTest {
+ // Setting a valid layout with holes
+ // [ Large A ] [ sa ][ sb ]
+ // [ Large B ] [ Large C ]
+ // [ Large D ]
+ val newTiles =
+ listOf(
+ TileSpec.create("largeA"),
+ TileSpec.create("smallA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("largeD"),
+ )
+ tileSpecRepository.setTiles(0, newTiles)
+
+ runCurrent()
+
+ val tiles = currentTilesInteractor.currentTiles.value
+ val tileSpecs = tiles.map { it.spec }
+
+ // Saved tiles should be unchanged
+ assertThat(tileSpecs).isEqualTo(newTiles)
+ }
+ }
+
+ @Test
+ fun invalidTilesWithInfiniteGridConsistencyInteractor_savesNewList() =
+ with(kosmos) {
+ testScope.runTest {
+ // Setting an invalid layout with holes
+ // [ sa ] [ Large A ]
+ // [ Large B ] [ sb ] [ sc ]
+ // [ sd ] [ se ] [ Large C ]
+ val newTiles =
+ listOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("largeA"),
+ TileSpec.create("largeB"),
+ TileSpec.create("smallB"),
+ TileSpec.create("smallC"),
+ TileSpec.create("smallD"),
+ TileSpec.create("smallE"),
+ TileSpec.create("largeC"),
+ )
+ tileSpecRepository.setTiles(0, newTiles)
+
+ runCurrent()
+
+ val tiles = currentTilesInteractor.currentTiles.value
+ val tileSpecs = tiles.map { it.spec }
+
+ // Expected grid
+ // [ sa ] [ Large A ] [ sb ]
+ // [ Large B ] [ sc ] [ sd ]
+ // [ se ] [ Large C ]
+ val expectedTiles =
+ listOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("largeA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("largeB"),
+ TileSpec.create("smallC"),
+ TileSpec.create("smallD"),
+ TileSpec.create("smallE"),
+ TileSpec.create("largeC"),
+ )
+
+ // Saved tiles should be unchanged
+ assertThat(tileSpecs).isEqualTo(expectedTiles)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
new file mode 100644
index 0000000..bda48ad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class InfiniteGridConsistencyInteractorTest : SysuiTestCase() {
+
+ private val iconOnlyTiles =
+ MutableStateFlow(
+ setOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("smallC"),
+ TileSpec.create("smallD"),
+ TileSpec.create("smallE"),
+ )
+ )
+ private val kosmos =
+ testKosmos().apply {
+ iconTilesRepository =
+ object : IconTilesRepository {
+ override val iconTilesSpecs: StateFlow<Set<TileSpec>>
+ get() = iconOnlyTiles.asStateFlow()
+ }
+ }
+ private val underTest = with(kosmos) { infiniteGridConsistencyInteractor }
+
+ @Test
+ fun validTiles_returnsUnchangedList() =
+ with(kosmos) {
+ testScope.runTest {
+ // Original grid
+ // [ Large A ] [ sa ][ sb ]
+ // [ Large B ] [ Large C ]
+ // [ Large D ]
+ val tiles =
+ listOf(
+ TileSpec.create("largeA"),
+ TileSpec.create("smallA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("largeD"),
+ )
+
+ val newTiles = underTest.reconcileTiles(tiles)
+
+ assertThat(newTiles).isEqualTo(tiles)
+ }
+ }
+
+ @Test
+ fun invalidTiles_moveIconTileForward() =
+ with(kosmos) {
+ testScope.runTest {
+ // Original grid
+ // [ Large A ] [ sa ]
+ // [ Large B ] [ Large C ]
+ // [ sb ] [ Large D ]
+ val tiles =
+ listOf(
+ TileSpec.create("largeA"),
+ TileSpec.create("smallA"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("smallB"),
+ TileSpec.create("largeD"),
+ )
+ // Expected grid
+ // [ Large A ] [ sa ][ sb ]
+ // [ Large B ] [ Large C ]
+ // [ Large D ]
+ val expectedTiles =
+ listOf(
+ TileSpec.create("largeA"),
+ TileSpec.create("smallA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("largeD"),
+ )
+
+ val newTiles = underTest.reconcileTiles(tiles)
+
+ assertThat(newTiles).isEqualTo(expectedTiles)
+ }
+ }
+
+ @Test
+ fun invalidTiles_moveIconTileBack() =
+ with(kosmos) {
+ testScope.runTest {
+ // Original grid
+ // [ sa ] [ Large A ]
+ // [ Large B ] [ Large C ]
+ // [ Large D ]
+ val tiles =
+ listOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("largeA"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("largeD"),
+ )
+ // Expected grid
+ // [ Large A ] [ Large B ]
+ // [ Large C ] [ Large D ]
+ // [ sa ]
+ val expectedTiles =
+ listOf(
+ TileSpec.create("largeA"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("largeD"),
+ TileSpec.create("smallA"),
+ )
+
+ val newTiles = underTest.reconcileTiles(tiles)
+
+ assertThat(newTiles).isEqualTo(expectedTiles)
+ }
+ }
+
+ @Test
+ fun invalidTiles_multipleCorrections() =
+ with(kosmos) {
+ testScope.runTest {
+ // Original grid
+ // [ sa ] [ Large A ]
+ // [ Large B ] [ sb ] [ sc ]
+ // [ sd ] [ se ] [ Large C ]
+ val tiles =
+ listOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("largeA"),
+ TileSpec.create("largeB"),
+ TileSpec.create("smallB"),
+ TileSpec.create("smallC"),
+ TileSpec.create("smallD"),
+ TileSpec.create("smallE"),
+ TileSpec.create("largeC"),
+ )
+ // Expected grid
+ // [ sa ] [ Large A ] [ sb ]
+ // [ Large B ] [ sc ] [ sd ]
+ // [ se ] [ Large C ]
+ val expectedTiles =
+ listOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("largeA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("largeB"),
+ TileSpec.create("smallC"),
+ TileSpec.create("smallD"),
+ TileSpec.create("smallE"),
+ TileSpec.create("largeC"),
+ )
+
+ val newTiles = underTest.reconcileTiles(tiles)
+
+ assertThat(newTiles).isEqualTo(expectedTiles)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index 2536a93..9798562 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -5,6 +5,7 @@
import static android.telephony.SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
import static android.telephony.SignalStrength.SIGNAL_STRENGTH_GREAT;
import static android.telephony.SignalStrength.SIGNAL_STRENGTH_POOR;
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource;
@@ -217,6 +218,8 @@
when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(SUB_ID);
+ SubscriptionInfo info = mock(SubscriptionInfo.class);
+ when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info);
when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt()))
.thenReturn(mSystemUIToast);
when(mSystemUIToast.getView()).thenReturn(mToastView);
@@ -1083,19 +1086,34 @@
}
@Test
- public void hasActiveSubId_activeSubIdListIsEmpty_returnFalse() {
- when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{});
+ public void hasActiveSubIdOnDds_noDds_returnFalse() {
+ when(SubscriptionManager.getDefaultDataSubscriptionId())
+ .thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
- assertThat(mInternetDialogController.hasActiveSubId()).isFalse();
+ assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isFalse();
}
@Test
- public void hasActiveSubId_activeSubIdListNotEmpty_returnTrue() {
- when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+ public void hasActiveSubIdOnDds_activeDds_returnTrue() {
mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
- assertThat(mInternetDialogController.hasActiveSubId()).isTrue();
+ assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isTrue();
+ }
+
+ @Test
+ public void hasActiveSubIdOnDds_activeDdsAndHasProvisioning_returnFalse() {
+ when(SubscriptionManager.getDefaultDataSubscriptionId())
+ .thenReturn(SUB_ID);
+ SubscriptionInfo info = mock(SubscriptionInfo.class);
+ when(info.isEmbedded()).thenReturn(true);
+ when(info.getProfileClass()).thenReturn(PROFILE_CLASS_PROVISIONING);
+ when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info);
+
+ mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+ assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isFalse();
}
private String getResourcesString(String name) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
index 6f88891..aefcc87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
@@ -251,7 +251,7 @@
// Mobile network should be gone if the list of active subscriptionId is null.
when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
- when(mInternetDialogController.hasActiveSubId()).thenReturn(false);
+ when(mInternetDialogController.hasActiveSubIdOnDds()).thenReturn(false);
mInternetDialogDelegate.updateDialog(true);
@@ -336,7 +336,7 @@
@Test
public void updateDialog_mobileDataIsEnabled_checkMobileDataSwitch() {
- doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
when(mInternetDialogController.isMobileDataEnabled()).thenReturn(true);
mMobileToggleSwitch.setChecked(false);
@@ -348,7 +348,7 @@
@Test
public void updateDialog_mobileDataIsNotChanged_checkMobileDataSwitch() {
- doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
when(mInternetDialogController.isMobileDataEnabled()).thenReturn(false);
mMobileToggleSwitch.setChecked(false);
@@ -361,7 +361,7 @@
@Test
public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
mInternetDialogDelegate.dismissDialog();
- doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
createInternetDialog();
// The preconditions WiFi ON and Internet WiFi are already in setUp()
doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
@@ -522,7 +522,7 @@
public void updateDialog_showSecondaryDataSub() {
mInternetDialogDelegate.dismissDialog();
doReturn(1).when(mInternetDialogController).getActiveAutoSwitchNonDdsSubId();
- doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
doReturn(false).when(mInternetDialogController).isAirplaneModeEnabled();
createInternetDialog();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 3793970..5b47c94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -446,6 +446,7 @@
mUiEventLogger,
() -> mKosmos.getInteractionJankMonitor(),
mJavaAdapter,
+ () -> mKeyguardTransitionInteractor,
() -> mShadeInteractor,
() -> mKosmos.getDeviceUnlockedInteractor(),
() -> mKosmos.getSceneInteractor(),
@@ -600,6 +601,7 @@
new UiEventLoggerFake(),
() -> mKosmos.getInteractionJankMonitor(),
mJavaAdapter,
+ () -> mKeyguardTransitionInteractor,
() -> mShadeInteractor,
() -> mKosmos.getDeviceUnlockedInteractor(),
() -> mKosmos.getSceneInteractor(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index a867b0f..45d0102 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -102,7 +102,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@RunWithLooper(setAsMainLooper = true)
-class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var view: NotificationShadeWindowView
@Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
@@ -160,7 +160,7 @@
private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 347620a..83ad18b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -55,7 +55,7 @@
@RunWith(ParameterizedAndroidJunit4::class)
@SmallTest
@EnableFlags(FooterViewRefactor.FLAG_NAME)
-class FooterViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
@@ -79,7 +79,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt
new file mode 100644
index 0000000..5e50af3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.app.Flags.FLAG_COMPACT_HEADS_UP_NOTIFICATION
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class HeadsUpStyleProviderImplTest : SysuiTestCase() {
+
+ @Rule @JvmField val setFlagsRule = SetFlagsRule()
+
+ private lateinit var statusBarModeRepositoryStore: FakeStatusBarModeRepository
+ private lateinit var headsUpStyleProvider: HeadsUpStyleProviderImpl
+
+ @Before
+ fun setUp() {
+ statusBarModeRepositoryStore = FakeStatusBarModeRepository()
+ statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = true
+
+ headsUpStyleProvider = HeadsUpStyleProviderImpl(statusBarModeRepositoryStore)
+ }
+
+ @Test
+ @DisableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION)
+ fun shouldApplyCompactStyle_returnsFalse_whenCompactFlagDisabled() {
+ assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION)
+ fun shouldApplyCompactStyle_returnsTrue_whenImmersiveModeEnabled() {
+ // GIVEN
+ statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = true
+
+ // THEN
+ assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION)
+ fun shouldApplyCompactStyle_returnsFalse_whenImmersiveModeDisabled() {
+ // GIVEN
+ statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = false
+
+ // THEN
+ assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
index 4f0f91a..926c35f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
@@ -134,7 +134,8 @@
/* isHeadsUpAnimation= */ eq(true),
/* onStartedRunnable= */ any(),
/* onFinishedRunnable= */ runnableCaptor.capture(),
- /* animationListener= */ any()
+ /* animationListener= */ any(),
+ /* clipSide= */ eq(ExpandableView.ClipSide.BOTTOM),
)
animatorTestRule.advanceTimeBy(disappearDuration) // move to the end of SSA animations
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 9b4f931..cb40f72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -66,7 +66,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@TestableLooper.RunWithLooper
-class KeyguardBypassControllerTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardBypassControllerTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val featureFlags = FakeFeatureFlags()
@@ -92,7 +92,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Captor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index b5525b1..36df61d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -295,6 +295,50 @@
}
@Test
+ fun subscriptions_subIsOnlyNtn_modelHasExclusivelyNtnTrue() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.subscriptions)
+
+ val onlyNtnSub =
+ mock<SubscriptionInfo>().also {
+ whenever(it.isOnlyNonTerrestrialNetwork).thenReturn(true)
+ whenever(it.subscriptionId).thenReturn(45)
+ whenever(it.groupUuid).thenReturn(GROUP_1)
+ whenever(it.carrierName).thenReturn("NTN only")
+ whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
+ }
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(onlyNtnSub))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isExclusivelyNonTerrestrial).isTrue()
+ }
+
+ @Test
+ fun subscriptions_subIsNotOnlyNtn_modelHasExclusivelyNtnFalse() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.subscriptions)
+
+ val notOnlyNtnSub =
+ mock<SubscriptionInfo>().also {
+ whenever(it.isOnlyNonTerrestrialNetwork).thenReturn(false)
+ whenever(it.subscriptionId).thenReturn(45)
+ whenever(it.groupUuid).thenReturn(GROUP_1)
+ whenever(it.carrierName).thenReturn("NTN only")
+ whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
+ }
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(notOnlyNtnSub))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isExclusivelyNonTerrestrial).isFalse()
+ }
+
+ @Test
fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() =
testScope.runTest {
val latest by collectLastValue(underTest.subscriptions)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 0b14be1..0f9cbfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -42,14 +42,11 @@
import com.google.common.truth.Truth.assertThat
import java.util.UUID
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.yield
-import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
@@ -68,7 +65,7 @@
set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
}
- private val testDispatcher = UnconfinedTestDispatcher()
+ private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
private val tableLogBuffer =
@@ -113,17 +110,12 @@
)
}
- @After fun tearDown() {}
-
@Test
fun filteredSubscriptions_default() =
testScope.runTest {
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
assertThat(latest).isEqualTo(listOf<SubscriptionModel>())
-
- job.cancel()
}
// Based on the logic from the old pipeline, we'll never filter subs when there are more than 2
@@ -133,12 +125,9 @@
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
assertThat(latest).isEqualTo(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
-
- job.cancel()
}
@Test
@@ -146,12 +135,9 @@
testScope.runTest {
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
-
- job.cancel()
}
@Test
@@ -160,12 +146,9 @@
connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
assertThat(latest).isEqualTo(listOf(SUB_3_OPP, SUB_4_OPP))
-
- job.cancel()
}
@Test
@@ -180,12 +163,9 @@
connectionsRepository.setSubscriptions(listOf(sub1, sub2))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
assertThat(latest).isEqualTo(listOf(sub1, sub2))
-
- job.cancel()
}
@Test
@@ -202,13 +182,10 @@
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
// Filtered subscriptions should show the active one when the config is false
assertThat(latest).isEqualTo(listOf(sub3))
-
- job.cancel()
}
@Test
@@ -225,13 +202,10 @@
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
// Filtered subscriptions should show the active one when the config is false
assertThat(latest).isEqualTo(listOf(sub4))
-
- job.cancel()
}
@Test
@@ -248,14 +222,11 @@
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
// Filtered subscriptions should show the primary (non-opportunistic) if the config is
// true
assertThat(latest).isEqualTo(listOf(sub1))
-
- job.cancel()
}
@Test
@@ -272,14 +243,11 @@
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
// Filtered subscriptions should show the primary (non-opportunistic) if the config is
// true
assertThat(latest).isEqualTo(listOf(sub1))
-
- job.cancel()
}
@Test
@@ -297,12 +265,9 @@
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
assertThat(latest).isEqualTo(listOf(sub3))
-
- job.cancel()
}
@Test
@@ -320,12 +285,9 @@
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
assertThat(latest).isEqualTo(listOf(sub1))
-
- job.cancel()
}
@Test
@@ -446,313 +408,345 @@
}
@Test
+ fun filteredSubscriptions_subNotExclusivelyNonTerrestrial_hasSub() =
+ testScope.runTest {
+ val notExclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = false,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ connectionsRepository.setSubscriptions(listOf(notExclusivelyNonTerrestrialSub))
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf(notExclusivelyNonTerrestrialSub))
+ }
+
+ @Test
+ fun filteredSubscriptions_subExclusivelyNonTerrestrial_doesNotHaveSub() =
+ testScope.runTest {
+ val exclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = true,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ connectionsRepository.setSubscriptions(listOf(exclusivelyNonTerrestrialSub))
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ fun filteredSubscription_mixOfExclusivelyNonTerrestrialAndOther_hasOtherSubsOnly() =
+ testScope.runTest {
+ val exclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = true,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ val otherSub1 =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = false,
+ subscriptionId = 1,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ val otherSub2 =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = false,
+ subscriptionId = 2,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ connectionsRepository.setSubscriptions(
+ listOf(otherSub1, exclusivelyNonTerrestrialSub, otherSub2)
+ )
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf(otherSub1, otherSub2))
+ }
+
+ @Test
+ fun filteredSubscriptions_exclusivelyNonTerrestrialSub_andOpportunistic_bothFiltersHappen() =
+ testScope.runTest {
+ // Exclusively non-terrestrial sub
+ val exclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = true,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ // Opportunistic subs
+ val (sub3, sub4) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+
+ // WHEN both an exclusively non-terrestrial sub and opportunistic sub pair is included
+ connectionsRepository.setSubscriptions(listOf(sub3, sub4, exclusivelyNonTerrestrialSub))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ // THEN both the only-non-terrestrial sub and the non-active sub are filtered out,
+ // leaving only sub3.
+ assertThat(latest).isEqualTo(listOf(sub3))
+ }
+
+ @Test
fun activeDataConnection_turnedOn() =
testScope.runTest {
CONNECTION_1.setDataEnabled(true)
- var latest: Boolean? = null
- val job =
- underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+ val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun activeDataConnection_turnedOff() =
testScope.runTest {
CONNECTION_1.setDataEnabled(true)
- var latest: Boolean? = null
- val job =
- underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
CONNECTION_1.setDataEnabled(false)
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun activeDataConnection_invalidSubId() =
testScope.runTest {
- var latest: Boolean? = null
- val job =
- underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID)
- yield()
// An invalid active subId should tell us that data is off
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun failedConnection_default_validated_notFailed() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun failedConnection_notDefault_notValidated_notFailed() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = false
connectionsRepository.defaultConnectionIsValidated.value = false
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun failedConnection_default_notValidated_failed() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = false
- yield()
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun failedConnection_carrierMergedDefault_notValidated_failed() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.hasCarrierMergedConnection.value = true
connectionsRepository.defaultConnectionIsValidated.value = false
- yield()
assertThat(latest).isTrue()
-
- job.cancel()
}
/** Regression test for b/275076959. */
@Test
fun failedConnection_dataSwitchInSameGroup_notFailed() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
+ runCurrent()
// WHEN there's a data change in the same subscription group
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
connectionsRepository.defaultConnectionIsValidated.value = false
+ runCurrent()
// THEN the default connection is *not* marked as failed because of forced validation
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun failedConnection_dataSwitchNotInSameGroup_isFailed() =
testScope.runTest {
- var latestConnectionFailed: Boolean? = null
- val job =
- underTest.isDefaultConnectionFailed
- .onEach { latestConnectionFailed = it }
- .launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
+ runCurrent()
// WHEN the connection is invalidated without a activeSubChangedInGroupEvent
connectionsRepository.defaultConnectionIsValidated.value = false
// THEN the connection is immediately marked as failed
- assertThat(latestConnectionFailed).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun alwaysShowDataRatIcon_configHasTrue() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
val config = MobileMappings.Config()
config.alwaysShowDataRatIcon = true
connectionsRepository.defaultDataSubRatConfig.value = config
- yield()
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun alwaysShowDataRatIcon_configHasFalse() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
val config = MobileMappings.Config()
config.alwaysShowDataRatIcon = false
connectionsRepository.defaultDataSubRatConfig.value = config
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun alwaysUseCdmaLevel_configHasTrue() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
val config = MobileMappings.Config()
config.alwaysShowCdmaRssi = true
connectionsRepository.defaultDataSubRatConfig.value = config
- yield()
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun alwaysUseCdmaLevel_configHasFalse() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
val config = MobileMappings.Config()
config.alwaysShowCdmaRssi = false
connectionsRepository.defaultDataSubRatConfig.value = config
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun isSingleCarrier_zeroSubscriptions_false() =
testScope.runTest {
- var latest: Boolean? = true
- val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isSingleCarrier)
connectionsRepository.setSubscriptions(emptyList())
- assertThat(latest).isFalse()
- job.cancel()
+ assertThat(latest).isFalse()
}
@Test
fun isSingleCarrier_oneSubscription_true() =
testScope.runTest {
- var latest: Boolean? = false
- val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isSingleCarrier)
connectionsRepository.setSubscriptions(listOf(SUB_1))
- assertThat(latest).isTrue()
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun isSingleCarrier_twoSubscriptions_false() =
testScope.runTest {
- var latest: Boolean? = true
- val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isSingleCarrier)
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
- assertThat(latest).isFalse()
- job.cancel()
+ assertThat(latest).isFalse()
}
@Test
fun isSingleCarrier_updates() =
testScope.runTest {
- var latest: Boolean? = false
- val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isSingleCarrier)
connectionsRepository.setSubscriptions(listOf(SUB_1))
assertThat(latest).isTrue()
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.mobileIsDefault)
connectionsRepository.mobileIsDefault.value = false
connectionsRepository.hasCarrierMergedConnection.value = false
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.mobileIsDefault)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.hasCarrierMergedConnection.value = false
assertThat(latest).isTrue()
-
- job.cancel()
}
/** Regression test for b/272586234. */
@Test
fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.mobileIsDefault)
connectionsRepository.mobileIsDefault.value = false
connectionsRepository.hasCarrierMergedConnection.value = true
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun mobileIsDefault_updatesWhenRepoUpdates() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.mobileIsDefault)
connectionsRepository.mobileIsDefault.value = true
assertThat(latest).isTrue()
@@ -762,8 +756,6 @@
connectionsRepository.hasCarrierMergedConnection.value = true
assertThat(latest).isTrue()
-
- job.cancel()
}
// The data switch tests are mostly testing the [forcingCellularValidation] flow, but that flow
@@ -772,95 +764,79 @@
@Test
fun dataSwitch_inSameGroup_validatedMatchesPreviousValue_expiresAfter2s() =
testScope.runTest {
- var latestConnectionFailed: Boolean? = null
- val job =
- underTest.isDefaultConnectionFailed
- .onEach { latestConnectionFailed = it }
- .launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
+ runCurrent()
// Trigger a data change in the same subscription group that's not yet validated
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
connectionsRepository.defaultConnectionIsValidated.value = false
+ runCurrent()
// After 1s, the force validation bit is still present, so the connection is not marked
// as failed
advanceTimeBy(1000)
- assertThat(latestConnectionFailed).isFalse()
+ assertThat(latest).isFalse()
// After 2s, the force validation expires so the connection updates to failed
advanceTimeBy(1001)
- assertThat(latestConnectionFailed).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun dataSwitch_inSameGroup_notValidated_immediatelyMarkedAsFailed() =
testScope.runTest {
- var latestConnectionFailed: Boolean? = null
- val job =
- underTest.isDefaultConnectionFailed
- .onEach { latestConnectionFailed = it }
- .launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = false
+ runCurrent()
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
- assertThat(latestConnectionFailed).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun dataSwitch_loseValidation_thenSwitchHappens_clearsForcedBit() =
testScope.runTest {
- var latestConnectionFailed: Boolean? = null
- val job =
- underTest.isDefaultConnectionFailed
- .onEach { latestConnectionFailed = it }
- .launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
// GIVEN the network starts validated
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
+ runCurrent()
// WHEN a data change happens in the same group
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
// WHEN the validation bit is lost
connectionsRepository.defaultConnectionIsValidated.value = false
+ runCurrent()
// WHEN another data change happens in the same group
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
// THEN the forced validation bit is still used...
- assertThat(latestConnectionFailed).isFalse()
+ assertThat(latest).isFalse()
advanceTimeBy(1000)
- assertThat(latestConnectionFailed).isFalse()
+ assertThat(latest).isFalse()
// ... but expires after 2s
advanceTimeBy(1001)
- assertThat(latestConnectionFailed).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun dataSwitch_whileAlreadyForcingValidation_resetsClock() =
testScope.runTest {
- var latestConnectionFailed: Boolean? = null
- val job =
- underTest.isDefaultConnectionFailed
- .onEach { latestConnectionFailed = it }
- .launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
+ runCurrent()
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
@@ -869,44 +845,37 @@
// WHEN another change in same group event happens
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
connectionsRepository.defaultConnectionIsValidated.value = false
+ runCurrent()
// THEN the forced validation remains for exactly 2 more seconds from now
// 1.500s from second event
advanceTimeBy(1500)
- assertThat(latestConnectionFailed).isFalse()
+ assertThat(latest).isFalse()
// 2.001s from the second event
advanceTimeBy(501)
- assertThat(latestConnectionFailed).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun isForceHidden_repoHasMobileHidden_true() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isForceHidden)
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun isForceHidden_repoDoesNotHaveMobileHidden_false() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isForceHidden)
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index cfa734a1..ab10bc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -47,7 +47,7 @@
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardStatusBarViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardStatusBarViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
@@ -66,7 +66,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt
index d1d2598..15032dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt
@@ -141,6 +141,88 @@
assertEquals(3, runnable.mRunCount)
}
+ @Test
+ fun testRemoveCallback_postDelayed() {
+ val clock = FakeSystemClock()
+ val fakeExecutor = FakeExecutor(clock)
+ val handler = mockExecutorHandler(fakeExecutor)
+ val runnable = RunnableImpl()
+
+ handler.postDelayed(runnable, 50)
+ handler.postDelayed(runnable, 150)
+ fakeExecutor.advanceClockToNext()
+ fakeExecutor.runAllReady()
+
+ assertEquals(1, runnable.mRunCount)
+ assertEquals(1, fakeExecutor.numPending())
+
+ handler.removeCallbacks(runnable)
+ assertEquals(0, fakeExecutor.numPending())
+
+ assertEquals(1, runnable.mRunCount)
+ }
+
+ @Test
+ fun testRemoveCallback_postAtTime() {
+ val clock = FakeSystemClock()
+ val fakeExecutor = FakeExecutor(clock)
+ val handler = mockExecutorHandler(fakeExecutor)
+ val runnable = RunnableImpl()
+ assertEquals(10000, clock.uptimeMillis())
+
+ handler.postAtTime(runnable, 10050)
+ handler.postAtTime(runnable, 10150)
+ fakeExecutor.advanceClockToNext()
+ fakeExecutor.runAllReady()
+
+ assertEquals(1, runnable.mRunCount)
+ assertEquals(1, fakeExecutor.numPending())
+
+ handler.removeCallbacks(runnable)
+ assertEquals(0, fakeExecutor.numPending())
+
+ assertEquals(1, runnable.mRunCount)
+ }
+
+ @Test
+ fun testRemoveCallback_mixed_allRemoved() {
+ val clock = FakeSystemClock()
+ val fakeExecutor = FakeExecutor(clock)
+ val handler = mockExecutorHandler(fakeExecutor)
+ val runnable = RunnableImpl()
+ assertEquals(10000, clock.uptimeMillis())
+
+ handler.postAtTime(runnable, 10050)
+ handler.postDelayed(runnable, 150)
+
+ handler.removeCallbacks(runnable)
+ assertEquals(0, fakeExecutor.numPending())
+
+ fakeExecutor.advanceClockToLast()
+ fakeExecutor.runAllReady()
+ assertEquals(0, runnable.mRunCount)
+ }
+
+ @Test
+ fun testRemoveCallback_differentRunnables_onlyMatchingRemoved() {
+ val clock = FakeSystemClock()
+ val fakeExecutor = FakeExecutor(clock)
+ val handler = mockExecutorHandler(fakeExecutor)
+ val runnable1 = RunnableImpl()
+ val runnable2 = RunnableImpl()
+
+ handler.postDelayed(runnable1, 50)
+ handler.postDelayed(runnable2, 150)
+
+ handler.removeCallbacks(runnable1)
+ assertEquals(1, fakeExecutor.numPending())
+
+ fakeExecutor.advanceClockToLast()
+ fakeExecutor.runAllReady()
+ assertEquals(0, runnable1.mRunCount)
+ assertEquals(1, runnable2.mRunCount)
+ }
+
/**
* Verifies that `Handler.removeMessages`, which doesn't make sense with executor backing,
* causes an error in the test (rather than failing silently like most mocks).
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 3b468aa..9864439 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -65,7 +65,6 @@
import android.widget.SeekBar;
import androidx.test.core.view.MotionEventBuilder;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.internal.jank.InteractionJankMonitor;
@@ -273,54 +272,30 @@
@Test
@DisableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
- public void testVolumeChange_noSliderHaptics_doesNotDeliverOnProgressChangedHaptics() {
- final State shellState = createShellState();
- VolumeDialogController.StreamState musicStreamState =
- shellState.states.get(AudioSystem.STREAM_MUSIC);
+ public void addSliderHaptics_withHapticsDisabled_doesNotDeliverOnProgressChangedHaptics() {
+ // GIVEN that the slider haptics flag is disabled and we try to add haptics to volume rows
+ mDialog.addSliderHapticsToRows();
- mDialog.show(SHOW_REASON_UNKNOWN);
- mTestableLooper.processMessages(1); //Only the SHOW message
- mDialog.removeDismissMessages(); // Temporarily remove the rescheduled DISMISS
+ // WHEN haptics try to be delivered to a volume stream
+ boolean canDeliverHaptics =
+ mDialog.canDeliverProgressHapticsToStream(AudioSystem.STREAM_MUSIC, true, 50);
- // Change the volume two times
- musicStreamState.level += 10;
- mDialog.onStateChangedH(shellState);
- musicStreamState.level += 10;
- mDialog.onStateChangedH(shellState);
-
- // expected: the type of the latest progress haptics for the stream should be DISABLED
- int type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC);
- assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_DISABLED, type);
-
- mDialog.dismiss(DISMISS_REASON_UNKNOWN); // Dismiss
- mTestableLooper.processAllMessages();
+ // THEN the result is that haptics are not successfully delivered
+ assertFalse(canDeliverHaptics);
}
- @Test @FlakyTest(bugId = 329099861)
+ @Test
@EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
- public void testVolumeChange_withSliderHaptics_deliversOnProgressChangedHapticsEagerly() {
- // create haptic plugins on the rows with the flag enabled
+ public void addSliderHaptics_withHapticsEnabled_canDeliverOnProgressChangedHaptics() {
+ // GIVEN that the slider haptics flag is enabled and we try to add haptics to volume rows
mDialog.addSliderHapticsToRows();
- final State shellState = createShellState();
- VolumeDialogController.StreamState musicStreamState =
- shellState.states.get(AudioSystem.STREAM_MUSIC);
- mDialog.show(SHOW_REASON_UNKNOWN);
- mTestableLooper.processMessages(1); //Only the SHOW message
- mDialog.removeDismissMessages(); // Temporarily remove the rescheduled DISMISS
+ // WHEN haptics try to be delivered to a volume stream
+ boolean canDeliverHaptics =
+ mDialog.canDeliverProgressHapticsToStream(AudioSystem.STREAM_MUSIC, true, 50);
- // Change the volume two times
- musicStreamState.level += 10;
- mDialog.onStateChangedH(shellState);
- musicStreamState.level += 10;
- mDialog.onStateChangedH(shellState);
-
- // expected: the type of the latest progress haptics for the stream should be EAGER
- int type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC);
- assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_EAGER, type);
-
- mDialog.dismiss(DISMISS_REASON_UNKNOWN); // Dismiss
- mTestableLooper.processAllMessages();
+ // THEN the result is that haptics are successfully delivered
+ assertTrue(canDeliverHaptics);
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
index 4085b1b..923b636 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
@@ -25,8 +25,9 @@
@SysUISingleton
class FakeAccessibilityRepository(
override val isTouchExplorationEnabled: MutableStateFlow<Boolean>,
+ override val isEnabled: MutableStateFlow<Boolean>,
) : AccessibilityRepository {
- @Inject constructor() : this(MutableStateFlow(false))
+ @Inject constructor() : this(MutableStateFlow(false), MutableStateFlow(false))
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
index 66c9afb..b23767e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
@@ -17,5 +17,13 @@
package com.android.systemui.animation
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
-val Kosmos.activityTransitionAnimator by Kosmos.Fixture { ActivityTransitionAnimator() }
+val Kosmos.activityTransitionAnimator by
+ Kosmos.Fixture {
+ ActivityTransitionAnimator(
+ // The main thread is checked in a bunch of places inside the different transitions
+ // animators, so we have to pass the real main executor here.
+ mainExecutor = testCase.context.mainExecutor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
index 77cb167..5a092f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
@@ -19,7 +19,13 @@
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testCase
val Kosmos.dialogTransitionAnimator by Fixture {
- fakeDialogTransitionAnimator(interactionJankMonitor = interactionJankMonitor)
+ fakeDialogTransitionAnimator(
+ // The main thread is checked in a bunch of places inside the different transitions
+ // animators, so we have to pass the real main executor here.
+ mainExecutor = testCase.context.mainExecutor,
+ interactionJankMonitor = interactionJankMonitor,
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt
index 48b72d0..1709329 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt
@@ -15,17 +15,20 @@
package com.android.systemui.animation
import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
/** A [DialogTransitionAnimator] to be used in tests. */
@JvmOverloads
fun fakeDialogTransitionAnimator(
+ @Main mainExecutor: Executor,
isUnlocked: Boolean = true,
isShowingAlternateAuthOnUnlock: Boolean = false,
isPredictiveBackQsDialogAnim: Boolean = false,
interactionJankMonitor: InteractionJankMonitor,
): DialogTransitionAnimator {
return DialogTransitionAnimator(
+ mainExecutor = mainExecutor,
callback =
FakeCallback(
isUnlocked = isUnlocked,
@@ -36,7 +39,7 @@
object : AnimationFeatureFlags {
override val isPredictiveBackQsDialogAnim = isPredictiveBackQsDialogAnim
},
- transitionAnimator = fakeTransitionAnimator(),
+ transitionAnimator = fakeTransitionAnimator(mainExecutor),
isForTesting = true,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
index bc7ec3f..d07875f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
@@ -15,10 +15,12 @@
package com.android.systemui.animation
import com.android.app.animation.Interpolators
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
/** A [TransitionAnimator] to be used in tests. */
-fun fakeTransitionAnimator(): TransitionAnimator {
- return TransitionAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
+fun fakeTransitionAnimator(@Main mainExecutor: Executor): TransitionAnimator {
+ return TransitionAnimator(mainExecutor, TEST_TIMINGS, TEST_INTERPOLATORS)
}
/**
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index 0975687..e37bdc1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -1,8 +1,6 @@
package com.android.systemui.biometrics.data.repository
-import android.hardware.biometrics.Flags
import android.hardware.biometrics.PromptInfo
-import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.shared.model.PromptKind
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -22,15 +20,12 @@
private var _challenge = MutableStateFlow<Long?>(null)
override val challenge = _challenge.asStateFlow()
- private val _kind = MutableStateFlow<PromptKind>(PromptKind.Biometric())
- override val kind = _kind.asStateFlow()
+ private val _promptKind = MutableStateFlow<PromptKind>(PromptKind.None)
+ override val promptKind = _promptKind.asStateFlow()
private val _isConfirmationRequired = MutableStateFlow(false)
override val isConfirmationRequired = _isConfirmationRequired.asStateFlow()
- private val _showBpWithoutIconForCredential = MutableStateFlow(false)
- override val showBpWithoutIconForCredential = _showBpWithoutIconForCredential.asStateFlow()
-
private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
override val opPackageName = _opPackageName.asStateFlow()
@@ -61,7 +56,7 @@
_promptInfo.value = promptInfo
_userId.value = userId
_challenge.value = gatekeeperChallenge
- _kind.value = kind
+ _promptKind.value = kind
_isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation
_opPackageName.value = opPackageName
}
@@ -70,22 +65,11 @@
_promptInfo.value = null
_userId.value = null
_challenge.value = null
- _kind.value = PromptKind.Biometric()
+ _promptKind.value = PromptKind.None
+ _opPackageName.value = null
_isConfirmationRequired.value = false
}
- override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) {
- val hasCredentialViewShown = kind.value !is PromptKind.Biometric
- val showBpForCredential =
- Flags.customBiometricPrompt() &&
- com.android.systemui.Flags.constraintBp() &&
- !Utils.isBiometricAllowed(promptInfo) &&
- Utils.isDeviceCredentialAllowed(promptInfo) &&
- promptInfo.contentView != null &&
- !promptInfo.isContentViewMoreOptionsButtonUsed
- _showBpWithoutIconForCredential.value = showBpForCredential && !hasCredentialViewShown
- }
-
fun setIsShowing(showing: Boolean) {
_isShowing.value = showing
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt
index 3a61bf6..9b3482b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt
@@ -18,8 +18,12 @@
import android.os.UserHandle
import com.android.systemui.common.shared.model.PackageChangeModel
+import com.android.systemui.common.shared.model.PackageInstallSession
import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filter
class FakePackageChangeRepository(private val systemClock: SystemClock) : PackageChangeRepository {
@@ -31,6 +35,15 @@
user == UserHandle.ALL || user == UserHandle.getUserHandleForUid(it.packageUid)
}
+ private val _packageInstallSessions = MutableStateFlow<List<PackageInstallSession>>(emptyList())
+
+ override val packageInstallSessionsForPrimaryUser: Flow<List<PackageInstallSession>> =
+ _packageInstallSessions.asStateFlow()
+
+ fun setInstallSessions(sessions: List<PackageInstallSession>) {
+ _packageInstallSessions.value = sessions
+ }
+
suspend fun notifyChange(model: PackageChangeModel) {
_packageChanged.emit(model)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 329c0f1..f7ce367 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -51,6 +51,7 @@
override fun abortRestoreWidgets() {}
private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
- _communalWidgets.value += listOf(CommunalWidgetContentModel(id, providerInfo, priority))
+ _communalWidgets.value +=
+ listOf(CommunalWidgetContentModel.Available(id, providerInfo, priority))
}
}
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 58b0ff8..67fa857 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,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntrySourceInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
@@ -49,6 +50,7 @@
keyguardViewController = { statusBarKeyguardViewManager },
deviceEntryInteractor = deviceEntryInteractor,
deviceEntrySourceInteractor = deviceEntrySourceInteractor,
+ accessibilityInteractor = accessibilityInteractor,
scope = testScope.backgroundScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 3762497..ec56327 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -33,6 +34,7 @@
uiEventLogger,
{ interactionJankMonitor },
mock(),
+ { keyguardTransitionInteractor },
{ shadeInteractor },
{ deviceUnlockedInteractor },
{ sceneInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
index 3f91122..d72630d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -32,6 +32,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.plugins.activityStarter
import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
import com.android.systemui.qs.footer.foregroundServicesRepository
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -48,7 +49,7 @@
QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake)
}
-var Kosmos.qsTileFactory by Fixture<QSFactory>()
+var Kosmos.qsTileFactory by Fixture<QSFactory> { FakeQSFactory(::tileCreator) }
val Kosmos.fgsManagerController by Fixture { FakeFgsManagerController() }
@@ -98,3 +99,7 @@
showPowerButton = true,
)
}
+
+private fun tileCreator(spec: String): QSTile {
+ return FakeQSTile(0).apply { tileSpec = spec }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt
index cff5980..744942c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt
@@ -16,11 +16,11 @@
package com.android.systemui.qs.external
-import android.os.Binder
import android.os.IBinder
+import android.os.IInterface
import android.service.quicksettings.IQSTileService
-class FakeIQSTileService : IQSTileService {
+class FakeIQSTileService : IQSTileService.Stub() {
var isTileAdded: Boolean = false
private set
@@ -31,9 +31,11 @@
get() = mutableClicks
private val mutableClicks: MutableList<IBinder?> = mutableListOf()
- private val binder = Binder()
+ override fun queryLocalInterface(descriptor: String): IInterface {
+ return this
+ }
- override fun asBinder(): IBinder = binder
+ override fun asBinder(): IBinder = this
override fun onTileAdded() {
isTileAdded = true
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt
new file mode 100644
index 0000000..a0fc76b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import android.app.activityManager
+import android.content.applicationContext
+import android.os.fakeExecutorHandler
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by
+ Kosmos.Fixture {
+ TileLifecycleManager.Factory { intent, userHandle ->
+ TileLifecycleManager(
+ fakeExecutorHandler,
+ applicationContext,
+ tileServices,
+ packageManagerAdapterFacade.packageManagerAdapter,
+ broadcastDispatcher,
+ intent,
+ userHandle,
+ activityManager,
+ mock(),
+ fakeExecutor,
+ )
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt
new file mode 100644
index 0000000..3f129da
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import android.content.applicationContext
+import android.os.fakeExecutorHandler
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository
+import com.android.systemui.qs.pipeline.domain.interactor.panelInteractor
+import com.android.systemui.settings.userTracker
+import com.android.systemui.statusbar.commandQueue
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+val Kosmos.tileServices: TileServices by
+ Kosmos.Fixture {
+ val qsHost: QSHost = mock { whenever(context).thenReturn(applicationContext) }
+ TileServices(
+ qsHost,
+ { fakeExecutorHandler },
+ broadcastDispatcher,
+ userTracker,
+ keyguardStateController,
+ commandQueue,
+ mock<StatusBarIconController>(),
+ panelInteractor,
+ tileLifecycleManagerFactory,
+ customTileAddedRepository,
+ fakeExecutor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt
index 36c2c2b..9a6730e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt
@@ -18,13 +18,9 @@
import android.content.ComponentName
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
var Kosmos.componentName: ComponentName by Kosmos.Fixture()
-/** Returns mocks */
-var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by Kosmos.Fixture { mock {} }
-
val Kosmos.iQSTileService: FakeIQSTileService by Kosmos.Fixture { FakeIQSTileService() }
val Kosmos.tileServiceManagerFacade: FakeTileServiceManagerFacade by
Kosmos.Fixture { FakeTileServiceManagerFacade(iQSTileService) }
@@ -34,4 +30,3 @@
val Kosmos.tileServicesFacade: FakeTileServicesFacade by
Kosmos.Fixture { (FakeTileServicesFacade(tileServiceManager)) }
-val Kosmos.tileServices: TileServices by Kosmos.Fixture { tileServicesFacade.tileServices }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
index f846d57..4acedaa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
@@ -18,4 +18,5 @@
import com.android.systemui.kosmos.Kosmos
-val Kosmos.gridLayoutTypeRepository by Kosmos.Fixture { GridLayoutTypeRepository() }
+var Kosmos.gridLayoutTypeRepository: GridLayoutTypeRepository by
+ Kosmos.Fixture { GridLayoutTypeRepositoryImpl() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
index 685e772..e40152a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
@@ -18,4 +18,4 @@
import com.android.systemui.kosmos.Kosmos
-val Kosmos.iconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
+var Kosmos.iconTilesRepository: IconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
diff --git a/core/java/android/app/StatusBarManager.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt
similarity index 62%
rename from core/java/android/app/StatusBarManager.aidl
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt
index 687678c..d8af3fa 100644
--- a/core/java/android/app/StatusBarManager.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,6 +14,8 @@
* limitations under the License.
*/
-package android.app;
+package com.android.systemui.qs.panels.data.repository
-parcelable StatusBarManager.DisableInfo;
\ No newline at end of file
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.infiniteGridSizeRepository by Kosmos.Fixture { InfiniteGridSizeRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt
new file mode 100644
index 0000000..edbc4c1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+
+val Kosmos.gridConsistencyInteractor by
+ Kosmos.Fixture {
+ GridConsistencyInteractor(
+ gridLayoutTypeInteractor,
+ currentTilesInteractor,
+ gridConsistencyInteractorsMap,
+ noopGridConsistencyInteractor,
+ FakeLogBuffer.Factory.create(),
+ applicationCoroutineScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
index c951642..34e99d3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
@@ -27,3 +27,6 @@
val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by
Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) }
+
+var Kosmos.gridConsistencyInteractorsMap: Map<GridLayoutType, GridTypeConsistencyInteractor> by
+ Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt
new file mode 100644
index 0000000..7f387d7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.infiniteGridConsistencyInteractor by
+ Kosmos.Fixture {
+ InfiniteGridConsistencyInteractor(iconTilesInteractor, infiniteGridSizeInteractor)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
index 1893c30..34b266a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
@@ -19,4 +19,5 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
-val Kosmos.infiniteGridLayout by Kosmos.Fixture { InfiniteGridLayout(iconTilesInteractor) }
+val Kosmos.infiniteGridLayout by
+ Kosmos.Fixture { InfiniteGridLayout(iconTilesInteractor, infiniteGridSizeInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt
new file mode 100644
index 0000000..6e11977
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.data.repository.infiniteGridSizeRepository
+
+val Kosmos.infiniteGridSizeInteractor by
+ Kosmos.Fixture { InfiniteGridSizeInteractor(infiniteGridSizeRepository) }
diff --git a/core/java/android/app/StatusBarManager.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt
similarity index 62%
copy from core/java/android/app/StatusBarManager.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt
index 687678c..e3beff7 100644
--- a/core/java/android/app/StatusBarManager.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,6 +14,8 @@
* limitations under the License.
*/
-package android.app;
+package com.android.systemui.qs.panels.domain.interactor
-parcelable StatusBarManager.DisableInfo;
\ No newline at end of file
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.noopGridConsistencyInteractor by Kosmos.Fixture { NoopGridConsistencyInteractor() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
index 5fd8762..9481fca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
@@ -20,8 +20,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap
import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor
-import com.android.systemui.qs.panels.domain.interactor.iconTilesInteractor
-import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
+import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
val Kosmos.tileGridViewModel by
@@ -30,7 +29,7 @@
gridLayoutTypeInteractor,
gridLayoutMap,
currentTilesInteractor,
- InfiniteGridLayout(iconTilesInteractor),
+ infiniteGridLayout,
applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.kt
new file mode 100644
index 0000000..d10780b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shade.shadeController
+
+val Kosmos.panelInteractor by Kosmos.Fixture { PanelInteractorImpl(shadeController) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
index 5c4b390..419e781 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
@@ -16,23 +16,63 @@
package com.android.systemui.qs.tiles.di
+import android.os.UserHandle
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.instanceIdSequenceFake
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider
import com.android.systemui.qs.tiles.viewmodel.qsTileViewModelAdaperFactory
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import javax.inject.Provider
-import org.mockito.Mockito
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
var Kosmos.newFactoryTileMap by Kosmos.Fixture { emptyMap<String, Provider<QSTileViewModel>>() }
+val Kosmos.customTileViewModelFactory: QSTileViewModelFactory.Component by
+ Kosmos.Fixture {
+ mock {
+ whenever(create(any())).thenAnswer { invocation ->
+ val tileSpec = invocation.getArgument<TileSpec>(0)
+ val config =
+ QSTileConfig(
+ tileSpec,
+ QSTileUIConfig.Empty,
+ instanceIdSequenceFake.newInstanceId(),
+ )
+ object : QSTileViewModel {
+ override val state: SharedFlow<QSTileState> =
+ MutableStateFlow(QSTileState.build({ null }, tileSpec.spec) {})
+ override val config: QSTileConfig = config
+ override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true)
+
+ override fun onUserChanged(user: UserHandle) {}
+
+ override fun forceUpdate() {}
+
+ override fun onActionPerformed(userAction: QSTileUserAction) {}
+
+ override fun destroy() {}
+ }
+ }
+ }
+ }
+
val Kosmos.newQSTileFactory by
Kosmos.Fixture {
NewQSTileFactory(
qSTileConfigProvider,
qsTileViewModelAdaperFactory,
newFactoryTileMap,
- mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)),
- mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)),
+ customTileViewModelFactory,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
index 561e254..42437d5a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
@@ -23,6 +23,7 @@
import com.android.systemui.plugins.activityStarter
import com.android.systemui.qs.external.FakeCustomTileStatePersister
import com.android.systemui.qs.external.tileServices
+import com.android.systemui.qs.external.tileServicesFacade
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.logging.QSTileLogger
@@ -38,10 +39,10 @@
import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.mockito.mock
-var Kosmos.tileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
+var Kosmos.customTileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
var Kosmos.customTileQsTileConfig: QSTileConfig by
- Kosmos.Fixture { QSTileConfigTestBuilder.build { tileSpec = this@Fixture.tileSpec } }
+ Kosmos.Fixture { QSTileConfigTestBuilder.build { tileSpec = this@Fixture.customTileSpec } }
val Kosmos.qsTileLogger: QSTileLogger by Kosmos.Fixture { mock {} }
val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by
@@ -50,7 +51,7 @@
val Kosmos.customTileInteractor: CustomTileInteractor by
Kosmos.Fixture {
CustomTileInteractor(
- tileSpec,
+ customTileSpec,
customTileDefaultsRepository,
customTileRepository,
testScope.backgroundScope,
@@ -61,7 +62,7 @@
val Kosmos.customTileRepository: FakeCustomTileRepository by
Kosmos.Fixture {
FakeCustomTileRepository(
- tileSpec,
+ customTileSpec,
customTileStatePersister,
packageManagerAdapterFacade,
testScope.testScheduler,
@@ -75,18 +76,18 @@
Kosmos.Fixture { FakeCustomTilePackageUpdatesRepository() }
val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by
- Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec.componentName) }
+ Kosmos.Fixture { FakePackageManagerAdapterFacade(customTileSpec.componentName) }
val Kosmos.customTileServiceInteractor: CustomTileServiceInteractor by
Kosmos.Fixture {
CustomTileServiceInteractor(
- tileSpec,
+ customTileSpec,
activityStarter,
{ customTileUserActionInteractor },
customTileInteractor,
userRepository,
qsTileLogger,
- tileServices,
+ tileServicesFacade.tileServices,
testScope.backgroundScope,
)
}
@@ -95,7 +96,7 @@
Kosmos.Fixture {
CustomTileUserActionInteractor(
testCase.context,
- tileSpec,
+ customTileSpec,
qsTileLogger,
mock {},
mock {},
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
index 634d121..fa8d363 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.impl.custom.data.repository
import android.content.ComponentName
+import android.content.pm.PackageInfo
import android.content.pm.ServiceInfo
import android.os.Bundle
import com.android.systemui.qs.external.PackageManagerAdapter
@@ -24,6 +25,7 @@
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import org.mockito.ArgumentMatchers.anyInt
/**
* Facade for [PackageManagerAdapter] to provide a fake-like behaviour. You can create this class
@@ -45,19 +47,33 @@
init {
whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer {
- ServiceInfo().apply {
- metaData =
- Bundle().apply {
- putBoolean(
- android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE,
- isToggleable
- )
- putBoolean(
- android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE,
- isActive
- )
- }
- }
+ createServiceInfo()
+ }
+ whenever(
+ packageManagerAdapter.getPackageInfoAsUser(
+ eq(componentName.packageName),
+ anyInt(),
+ anyInt()
+ )
+ )
+ .thenAnswer { PackageInfo().apply { packageName = componentName.packageName } }
+ whenever(packageManagerAdapter.getServiceInfo(eq(componentName), anyInt(), anyInt()))
+ .thenAnswer { createServiceInfo() }
+ }
+
+ private fun createServiceInfo(): ServiceInfo {
+ return ServiceInfo().apply {
+ metaData =
+ Bundle().apply {
+ putBoolean(
+ android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE,
+ isToggleable
+ )
+ putBoolean(
+ android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE,
+ isActive
+ )
+ }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt
new file mode 100644
index 0000000..5c21ab6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night
+
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsNightDisplayTileConfig by
+ Kosmos.Fixture { QSAccessibilityModule.provideNightDisplayTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
index 38ede44..ea02d0c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
@@ -76,6 +76,16 @@
delegate.assertFlagValid()
delegate.programmaticCollapseShade()
}
+
+ fun setQsFullscreen(qsFullscreen: Boolean) {
+ delegate.assertFlagValid()
+ delegate.setQsFullscreen(qsFullscreen)
+ }
+
+ fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean) {
+ delegate.assertFlagValid()
+ delegate.setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer)
+ }
}
/** Sets up shade state for tests for a specific value of the scene container flag. */
@@ -103,6 +113,10 @@
/** Sets the shade to half collapsed with no touch input. */
fun programmaticCollapseShade()
+
+ fun setQsFullscreen(qsFullscreen: Boolean)
+
+ fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean)
}
/** Sets up shade state for tests when the scene container flag is disabled. */
@@ -146,6 +160,14 @@
shadeRepository.setLegacyShadeExpansion(.5f)
testScope.runCurrent()
}
+
+ override fun setQsFullscreen(qsFullscreen: Boolean) {
+ shadeRepository.legacyQsFullscreen.value = true
+ }
+
+ override fun setLegacyExpandedOrAwaitingInputTransfer(expanded: Boolean) {
+ shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(expanded)
+ }
}
/** Sets up shade state for tests when the scene container flag is enabled. */
@@ -183,6 +205,16 @@
setTransitionProgress(Scenes.Shade, Scenes.Lockscreen, .5f, false)
}
+ override fun setQsFullscreen(qsFullscreen: Boolean) {
+ setQsExpansion(1f)
+ }
+
+ override fun setLegacyExpandedOrAwaitingInputTransfer(
+ legacyExpandedOrAwaitingInputTransfer: Boolean
+ ) {
+ setShadeExpansion(.1f)
+ }
+
override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) {
if (lockscreenShadeExpansion == 0f) {
setIdleScene(Scenes.Lockscreen)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryKosmos.kt
new file mode 100644
index 0000000..3bb9580
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.content.applicationContext
+import com.android.systemui.animation.dialogTransitionAnimator
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.model.sysUiState
+
+val Kosmos.systemUIDialogFactory by
+ Kosmos.Fixture {
+ SystemUIDialogFactory(
+ applicationContext,
+ systemUIDialogManager,
+ sysUiState,
+ broadcastDispatcher,
+ dialogTransitionAnimator,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt
index 184d4b5..92f9248 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt
@@ -17,6 +17,8 @@
package com.android.systemui.util.concurrency
import android.os.Handler
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.Executor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyLong
@@ -29,9 +31,14 @@
* Wrap an [Executor] in a mock [Handler] that execute when [Handler.post] is called, and throws an
* exception otherwise. This is useful when a class requires a Handler only because Handlers are
* used by ContentObserver, and no other methods are used.
+ *
+ * If the [executor] is a [DelayableExecutor], it also supports:
+ * * [Handler.postDelayed] with a Runnable parameter
+ * * [Handler.postAtTime] with a RunnableParameter
*/
fun mockExecutorHandler(executor: Executor): Handler {
val handlerMock = Mockito.mock(Handler::class.java, RuntimeExceptionAnswer())
+ val cancellations = ConcurrentHashMap<Runnable, MutableList<Cancellation>>()
doAnswer { invocation: InvocationOnMock ->
executor.execute(invocation.getArgument(0))
true
@@ -42,7 +49,19 @@
doAnswer { invocation: InvocationOnMock ->
val runnable = invocation.getArgument<Runnable>(0)
val uptimeMillis = invocation.getArgument<Long>(1)
- executor.executeAtTime(runnable, uptimeMillis)
+ val token = Any()
+ val canceller =
+ executor.executeAtTime(
+ {
+ cancellations.get(runnable)?.removeIf { it.token == token }
+ cancellations.remove(runnable, emptyList())
+ runnable.run()
+ },
+ uptimeMillis
+ )
+ cancellations
+ .getOrPut(runnable) { CopyOnWriteArrayList() }
+ .add(Cancellation(token, canceller))
true
}
.`when`(handlerMock)
@@ -50,15 +69,36 @@
doAnswer { invocation: InvocationOnMock ->
val runnable = invocation.getArgument<Runnable>(0)
val delayInMillis = invocation.getArgument<Long>(1)
- executor.executeDelayed(runnable, delayInMillis)
+ val token = Any()
+ val canceller =
+ executor.executeDelayed(
+ {
+ cancellations.get(runnable)?.removeIf { it.token == token }
+ cancellations.remove(runnable, emptyList())
+ runnable.run()
+ },
+ delayInMillis
+ )
+ cancellations
+ .getOrPut(runnable) { CopyOnWriteArrayList() }
+ .add(Cancellation(token, canceller))
true
}
.`when`(handlerMock)
.postDelayed(any(), anyLong())
+ doAnswer { invocation: InvocationOnMock ->
+ val runnable = invocation.getArgument<Runnable>(0)
+ cancellations.remove(runnable)?.forEach(Runnable::run)
+ Unit
+ }
+ .`when`(handlerMock)
+ .removeCallbacks(any())
}
return handlerMock
}
+private class Cancellation(val token: Any, canceller: Runnable) : Runnable by canceller
+
private class RuntimeExceptionAnswer : Answer<Any> {
override fun answer(invocation: InvocationOnMock): Any {
throw RuntimeException(invocation.method.name + " is not stubbed")
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index d743558..59bbff1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -19,9 +19,8 @@
import android.content.packageManager
import android.content.pm.ApplicationInfo
import android.os.Handler
-import android.testing.TestableLooper
+import android.os.looper
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.mediaOutputDialogManager
import com.android.systemui.util.mockito.any
@@ -54,7 +53,7 @@
testScope.backgroundScope,
testScope.testScheduler,
mediaControllerRepository,
- Handler(TestableLooper.get(testCase).looper),
+ Handler(looper),
)
}
@@ -62,7 +61,7 @@
Kosmos.Fixture {
MediaDeviceSessionInteractor(
testScope.testScheduler,
- Handler(TestableLooper.get(testCase).looper),
+ Handler(looper),
mediaControllerRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorKosmos.kt
new file mode 100644
index 0000000..d1d873e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.domain.interactor
+
+import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.domain.interactor.notificationsSoundPolicyInteractor
+import com.android.systemui.volume.data.repository.audioRepository
+
+val Kosmos.audioVolumeInteractor by
+ Kosmos.Fixture {
+ AudioVolumeInteractor(
+ audioRepository,
+ notificationsSoundPolicyInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
deleted file mode 100644
index 146f109..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.panel
-
-import android.content.res.mainResources
-import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.statusbar.policy.fakeConfigurationController
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.volume.panel.dagger.factory.KosmosVolumePanelComponentFactory
-import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
-import com.android.systemui.volume.panel.domain.TestComponentAvailabilityCriteria
-import com.android.systemui.volume.panel.domain.VolumePanelStartable
-import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
-import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractorImpl
-import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
-import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
-import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
-import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
-import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
-import javax.inject.Provider
-
-val Kosmos.mockVolumePanelUiComponent: VolumePanelUiComponent by Kosmos.Fixture { mock {} }
-val Kosmos.mockVolumePanelUiComponentProvider: Provider<VolumePanelUiComponent> by
- Kosmos.Fixture { Provider { mockVolumePanelUiComponent } }
-var Kosmos.componentByKey: Map<VolumePanelComponentKey, Provider<VolumePanelUiComponent>> by
- Kosmos.Fixture { emptyMap() }
-val Kosmos.componentsFactory: ComponentsFactory by
- Kosmos.Fixture { ComponentsFactory(componentByKey) }
-
-var Kosmos.componentsLayoutManager: ComponentsLayoutManager by Kosmos.Fixture()
-var Kosmos.enabledComponents: Collection<VolumePanelComponentKey> by
- Kosmos.Fixture { componentByKey.keys }
-var Kosmos.volumePanelStartables: Set<VolumePanelStartable> by
- Kosmos.Fixture { emptySet<VolumePanelStartable>() }
-val Kosmos.unavailableCriteria: Provider<ComponentAvailabilityCriteria> by
- Kosmos.Fixture { Provider { TestComponentAvailabilityCriteria(false) } }
-val Kosmos.availableCriteria: Provider<ComponentAvailabilityCriteria> by
- Kosmos.Fixture { Provider { TestComponentAvailabilityCriteria(true) } }
-var Kosmos.defaultCriteria: Provider<ComponentAvailabilityCriteria> by
- Kosmos.Fixture { availableCriteria }
-var Kosmos.criteriaByKey: Map<VolumePanelComponentKey, Provider<ComponentAvailabilityCriteria>> by
- Kosmos.Fixture { emptyMap() }
-var Kosmos.componentsInteractor: ComponentsInteractor by
- Kosmos.Fixture {
- ComponentsInteractorImpl(
- enabledComponents,
- defaultCriteria,
- testScope.backgroundScope,
- criteriaByKey,
- )
- }
-
-var Kosmos.volumePanelViewModel: VolumePanelViewModel by
- Kosmos.Fixture {
- VolumePanelViewModel(
- mainResources,
- testScope.backgroundScope,
- KosmosVolumePanelComponentFactory(this),
- fakeConfigurationController,
- broadcastDispatcher,
- )
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponentKosmos.kt
new file mode 100644
index 0000000..2ea27c7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponentKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.bottombar.ui
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.panel.component.bottombar.ui.viewmodel.bottomBarViewModel
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.availableCriteria
+
+var Kosmos.bottomBarComponent by Kosmos.Fixture { BottomBarComponent(bottomBarViewModel) }
+var Kosmos.bottomBarAvailabilityCriteria: ComponentAvailabilityCriteria by
+ Kosmos.Fixture { availableCriteria }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelKosmos.kt
new file mode 100644
index 0000000..128ede1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.bottombar.ui.viewmodel
+
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.volume.panel.ui.viewmodel.volumePanelViewModel
+
+var Kosmos.bottomBarViewModel: BottomBarViewModel by
+ Kosmos.Fixture {
+ BottomBarViewModel(
+ activityStarter,
+ volumePanelViewModel,
+ uiEventLogger,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModuleKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModuleKosmos.kt
new file mode 100644
index 0000000..0c814c5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModuleKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.panel.component.mediaoutput.ui.composable.MediaOutputComponent
+import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.mediaOutputViewModel
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.availableCriteria
+
+var Kosmos.mediaOutputComponent: MediaOutputComponent by
+ Kosmos.Fixture { MediaOutputComponent(mediaOutputViewModel) }
+var Kosmos.mediaOutputAvailabilityCriteria: ComponentAvailabilityCriteria by
+ Kosmos.Fixture { availableCriteria }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt
new file mode 100644
index 0000000..6d4576e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.domain.interactor.audioModeInteractor
+import com.android.systemui.volume.domain.interactor.audioOutputInteractor
+import com.android.systemui.volume.mediaDeviceSessionInteractor
+import com.android.systemui.volume.mediaOutputActionsInteractor
+import com.android.systemui.volume.mediaOutputInteractor
+
+var Kosmos.mediaOutputViewModel by
+ Kosmos.Fixture {
+ MediaOutputViewModel(
+ applicationContext,
+ testScope.backgroundScope,
+ mediaOutputActionsInteractor,
+ mediaDeviceSessionInteractor,
+ audioOutputInteractor,
+ audioModeInteractor,
+ mediaOutputInteractor,
+ uiEventLogger,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/VolumeModuleKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/VolumeModuleKosmos.kt
new file mode 100644
index 0000000..a17e745
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/VolumeModuleKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.panel.component.volume.ui.composable.VolumeSlidersComponent
+import com.android.systemui.volume.panel.component.volume.ui.viewmodel.audioVolumeComponentViewModel
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.availableCriteria
+
+var Kosmos.volumeSlidersComponent: VolumeSlidersComponent by
+ Kosmos.Fixture { VolumeSlidersComponent(audioVolumeComponentViewModel) }
+var Kosmos.volumeSlidersAvailabilityCriteria: ComponentAvailabilityCriteria by
+ Kosmos.Fixture { availableCriteria }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt
new file mode 100644
index 0000000..a0a39d1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.data.repository.audioRepository
+import com.android.systemui.volume.mediaOutputInteractor
+
+val Kosmos.audioSlidersInteractor by
+ Kosmos.Fixture {
+ AudioSlidersInteractor(
+ testScope.backgroundScope,
+ mediaOutputInteractor,
+ audioRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
new file mode 100644
index 0000000..b2b19de
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.domain.interactor.audioVolumeInteractor
+import kotlinx.coroutines.CoroutineScope
+
+val Kosmos.audioStreamSliderViewModelFactory by
+ Kosmos.Fixture {
+ object : AudioStreamSliderViewModel.Factory {
+
+ override fun create(
+ audioStream: AudioStreamSliderViewModel.FactoryAudioStreamWrapper,
+ coroutineScope: CoroutineScope,
+ ): AudioStreamSliderViewModel {
+ return AudioStreamSliderViewModel(
+ audioStream,
+ coroutineScope,
+ applicationContext,
+ audioVolumeInteractor,
+ uiEventLogger,
+ )
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt
new file mode 100644
index 0000000..f0cb2cd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.mediaDeviceSessionInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
+import kotlinx.coroutines.CoroutineScope
+
+val Kosmos.castVolumeSliderViewModelFactory by
+ Kosmos.Fixture {
+ object : CastVolumeSliderViewModel.Factory {
+ override fun create(
+ session: MediaDeviceSession,
+ coroutineScope: CoroutineScope
+ ): CastVolumeSliderViewModel {
+ return CastVolumeSliderViewModel(
+ session,
+ coroutineScope,
+ applicationContext,
+ mediaDeviceSessionInteractor,
+ )
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt
new file mode 100644
index 0000000..45a291e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.mediaDeviceSessionInteractor
+import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.volume.domain.interactor.audioSlidersInteractor
+import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.audioStreamSliderViewModelFactory
+import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.castVolumeSliderViewModelFactory
+
+val Kosmos.audioVolumeComponentViewModel by
+ Kosmos.Fixture {
+ AudioVolumeComponentViewModel(
+ testScope.backgroundScope,
+ mediaOutputInteractor,
+ mediaDeviceSessionInteractor,
+ audioStreamSliderViewModelFactory,
+ castVolumeSliderViewModelFactory,
+ audioSlidersInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
index 1e895b5..587a7ea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
@@ -18,16 +18,16 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
-import com.android.systemui.volume.panel.componentsFactory
-import com.android.systemui.volume.panel.componentsInteractor
-import com.android.systemui.volume.panel.componentsLayoutManager
import com.android.systemui.volume.panel.dagger.VolumePanelComponent
import com.android.systemui.volume.panel.domain.VolumePanelStartable
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
+import com.android.systemui.volume.panel.domain.interactor.componentsInteractor
import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
+import com.android.systemui.volume.panel.ui.composable.componentsFactory
import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.layout.componentsLayoutManager
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
-import com.android.systemui.volume.panel.volumePanelStartables
+import com.android.systemui.volume.panel.ui.viewmodel.volumePanelStartables
import kotlinx.coroutines.CoroutineScope
class KosmosVolumePanelComponentFactory(private val kosmos: Kosmos) : VolumePanelComponentFactory {
diff --git a/core/java/android/app/StatusBarManager.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/VolumePanelComponentFactoryKosmos.kt
similarity index 61%
copy from core/java/android/app/StatusBarManager.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/VolumePanelComponentFactoryKosmos.kt
index 687678c..b0b06f1 100644
--- a/core/java/android/app/StatusBarManager.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/VolumePanelComponentFactoryKosmos.kt
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,6 +14,8 @@
* limitations under the License.
*/
-package android.app;
+package com.android.systemui.volume.panel.dagger.factory
-parcelable StatusBarManager.DisableInfo;
\ No newline at end of file
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.volumePanelComponentFactory by Kosmos.Fixture { KosmosVolumePanelComponentFactory(this) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/ComponentAvailabilityCriteriaKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/ComponentAvailabilityCriteriaKosmos.kt
new file mode 100644
index 0000000..e0547ee
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/ComponentAvailabilityCriteriaKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.domain
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.unavailableCriteria: ComponentAvailabilityCriteria by
+ Kosmos.Fixture { TestComponentAvailabilityCriteria(false) }
+val Kosmos.availableCriteria: ComponentAvailabilityCriteria by
+ Kosmos.Fixture { AlwaysAvailableCriteria() }
+var Kosmos.defaultCriteria: ComponentAvailabilityCriteria by Kosmos.Fixture { availableCriteria }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt
new file mode 100644
index 0000000..8862942
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.panel.component.bottombar.ui.bottomBarAvailabilityCriteria
+import com.android.systemui.volume.panel.component.mediaoutput.mediaOutputAvailabilityCriteria
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.component.volume.volumeSlidersAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.defaultCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.ui.composable.enabledComponents
+import javax.inject.Provider
+
+var Kosmos.criteriaByKey: Map<VolumePanelComponentKey, Provider<ComponentAvailabilityCriteria>> by
+ Kosmos.Fixture { emptyMap() }
+var Kosmos.prodCriteriaByKey:
+ Map<VolumePanelComponentKey, Provider<ComponentAvailabilityCriteria>> by
+ Kosmos.Fixture {
+ mapOf(
+ VolumePanelComponents.MEDIA_OUTPUT to Provider { mediaOutputAvailabilityCriteria },
+ VolumePanelComponents.VOLUME_SLIDERS to Provider { volumeSlidersAvailabilityCriteria },
+ VolumePanelComponents.BOTTOM_BAR to Provider { bottomBarAvailabilityCriteria },
+ )
+ }
+
+var Kosmos.componentsInteractor: ComponentsInteractor by
+ Kosmos.Fixture {
+ ComponentsInteractorImpl(
+ enabledComponents,
+ { defaultCriteria },
+ testScope.backgroundScope,
+ criteriaByKey,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponentKosmos.kt
new file mode 100644
index 0000000..afe8e62
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponentKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.shared.model
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+import javax.inject.Provider
+
+val Kosmos.mockVolumePanelUiComponent: VolumePanelUiComponent by Kosmos.Fixture { mock {} }
+val Kosmos.mockVolumePanelUiComponentProvider: Provider<VolumePanelUiComponent> by
+ Kosmos.Fixture { Provider { mock {} } }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryKosmos.kt
new file mode 100644
index 0000000..bacf22c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.composable
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.panel.component.bottombar.ui.bottomBarComponent
+import com.android.systemui.volume.panel.component.mediaoutput.mediaOutputComponent
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.component.volume.volumeSlidersComponent
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import javax.inject.Provider
+
+var Kosmos.componentByKey: Map<VolumePanelComponentKey, Provider<VolumePanelUiComponent>> by
+ Kosmos.Fixture { emptyMap() }
+var Kosmos.prodComponentByKey: Map<VolumePanelComponentKey, Provider<VolumePanelUiComponent>> by
+ Kosmos.Fixture {
+ mapOf(
+ VolumePanelComponents.BOTTOM_BAR to Provider { bottomBarComponent },
+ VolumePanelComponents.MEDIA_OUTPUT to Provider { mediaOutputComponent },
+ VolumePanelComponents.VOLUME_SLIDERS to Provider { volumeSlidersComponent },
+ )
+ }
+var Kosmos.enabledComponents: Collection<VolumePanelComponentKey> by
+ Kosmos.Fixture { componentByKey.keys }
+
+val Kosmos.componentsFactory: ComponentsFactory by
+ Kosmos.Fixture { ComponentsFactory(componentByKey) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManagerKosmos.kt
new file mode 100644
index 0000000..e0480e7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManagerKosmos.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.layout
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+
+var Kosmos.volumePanelBottomBarComponentKey: VolumePanelComponentKey by
+ Kosmos.Fixture { VolumePanelComponents.BOTTOM_BAR }
+var Kosmos.volumePanelHeaderComponentKeys: Collection<VolumePanelComponentKey> by
+ Kosmos.Fixture { listOf(VolumePanelComponents.MEDIA_OUTPUT) }
+var Kosmos.volumePanelFooterComponentKeys: Collection<VolumePanelComponentKey> by
+ Kosmos.Fixture {
+ listOf(
+ VolumePanelComponents.ANC,
+ VolumePanelComponents.SPATIAL_AUDIO,
+ VolumePanelComponents.CAPTIONING,
+ )
+ }
+
+var Kosmos.componentsLayoutManager: ComponentsLayoutManager by
+ Kosmos.Fixture {
+ DefaultComponentsLayoutManager(
+ bottomBar = volumePanelBottomBarComponentKey,
+ headerComponents = volumePanelHeaderComponentKeys,
+ footerComponents = volumePanelFooterComponentKeys,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt
new file mode 100644
index 0000000..a606588
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.volume.panel.dagger.factory.volumePanelComponentFactory
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
+
+var Kosmos.volumePanelStartables: Set<VolumePanelStartable> by Kosmos.Fixture { emptySet() }
+
+var Kosmos.volumePanelViewModel: VolumePanelViewModel by
+ Kosmos.Fixture { volumePanelViewModelFactory.create(testScope.backgroundScope) }
+
+val Kosmos.volumePanelViewModelFactory: VolumePanelViewModel.Factory by
+ Kosmos.Fixture {
+ VolumePanelViewModel.Factory(
+ applicationContext,
+ volumePanelComponentFactory,
+ configurationController,
+ broadcastDispatcher,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorKosmos.kt
new file mode 100644
index 0000000..63b3f23
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.ui.navigation
+
+import com.android.internal.logging.uiEventLoggerFake
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.mainCoroutineContext
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.statusbar.phone.systemUIDialogFactory
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.volume.VolumePanelFactory
+import com.android.systemui.volume.panel.ui.viewmodel.volumePanelViewModelFactory
+
+val Kosmos.volumeNavigator by
+ Kosmos.Fixture {
+ VolumeNavigator(
+ testScope,
+ mainCoroutineContext,
+ mock<VolumePanelFactory> { /* Unsupported and unused */},
+ activityStarter,
+ volumePanelViewModelFactory,
+ systemUIDialogFactory,
+ uiEventLoggerFake,
+ )
+ }
diff --git a/ravenwood/scripts/convert-androidtest.py b/ravenwood/scripts/convert-androidtest.py
new file mode 100755
index 0000000..61ec54b
--- /dev/null
+++ b/ravenwood/scripts/convert-androidtest.py
@@ -0,0 +1,184 @@
+#!/usr/bin/python3
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script converts a legacy test class (using AndroidTestCase, TestCase or
+# InstrumentationTestCase to a modern style test class, in a best-effort manner.
+#
+# Usage:
+# convert-androidtest.py TARGET-FILE [TARGET-FILE ...]
+#
+# Caveats:
+# - It adds all the extra imports, even if they're not needed.
+# - It won't sort imports.
+# - It also always adds getContext() and getTestContext().
+#
+
+import sys
+import fileinput
+import re
+import subprocess
+
+# Print message on console
+def log(msg):
+ print(msg, file=sys.stderr)
+
+
+# Matches `extends AndroidTestCase` (or another similar base class)
+re_extends = re.compile(
+ r''' \b extends \s+ (AndroidTestCase|TestCase|InstrumentationTestCase) \s* ''',
+ re.S + re.X)
+
+
+# Look into given files and return the files that have `re_extends`.
+def find_target_files(files):
+ ret = []
+
+ for file in files:
+ try:
+ with open(file, 'r') as f:
+ data = f.read()
+
+ if re_extends.search(data):
+ ret.append(file)
+
+ except FileNotFoundError as e:
+ log(f'Failed to open file {file}: {e}')
+
+ return ret
+
+
+def main(args):
+ files = args
+
+ # Find the files that should be processed.
+ files = find_target_files(files)
+
+ if len(files) == 0:
+ log("No target files found.")
+ return 0
+
+ # Process the files.
+ with fileinput.input(files=(files), inplace = True, backup = '.bak') as f:
+ import_seen = False
+ carry_over = ''
+ class_body_started = False
+ class_seen = False
+
+ def on_file_start():
+ nonlocal import_seen, carry_over, class_body_started, class_seen
+ import_seen = False
+ carry_over = ''
+ class_body_started = False
+ class_seen = False
+
+ for line in f:
+ if (fileinput.filelineno() == 1):
+ log(f"Processing: {fileinput.filename()}")
+ on_file_start()
+
+ line = line.rstrip('\n')
+
+ # Carry over a certain line to the next line.
+ if re.search(r'''@Override\b''', line):
+ carry_over = carry_over + line + '\n'
+ continue
+
+ if carry_over:
+ line = carry_over + line
+ carry_over = ''
+
+
+ # Remove the base class from the class definition.
+ line = re_extends.sub('', line)
+
+ # Add a @RunWith.
+ if not class_seen and re.search(r'''\b class \b''', line, re.X):
+ class_seen = True
+ print("@RunWith(AndroidJUnit4.class)")
+
+
+ # Inject extra imports.
+ if not import_seen and re.search(r'''^import\b''', line):
+ import_seen = True
+ print("""\
+import android.content.Context;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertSame;
+import static junit.framework.TestCase.assertNotSame;
+import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.fail;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+""")
+
+ # Add @Test to the test methods.
+ if re.search(r'''^ \s* public \s* void \s* test''', line, re.X):
+ print(" @Test")
+
+ # Convert setUp/tearDown to @Before/@After.
+ if re.search(r''' ^\s+ ( \@Override \s+ ) ? (public|protected) \s+ void \s+ (setUp|tearDown) ''',
+ line, re.X):
+ if re.search('setUp', line):
+ print(' @Before')
+ else:
+ print(' @After')
+
+ line = re.sub(r''' \s* \@Override \s* \n ''', '', line, 0, re.X)
+ line = re.sub(r'''protected''', 'public', line, 0, re.X)
+
+ # Remove the super setUp / tearDown call.
+ if re.search(r''' \b super \. (setUp|tearDown) \b ''', line, re.X):
+ continue
+
+ # Convert mContext to getContext().
+ line = re.sub(r'''\b mContext \b ''', 'getContext()', line, 0, re.X)
+
+ # Print the processed line.
+ print(line)
+
+ # Add getContext() / getTestContext() at the beginning of the class.
+ if not class_body_started and re.search(r'''\{''', line):
+ class_body_started = True
+ print("""\
+ private Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getTargetContext();
+ }
+
+ private Context getTestContext() {
+ return InstrumentationRegistry.getInstrumentation().getContext();
+ }
+""")
+
+
+ # Run diff
+ for file in files:
+ subprocess.call(["diff", "-u", "--color=auto", f"{file}.bak", file])
+
+ log(f'{len(files)} file(s) converted.')
+
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index e830523..06a0297 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -83,6 +83,7 @@
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
@@ -172,6 +173,7 @@
private static final String TAG = "AppWidgetServiceImpl";
private static final boolean DEBUG = false;
+ private static final boolean DEBUG_NULL_PROVIDER_INFO = Build.IS_DEBUGGABLE;
private static final String OLD_KEYGUARD_HOST_PACKAGE = "android";
private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
@@ -736,7 +738,10 @@
}
RemoteViews views = new RemoteViews(mContext.getPackageName(),
R.layout.work_widget_mask_view);
- ApplicationInfo appInfo = provider.info.providerInfo.applicationInfo;
+ final ActivityInfo activityInfo = provider.info.providerInfo;
+ final ApplicationInfo appInfo = activityInfo != null ? activityInfo.applicationInfo : null;
+ final String packageName = appInfo != null
+ ? appInfo.packageName : provider.id.componentName.getPackageName();
final int appUserId = provider.getUserId();
boolean showBadge = false;
@@ -750,7 +755,7 @@
} else if (provider.maskedBySuspendedPackage) {
showBadge = mUserManager.hasBadge(appUserId);
final UserPackage suspendingPackage = mPackageManagerInternal.getSuspendingPackage(
- appInfo.packageName, appUserId);
+ packageName, appUserId);
// TODO(b/281839596): don't rely on platform always meaning suspended by admin.
if (suspendingPackage != null
&& PLATFORM_PACKAGE_NAME.equals(suspendingPackage.packageName)) {
@@ -759,11 +764,11 @@
} else {
final SuspendDialogInfo dialogInfo =
mPackageManagerInternal.getSuspendedDialogInfo(
- appInfo.packageName, suspendingPackage, appUserId);
+ packageName, suspendingPackage, appUserId);
// onUnsuspend is null because we don't want to start any activity on
// unsuspending from a suspended widget.
onClickIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent(
- appInfo.packageName, suspendingPackage, dialogInfo, null, null,
+ packageName, suspendingPackage, dialogInfo, null, null,
appUserId);
}
} else if (provider.maskedByLockedProfile) {
@@ -778,7 +783,7 @@
showBadge = mUserManager.hasBadge(appUserId);
}
- Icon icon = appInfo.icon != 0
+ Icon icon = (appInfo != null && appInfo.icon != 0)
? Icon.createWithResource(appInfo.packageName, appInfo.icon)
: Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
views.setImageViewIcon(R.id.work_widget_app_icon, icon);
@@ -2955,6 +2960,9 @@
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
info.provider = providerId.componentName;
info.providerInfo = ri.activityInfo;
+ if (DEBUG_NULL_PROVIDER_INFO) {
+ Objects.requireNonNull(ri.activityInfo);
+ }
return info;
}
return null;
@@ -2989,6 +2997,9 @@
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
info.provider = providerId.componentName;
info.providerInfo = activityInfo;
+ if (DEBUG_NULL_PROVIDER_INFO) {
+ Objects.requireNonNull(activityInfo);
+ }
final Resources resources;
final long identity = Binder.clearCallingIdentity();
@@ -3564,6 +3575,9 @@
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
info.provider = providerId.componentName;
info.providerInfo = providerInfo;
+ if (DEBUG_NULL_PROVIDER_INFO) {
+ Objects.requireNonNull(providerInfo);
+ }
provider = new Provider();
provider.setPartialInfoLocked(info);
@@ -3580,6 +3594,9 @@
if (info != null) {
info.provider = providerId.componentName;
info.providerInfo = providerInfo;
+ if (DEBUG_NULL_PROVIDER_INFO) {
+ Objects.requireNonNull(providerInfo);
+ }
provider.setInfoLocked(info);
}
}
@@ -5714,7 +5731,9 @@
// so we tear it down in anticipation of it (possibly) being
// reconstructed due to the restore
host.widgets.remove(widget);
- provider.widgets.remove(widget);
+ if (provider != null) {
+ provider.widgets.remove(widget);
+ }
// Check if we need to destroy any services (if no other app widgets are
// referencing the same service)
decrementAppWidgetServiceRefCount(widget);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 763879e..6fc05b7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -1998,6 +1998,19 @@
}
@Override
+ public void setViewAutofilled(int sessionId, @NonNull AutofillId id, int userId) {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ service.setViewAutofilled(sessionId, getCallingUid(), id);
+ } else if (sVerbose) {
+ Slog.v(TAG, "setAutofillFailure(): no service for " + userId);
+ }
+ }
+ }
+
+ @Override
public void finishSession(int sessionId, int userId,
@AutofillCommitReason int commitReason) {
synchronized (mLock) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 92acce2..588266f 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -466,6 +466,7 @@
@GuardedBy("mLock")
void setAutofillFailureLocked(int sessionId, int uid, @NonNull List<AutofillId> ids) {
if (!isEnabledLocked()) {
+ Slog.wtf(TAG, "Service not enabled");
return;
}
final Session session = mSessions.get(sessionId);
@@ -477,8 +478,23 @@
}
@GuardedBy("mLock")
+ void setViewAutofilled(int sessionId, int uid, @NonNull AutofillId id) {
+ if (!isEnabledLocked()) {
+ Slog.wtf(TAG, "Service not enabled");
+ return;
+ }
+ final Session session = mSessions.get(sessionId);
+ if (session == null || uid != session.uid) {
+ Slog.v(TAG, "setViewAutofilled(): no session for " + sessionId + "(" + uid + ")");
+ return;
+ }
+ session.setViewAutofilled(id);
+ }
+
+ @GuardedBy("mLock")
void finishSessionLocked(int sessionId, int uid, @AutofillCommitReason int commitReason) {
if (!isEnabledLocked()) {
+ Slog.wtf(TAG, "Service not enabled");
return;
}
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 9c84b12..f289115 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -65,6 +65,7 @@
import android.provider.Settings;
import android.service.autofill.Dataset;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Slog;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
@@ -548,9 +549,10 @@
/**
* Set views_fillable_total_count as long as mEventInternal presents.
*/
- public void maybeSetViewFillableCounts(int totalFillableCount) {
+ public void maybeSetViewFillablesAndCount(List<AutofillId> autofillIds) {
mEventInternal.ifPresent(event -> {
- event.mViewFillableTotalCount = totalFillableCount;
+ event.mAutofillIdsAttemptedAutofill = new ArraySet<>(autofillIds);
+ event.mViewFillableTotalCount = event.mAutofillIdsAttemptedAutofill.size();
});
}
@@ -564,6 +566,41 @@
});
}
+ /** Sets focused_autofill_id using view id */
+ public void maybeSetFocusedId(AutofillId id) {
+ maybeSetFocusedId(id.getViewId());
+ }
+
+ /** Sets focused_autofill_id as long as mEventInternal is present */
+ public void maybeSetFocusedId(int id) {
+ mEventInternal.ifPresent(event -> {
+ event.mFocusedId = id;
+ });
+ }
+ /**
+ * Set views_filled_failure_count using failure count as long as mEventInternal
+ * presents.
+ */
+ public void maybeAddSuccessId(AutofillId autofillId) {
+ mEventInternal.ifPresent(event -> {
+ ArraySet<AutofillId> autofillIds = event.mAutofillIdsAttemptedAutofill;
+ if (autofillIds == null) {
+ Slog.w(TAG, "Attempted autofill ids is null, but received autofillId:" + autofillId
+ + " successfully filled");
+ event.mViewFilledButUnexpectedCount++;
+ } else if (autofillIds.contains(autofillId)) {
+ if (sVerbose) {
+ Slog.v(TAG, "Logging autofill for id:" + autofillId);
+ event.mViewFillSuccessCount++;
+ }
+ } else {
+ Slog.w(TAG, "Successfully filled autofillId:" + autofillId
+ + " not found in list of attempted autofill ids: " + autofillIds);
+ event.mViewFilledButUnexpectedCount++;
+ }
+ });
+ }
+
public void logAndEndEvent() {
if (!mEventInternal.isPresent()) {
Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
@@ -608,7 +645,10 @@
+ " mIsCredentialRequest=" + event.mIsCredentialRequest
+ " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential
+ " mViewFillableTotalCount=" + event.mViewFillableTotalCount
- + " mViewFillFailureCount=" + event.mViewFillFailureCount);
+ + " mViewFillFailureCount=" + event.mViewFillFailureCount
+ + " mFocusedId=" + event.mFocusedId
+ + " mViewFillSuccessCount=" + event.mViewFillSuccessCount
+ + " mViewFilledButUnexpectedCount=" + event.mViewFilledButUnexpectedCount);
}
// TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -651,7 +691,10 @@
event.mIsCredentialRequest,
event.mWebviewRequestedCredential,
event.mViewFillableTotalCount,
- event.mViewFillFailureCount);
+ event.mViewFillFailureCount,
+ event.mFocusedId,
+ event.mViewFillSuccessCount,
+ event.mViewFilledButUnexpectedCount);
mEventInternal = Optional.empty();
}
@@ -689,7 +732,14 @@
boolean mWebviewRequestedCredential = false;
int mViewFillableTotalCount = -1;
int mViewFillFailureCount = -1;
+ int mFocusedId = -1;
+ // Default value for success count is set to 0 explicitly. Setting it to -1 for
+ // uninitialized doesn't help much, as this would be non-zero only if callback is received.
+ int mViewFillSuccessCount = 0;
+ int mViewFilledButUnexpectedCount = 0;
+
+ ArraySet<AutofillId> mAutofillIdsAttemptedAutofill;
PresentationStatsEventInternal() {}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 8b13c4b7..07b16c5 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -4669,6 +4669,7 @@
mFieldClassificationIdSnapshot);
mPresentationStatsEventLogger.maybeSetAvailableCount(
response.getDatasets(), mCurrentViewId);
+ mPresentationStatsEventLogger.maybeSetFocusedId(mCurrentViewId);
}
@GuardedBy("mLock")
@@ -5381,7 +5382,20 @@
}
}
mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids.size());
- mPresentationStatsEventLogger.logAndEndEvent();
+ }
+
+ /**
+ * Sets the state of views that failed to autofill.
+ */
+ @GuardedBy("mLock")
+ void setViewAutofilled(@NonNull AutofillId id) {
+ if (sVerbose) {
+ Slog.v(TAG, "View autofilled: " + id);
+ }
+ if (id.getSessionId() == AutofillId.NO_SESSION) {
+ id.setSessionId(this.id);
+ }
+ mPresentationStatsEventLogger.maybeAddSuccessId(id);
}
@GuardedBy("mLock")
@@ -6589,7 +6603,7 @@
if (sVerbose) {
Slog.v(TAG, "Total views to be autofilled: " + ids.size());
}
- mPresentationStatsEventLogger.maybeSetViewFillableCounts(ids.size());
+ mPresentationStatsEventLogger.maybeSetViewFillablesAndCount(ids);
if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
mClient.autofill(id, ids, values, hideHighlight);
if (dataset.getId() != null) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
index 70443f9..38a412f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
+++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
@@ -118,7 +118,7 @@
final InputMethodManagerInternal inputMethodManagerInternal =
LocalServices.getService(InputMethodManagerInternal.class);
if (!inputMethodManagerInternal.transferTouchFocusToImeWindow(sourceInputToken,
- displayId)) {
+ displayId, mUserId)) {
Slog.e(TAG, "Cannot transfer touch focus from suggestion to IME");
mOnErrorCallback.run();
}
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index d7c65c7..70af49c 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -224,7 +224,7 @@
token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
mNativeWrapper.closeUinput(inputDeviceDescriptor.getNativePointer());
String phys = inputDeviceDescriptor.getPhys();
- InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys);
+ InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys);
// Type associations are added in the case of navigation touchpads. Those should be removed
// once the input device gets closed.
if (inputDeviceDescriptor.getType() == InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD) {
@@ -287,7 +287,7 @@
private void setUniqueIdAssociation(int displayId, String phys) {
final String displayUniqueId = mDisplayManagerInternal.getDisplayInfo(displayId).uniqueId;
- InputManagerGlobal.getInstance().addUniqueIdAssociation(phys, displayUniqueId);
+ InputManagerGlobal.getInstance().addUniqueIdAssociationByPort(phys, displayUniqueId);
}
boolean sendDpadKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) {
@@ -789,7 +789,7 @@
throw e;
}
} catch (DeviceCreationException e) {
- InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys);
+ InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys);
throw e;
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 3ff0504..d153c18 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -258,6 +258,8 @@
"core_os_flags_lib",
"connectivity_flags_lib",
"dreams_flags_lib",
+ "aconfig_new_storage_flags_lib",
+ "aconfigd_java_proto_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 748253f..1a8c3b0 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -35,7 +35,6 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
-import android.os.Bundle;
import android.os.BundleMerger;
import android.os.Debug;
import android.os.DropBoxManager;
@@ -176,6 +175,16 @@
}
};
+ private static final BundleMerger sDropboxEntryAddedExtrasMerger;
+ static {
+ sDropboxEntryAddedExtrasMerger = new BundleMerger();
+ sDropboxEntryAddedExtrasMerger.setDefaultMergeStrategy(BundleMerger.STRATEGY_FIRST);
+ sDropboxEntryAddedExtrasMerger.setMergeStrategy(DropBoxManager.EXTRA_TIME,
+ BundleMerger.STRATEGY_COMPARABLE_MAX);
+ sDropboxEntryAddedExtrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT,
+ BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD);
+ }
+
private final IDropBoxManagerService.Stub mStub = new IDropBoxManagerService.Stub() {
@Override
public void addData(String tag, byte[] data, int flags) {
@@ -284,7 +293,7 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SEND_BROADCAST:
- prepareAndSendBroadcast((Intent) msg.obj, null);
+ prepareAndSendBroadcast((Intent) msg.obj, false);
break;
case MSG_SEND_DEFERRED_BROADCAST:
Intent deferredIntent;
@@ -292,31 +301,42 @@
deferredIntent = mDeferredMap.remove((String) msg.obj);
}
if (deferredIntent != null) {
- prepareAndSendBroadcast(deferredIntent,
- createBroadcastOptions(deferredIntent));
+ prepareAndSendBroadcast(deferredIntent, true);
}
break;
}
}
- private void prepareAndSendBroadcast(Intent intent, Bundle options) {
+ private void prepareAndSendBroadcast(Intent intent, boolean deferrable) {
if (!DropBoxManagerService.this.mBooted) {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
if (Flags.enableReadDropboxPermission()) {
- BroadcastOptions unbundledOptions = (options == null)
- ? BroadcastOptions.makeBasic() : BroadcastOptions.fromBundle(options);
-
- unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true);
+ options.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true);
+ if (deferrable) {
+ final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG)
+ + "-READ_DROPBOX_DATA";
+ setBroadcastOptionsForDeferral(options, matchingKey);
+ }
getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
- Manifest.permission.READ_DROPBOX_DATA, unbundledOptions.toBundle());
+ Manifest.permission.READ_DROPBOX_DATA, options.toBundle());
- unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false);
+ options.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false);
+ if (deferrable) {
+ final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG)
+ + "-READ_LOGS";
+ setBroadcastOptionsForDeferral(options, matchingKey);
+ }
getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
- Manifest.permission.READ_LOGS, unbundledOptions.toBundle());
+ Manifest.permission.READ_LOGS, options.toBundle());
} else {
+ if (deferrable) {
+ final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG);
+ setBroadcastOptionsForDeferral(options, matchingKey);
+ }
getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
- android.Manifest.permission.READ_LOGS, options);
+ android.Manifest.permission.READ_LOGS, options.toBundle());
}
}
@@ -328,21 +348,12 @@
return dropboxIntent;
}
- private Bundle createBroadcastOptions(Intent intent) {
- final BundleMerger extrasMerger = new BundleMerger();
- extrasMerger.setDefaultMergeStrategy(BundleMerger.STRATEGY_FIRST);
- extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_TIME,
- BundleMerger.STRATEGY_COMPARABLE_MAX);
- extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT,
- BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD);
-
- return BroadcastOptions.makeBasic()
- .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED)
+ private void setBroadcastOptionsForDeferral(BroadcastOptions options, String matchingKey) {
+ options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED)
.setDeliveryGroupMatchingKey(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED,
- intent.getStringExtra(DropBoxManager.EXTRA_TAG))
- .setDeliveryGroupExtrasMerger(extrasMerger)
- .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
- .toBundle();
+ matchingKey)
+ .setDeliveryGroupExtrasMerger(sDropboxEntryAddedExtrasMerger)
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
}
/**
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index f1776f4..f1d3584 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -1986,11 +1986,9 @@
// the status bar should be totally disabled, the calls below will
// have no effect until the device is unlocked.
if (mStatusBarManager != null) {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- if (mCarModeEnabled) {
- info.setNotificationTickerDisabled(true);
- }
- mStatusBarManager.requestDisabledComponent(info, "adjustStatusBarCarModeLocked");
+ mStatusBarManager.disable(mCarModeEnabled
+ ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
+ : StatusBarManager.DISABLE_NONE);
}
if (mNotificationManager == null) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4ca9e33..94bf813 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -157,6 +157,7 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
+import android.app.RemoteServiceException.ForegroundServiceDidNotStopInTimeException;
import android.app.Service;
import android.app.ServiceStartArgs;
import android.app.StartForegroundCalledOnStoppedServiceException;
@@ -784,7 +785,7 @@
ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,
"SERVICE_FOREGROUND_TIMEOUT");
this.mFGSAnrTimer = new ServiceAnrTimer(service,
- ActivityManagerService.SERVICE_FGS_ANR_TIMEOUT_MSG,
+ ActivityManagerService.SERVICE_FGS_CRASH_TIMEOUT_MSG,
"FGS_TIMEOUT");
}
@@ -1168,9 +1169,7 @@
}
private boolean shouldAllowBootCompletedStart(ServiceRecord r, int foregroundServiceType) {
- @PowerExemptionManager.ReasonCode final int fgsStartReasonCode =
- r.mInfoTempFgsAllowListReason != null ? r.mInfoTempFgsAllowListReason.mReasonCode
- : REASON_DENIED;
+ @PowerExemptionManager.ReasonCode final int fgsStartReasonCode = r.getFgsAllowStart();
if (Flags.fgsBootCompleted()
&& CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, r.appInfo.uid)
&& fgsStartReasonCode == PowerExemptionManager.REASON_BOOT_COMPLETED) {
@@ -2454,10 +2453,21 @@
} else if (lastTimeOutAt > 0) {
// Time limit was exhausted within the past 24 hours and the app
// has not been in the TOP state since then, throw an exception.
- throw new ForegroundServiceStartNotAllowedException("Time limit"
- + " already exhausted for foreground service type "
+ final String exceptionMsg = "Time limit already exhausted for"
+ + " foreground service type "
+ ServiceInfo.foregroundServiceTypeToLabel(
- foregroundServiceType));
+ foregroundServiceType);
+ // Only throw an exception if the new ANR behavior
+ // ("do nothing") is not gated or the new crashing logic gate
+ // is enabled; otherwise, reset the limit temporarily.
+ if (!android.app.Flags.gateFgsTimeoutAnrBehavior()
+ || android.app.Flags.enableFgsTimeoutCrashBehavior()) {
+ throw new ForegroundServiceStartNotAllowedException(
+ exceptionMsg);
+ } else {
+ Slog.wtf(TAG, exceptionMsg);
+ fgsTypeInfo.reset();
+ }
}
}
} else {
@@ -3929,12 +3939,12 @@
Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e);
}
- // ANR the service after giving the service some time to clean up.
- mFGSAnrTimer.start(sr, mAm.mConstants.mFgsAnrExtraWaitDuration);
+ // Crash the service after giving the service some time to clean up.
+ mFGSAnrTimer.start(sr, mAm.mConstants.mFgsCrashExtraWaitDuration);
}
}
- void onFgsAnrTimeout(ServiceRecord sr) {
+ void onFgsCrashTimeout(ServiceRecord sr) {
final int fgsType = getTimeLimitedFgsType(sr.foregroundServiceType);
if (fgsType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
return; // no timed out FGS type was found (either it was stopped or it switched types)
@@ -3943,20 +3953,42 @@
+ ServiceInfo.foregroundServiceTypeToLabel(fgsType)
+ " did not stop within its timeout: " + sr.getComponentName();
- final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason);
- tr.mLatencyTracker.waitingOnAMSLockStarted();
- synchronized (mAm) {
- tr.mLatencyTracker.waitingOnAMSLockEnded();
+ if (android.app.Flags.gateFgsTimeoutAnrBehavior()) {
+ // Log a WTF instead of throwing an ANR while the new behavior is gated.
+ Slog.wtf(TAG, reason);
+ return;
+ }
- Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr);
- traceInstant("FGS ANR: ", sr);
- if (sr.app != null) {
- mAm.appNotResponding(sr.app, tr);
+ if (android.app.Flags.enableFgsTimeoutCrashBehavior()) {
+ // Crash the app
+ synchronized (mAm) {
+ Slog.e(TAG_SERVICE, "FGS Crashed: " + sr);
+ traceInstant("FGS Crash: ", sr);
+ if (sr.app != null) {
+ mAm.crashApplicationWithTypeWithExtras(sr.app.uid, sr.app.getPid(),
+ sr.app.info.packageName, sr.app.userId, reason, false /*force*/,
+ ForegroundServiceDidNotStopInTimeException.TYPE_ID,
+ ForegroundServiceDidNotStopInTimeException
+ .createExtrasForService(sr.getComponentName()));
+ }
}
+ } else {
+ // ANR the app if the new crash behavior is not enabled
+ final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason);
+ tr.mLatencyTracker.waitingOnAMSLockStarted();
+ synchronized (mAm) {
+ tr.mLatencyTracker.waitingOnAMSLockEnded();
- // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR
- // dialog really doesn't remember the "cause" (especially if there have been multiple
- // ANRs), so it's not doable.
+ Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr);
+ traceInstant("FGS ANR: ", sr);
+ if (sr.app != null) {
+ mAm.appNotResponding(sr.app, tr);
+ }
+
+ // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR
+ // dialog really doesn't remember the "cause" (especially if there have been
+ // multiple ANRs), so it's not doable.
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 9e06b75..26aa053 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1115,17 +1115,17 @@
/**
* If a service of a timeout-enforced type doesn't finish within this duration after its
- * timeout, then we'll declare an ANR.
+ * timeout, then we'll crash the app.
* i.e. if the time limit for a type is 1 hour, and this extra duration is 10 seconds, then
- * the app will be ANR'ed 1 hour and 10 seconds after it started.
+ * the app will crash 1 hour and 10 seconds after it started.
*/
- private static final String KEY_FGS_ANR_EXTRA_WAIT_DURATION = "fgs_anr_extra_wait_duration";
+ private static final String KEY_FGS_CRASH_EXTRA_WAIT_DURATION = "fgs_crash_extra_wait_duration";
- /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */
- static final long DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION = 10_000;
+ /** @see #KEY_FGS_CRASH_EXTRA_WAIT_DURATION */
+ static final long DEFAULT_FGS_CRASH_EXTRA_WAIT_DURATION = 10_000;
- /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */
- public volatile long mFgsAnrExtraWaitDuration = DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION;
+ /** @see #KEY_FGS_CRASH_EXTRA_WAIT_DURATION */
+ public volatile long mFgsCrashExtraWaitDuration = DEFAULT_FGS_CRASH_EXTRA_WAIT_DURATION;
/** @see #KEY_USE_TIERED_CACHED_ADJ */
public boolean USE_TIERED_CACHED_ADJ = DEFAULT_USE_TIERED_CACHED_ADJ;
@@ -1315,8 +1315,8 @@
case KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION:
updateShortFgsAnrExtraWaitDuration();
break;
- case KEY_FGS_ANR_EXTRA_WAIT_DURATION:
- updateFgsAnrExtraWaitDuration();
+ case KEY_FGS_CRASH_EXTRA_WAIT_DURATION:
+ updateFgsCrashExtraWaitDuration();
break;
case KEY_PROACTIVE_KILLS_ENABLED:
updateProactiveKillsEnabled();
@@ -2199,11 +2199,11 @@
DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION);
}
- private void updateFgsAnrExtraWaitDuration() {
- mFgsAnrExtraWaitDuration = DeviceConfig.getLong(
+ private void updateFgsCrashExtraWaitDuration() {
+ mFgsCrashExtraWaitDuration = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- KEY_FGS_ANR_EXTRA_WAIT_DURATION,
- DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION);
+ KEY_FGS_CRASH_EXTRA_WAIT_DURATION,
+ DEFAULT_FGS_CRASH_EXTRA_WAIT_DURATION);
}
private void updateEnableWaitForFinishAttachApplication() {
@@ -2456,8 +2456,8 @@
pw.print("="); pw.println(mMediaProcessingFgsTimeoutDuration);
pw.print(" "); pw.print(KEY_DATA_SYNC_FGS_TIMEOUT_DURATION);
pw.print("="); pw.println(mDataSyncFgsTimeoutDuration);
- pw.print(" "); pw.print(KEY_FGS_ANR_EXTRA_WAIT_DURATION);
- pw.print("="); pw.println(mFgsAnrExtraWaitDuration);
+ pw.print(" "); pw.print(KEY_FGS_CRASH_EXTRA_WAIT_DURATION);
+ pw.print("="); pw.println(mFgsCrashExtraWaitDuration);
pw.print(" "); pw.print(KEY_USE_TIERED_CACHED_ADJ);
pw.print("="); pw.println(USE_TIERED_CACHED_ADJ);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 00c2df6..46ed1fd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1670,7 +1670,7 @@
static final int BIND_APPLICATION_TIMEOUT_SOFT_MSG = 82;
static final int BIND_APPLICATION_TIMEOUT_HARD_MSG = 83;
static final int SERVICE_FGS_TIMEOUT_MSG = 84;
- static final int SERVICE_FGS_ANR_TIMEOUT_MSG = 85;
+ static final int SERVICE_FGS_CRASH_TIMEOUT_MSG = 85;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -2041,8 +2041,8 @@
case SERVICE_FGS_TIMEOUT_MSG: {
mServices.onFgsTimeout((ServiceRecord) msg.obj);
} break;
- case SERVICE_FGS_ANR_TIMEOUT_MSG: {
- mServices.onFgsAnrTimeout((ServiceRecord) msg.obj);
+ case SERVICE_FGS_CRASH_TIMEOUT_MSG: {
+ mServices.onFgsCrashTimeout((ServiceRecord) msg.obj);
} break;
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 3cea014..a182a10 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -115,6 +115,7 @@
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.DisplayMetrics;
+import android.util.SparseArray;
import android.util.TeeWriter;
import android.util.proto.ProtoOutputStream;
import android.view.Choreographer;
@@ -275,9 +276,9 @@
case "compact":
return runCompact(pw);
case "freeze":
- return runFreeze(pw);
+ return runFreeze(pw, true);
case "unfreeze":
- return runUnfreeze(pw);
+ return runFreeze(pw, false);
case "instrument":
getOutPrintWriter().println("Error: must be invoked through 'am instrument'.");
return -1;
@@ -1203,45 +1204,27 @@
}
@NeverCompile
- int runFreeze(PrintWriter pw) throws RemoteException {
+ int runFreeze(PrintWriter pw, boolean freeze) throws RemoteException {
String freezerOpt = getNextOption();
boolean isSticky = false;
- if (freezerOpt != null) {
- isSticky = freezerOpt.equals("--sticky");
- }
- ProcessRecord app = getProcessFromShell();
- if (app == null) {
- getErrPrintWriter().println("Error: could not find process");
- return -1;
- }
- pw.println("Freezing pid: " + app.mPid + " sticky=" + isSticky);
- synchronized (mInternal) {
- synchronized (mInternal.mProcLock) {
- app.mOptRecord.setFreezeSticky(isSticky);
- mInternal.mOomAdjuster.mCachedAppOptimizer.forceFreezeAppAsyncLSP(app);
- }
- }
- return 0;
- }
- @NeverCompile
- int runUnfreeze(PrintWriter pw) throws RemoteException {
- String freezerOpt = getNextOption();
- boolean isSticky = false;
if (freezerOpt != null) {
isSticky = freezerOpt.equals("--sticky");
}
- ProcessRecord app = getProcessFromShell();
- if (app == null) {
- getErrPrintWriter().println("Error: could not find process");
+ ProcessRecord proc = getProcessFromShell();
+ if (proc == null) {
return -1;
}
- pw.println("Unfreezing pid: " + app.mPid);
+ pw.print(freeze ? "Freezing" : "Unfreezing");
+ pw.print(" process " + proc.processName);
+ pw.println(" (" + proc.mPid + ") sticky=" + isSticky);
synchronized (mInternal) {
synchronized (mInternal.mProcLock) {
- synchronized (mInternal.mOomAdjuster.mCachedAppOptimizer.mFreezerLock) {
- app.mOptRecord.setFreezeSticky(isSticky);
- mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(app, 0,
+ proc.mOptRecord.setFreezeSticky(isSticky);
+ if (freeze) {
+ mInternal.mOomAdjuster.mCachedAppOptimizer.forceFreezeAppAsyncLSP(proc);
+ } else {
+ mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(proc, 0,
true);
}
}
@@ -1250,43 +1233,42 @@
}
/**
- * Parses from the shell the process name and user id if provided and provides the corresponding
- * {@link ProcessRecord)} If no user is provided, it will fallback to current user.
- * Example usage: {@code <processname> --user current} or {@code <processname>}
- * @return process record of process, null if none found.
+ * Parses from the shell the pid or process name and provides the corresponding
+ * {@link ProcessRecord}.
+ * Example usage: {@code <processname>} or {@code <pid>}
+ * @return process record of process, null if none or more than one found.
* @throws RemoteException
*/
@NeverCompile
ProcessRecord getProcessFromShell() throws RemoteException {
- ProcessRecord app;
- String processName = getNextArgRequired();
- synchronized (mInternal.mProcLock) {
- // Default to current user
- int userId = getUserIdFromShellOrFallback();
- final int uid =
- mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId);
- app = mInternal.getProcessRecordLocked(processName, uid);
+ ProcessRecord proc = null;
+ String process = getNextArgRequired();
+ try {
+ int pid = Integer.parseInt(process);
+ synchronized (mInternal.mPidsSelfLocked) {
+ proc = mInternal.mPidsSelfLocked.get(pid);
+ }
+ } catch (NumberFormatException e) {
+ // Fallback to process name if it's not a valid pid
}
- return app;
- }
- /**
- * @return User id from command line provided in the form of
- * {@code --user <userid|current|all>} and if the argument is not found it will fallback
- * to current user.
- * @throws RemoteException
- */
- @NeverCompile
- int getUserIdFromShellOrFallback() throws RemoteException {
- int userId = mInterface.getCurrentUserId();
- String userOpt = getNextOption();
- if (userOpt != null && "--user".equals(userOpt)) {
- int inputUserId = UserHandle.parseUserArg(getNextArgRequired());
- if (inputUserId != UserHandle.USER_CURRENT) {
- userId = inputUserId;
+ if (proc == null) {
+ synchronized (mInternal.mProcLock) {
+ ArrayMap<String, SparseArray<ProcessRecord>> all =
+ mInternal.mProcessList.getProcessNamesLOSP().getMap();
+ SparseArray<ProcessRecord> procs = all.get(process);
+ if (procs == null || procs.size() == 0) {
+ getErrPrintWriter().println("Error: could not find process");
+ return null;
+ } else if (procs.size() > 1) {
+ getErrPrintWriter().println("Error: more than one processes found");
+ return null;
+ }
+ proc = procs.valueAt(0);
}
}
- return userId;
+
+ return proc;
}
int runDumpHeap(PrintWriter pw) throws RemoteException {
@@ -4306,24 +4288,26 @@
pw.println(" --allow-background-activity-starts: The receiver may start activities");
pw.println(" even if in the background.");
pw.println(" --async: Send without waiting for the completion of the receiver.");
- pw.println(" compact [some|full] <process_name> [--user <USER_ID>]");
- pw.println(" Perform a single process compaction.");
+ pw.println(" compact {some|full} <PROCESS>");
+ pw.println(" Perform a single process compaction. The given <PROCESS> argument");
+ pw.println(" may be either a process name or pid.");
pw.println(" some: execute file compaction.");
pw.println(" full: execute anon + file compaction.");
- pw.println(" system: system compaction.");
pw.println(" compact system");
pw.println(" Perform a full system compaction.");
- pw.println(" compact native [some|full] <pid>");
+ pw.println(" compact native {some|full} <pid>");
pw.println(" Perform a native compaction for process with <pid>.");
pw.println(" some: execute file compaction.");
pw.println(" full: execute anon + file compaction.");
- pw.println(" freeze [--sticky] <processname> [--user <USER_ID>]");
- pw.println(" Freeze a process.");
- pw.println(" --sticky: persists the frozen state for the process lifetime or");
+ pw.println(" freeze [--sticky] <PROCESS>");
+ pw.println(" Freeze a process. The given <PROCESS> argument");
+ pw.println(" may be either a process name or pid. Options are:");
+ pw.println(" --sticky: persists the frozen state for the process lifetime or");
pw.println(" until an unfreeze is triggered via shell");
- pw.println(" unfreeze [--sticky] <processname> [--user <USER_ID>]");
- pw.println(" Unfreeze a process.");
- pw.println(" --sticky: persists the unfrozen state for the process lifetime or");
+ pw.println(" unfreeze [--sticky] <PROCESS>");
+ pw.println(" Unfreeze a process. The given <PROCESS> argument");
+ pw.println(" may be either a process name or pid. Options are:");
+ pw.println(" --sticky: persists the unfrozen state for the process lifetime or");
pw.println(" until a freeze is triggered via shell");
pw.println(" instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]");
pw.println(" [--user <USER_ID> | current]");
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index cc6ae5c..8647750 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -3277,7 +3277,13 @@
}
final int curSchedGroup = state.getCurrentSchedulingGroup();
- if (state.getSetSchedGroup() != curSchedGroup) {
+ if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0
+ && ActivityManager.isProcStateBackground(state.getCurProcState())
+ && !state.hasStartedServices()) {
+ app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED,
+ ApplicationExitInfo.SUBREASON_REMOVE_TASK, true);
+ success = false;
+ } else if (state.getSetSchedGroup() != curSchedGroup) {
int oldSchedGroup = state.getSetSchedGroup();
state.setSetSchedGroup(curSchedGroup);
if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
@@ -3285,74 +3291,66 @@
+ " to " + curSchedGroup + ": " + state.getAdjType();
reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
}
- if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0
- && ActivityManager.isProcStateBackground(state.getSetProcState())
- && !state.hasStartedServices()) {
- app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED,
- ApplicationExitInfo.SUBREASON_REMOVE_TASK, true);
- success = false;
- } else {
- int processGroup;
- switch (curSchedGroup) {
- case SCHED_GROUP_BACKGROUND:
- processGroup = THREAD_GROUP_BACKGROUND;
- break;
- case SCHED_GROUP_TOP_APP:
- case SCHED_GROUP_TOP_APP_BOUND:
- processGroup = THREAD_GROUP_TOP_APP;
- break;
- case SCHED_GROUP_RESTRICTED:
- processGroup = THREAD_GROUP_RESTRICTED;
- break;
- default:
- processGroup = THREAD_GROUP_DEFAULT;
- break;
- }
- mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
- 0 /* unused */, app.getPid(), processGroup, app.processName));
- try {
- final int renderThreadTid = app.getRenderThreadTid();
- if (curSchedGroup == SCHED_GROUP_TOP_APP) {
- // do nothing if we already switched to RT
- if (oldSchedGroup != SCHED_GROUP_TOP_APP) {
- app.getWindowProcessController().onTopProcChanged();
- if (app.useFifoUiScheduling()) {
- // Switch UI pipeline for app to SCHED_FIFO
- state.setSavedPriority(Process.getThreadPriority(app.getPid()));
- ActivityManagerService.setFifoPriority(app, true /* enable */);
- } else {
- // Boost priority for top app UI and render threads
- setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
- if (renderThreadTid != 0) {
- try {
- setThreadPriority(renderThreadTid,
- THREAD_PRIORITY_TOP_APP_BOOST);
- } catch (IllegalArgumentException e) {
- // thread died, ignore
- }
+ int processGroup;
+ switch (curSchedGroup) {
+ case SCHED_GROUP_BACKGROUND:
+ processGroup = THREAD_GROUP_BACKGROUND;
+ break;
+ case SCHED_GROUP_TOP_APP:
+ case SCHED_GROUP_TOP_APP_BOUND:
+ processGroup = THREAD_GROUP_TOP_APP;
+ break;
+ case SCHED_GROUP_RESTRICTED:
+ processGroup = THREAD_GROUP_RESTRICTED;
+ break;
+ default:
+ processGroup = THREAD_GROUP_DEFAULT;
+ break;
+ }
+ mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
+ 0 /* unused */, app.getPid(), processGroup, app.processName));
+ try {
+ final int renderThreadTid = app.getRenderThreadTid();
+ if (curSchedGroup == SCHED_GROUP_TOP_APP) {
+ // do nothing if we already switched to RT
+ if (oldSchedGroup != SCHED_GROUP_TOP_APP) {
+ app.getWindowProcessController().onTopProcChanged();
+ if (app.useFifoUiScheduling()) {
+ // Switch UI pipeline for app to SCHED_FIFO
+ state.setSavedPriority(Process.getThreadPriority(app.getPid()));
+ ActivityManagerService.setFifoPriority(app, true /* enable */);
+ } else {
+ // Boost priority for top app UI and render threads
+ setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
+ if (renderThreadTid != 0) {
+ try {
+ setThreadPriority(renderThreadTid,
+ THREAD_PRIORITY_TOP_APP_BOOST);
+ } catch (IllegalArgumentException e) {
+ // thread died, ignore
}
}
}
- } else if (oldSchedGroup == SCHED_GROUP_TOP_APP
- && curSchedGroup != SCHED_GROUP_TOP_APP) {
- app.getWindowProcessController().onTopProcChanged();
- if (app.useFifoUiScheduling()) {
- // Reset UI pipeline to SCHED_OTHER
- ActivityManagerService.setFifoPriority(app, false /* enable */);
- setThreadPriority(app.getPid(), state.getSavedPriority());
- } else {
- // Reset priority for top app UI and render threads
- setThreadPriority(app.getPid(), 0);
- }
+ }
+ } else if (oldSchedGroup == SCHED_GROUP_TOP_APP
+ && curSchedGroup != SCHED_GROUP_TOP_APP) {
+ app.getWindowProcessController().onTopProcChanged();
+ if (app.useFifoUiScheduling()) {
+ // Reset UI pipeline to SCHED_OTHER
+ ActivityManagerService.setFifoPriority(app, false /* enable */);
+ setThreadPriority(app.getPid(), state.getSavedPriority());
+ } else {
+ // Reset priority for top app UI and render threads
+ setThreadPriority(app.getPid(), 0);
+ }
- if (renderThreadTid != 0) {
- setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY);
- }
+ if (renderThreadTid != 0) {
+ setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY);
}
- } catch (Exception e) {
- if (DEBUG_ALL) {
- Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e);
- }
+ }
+ } catch (Exception e) {
+ if (DEBUG_ALL) {
+ Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e);
}
}
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 9520621..827db57 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -20,6 +20,8 @@
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.Uri;
+import android.net.LocalSocketAddress;
+import android.net.LocalSocket;
import android.os.AsyncTask;
import android.os.Build;
import android.os.SystemProperties;
@@ -30,6 +32,14 @@
import com.android.internal.annotations.VisibleForTesting;
+import android.aconfigd.Aconfigd.StorageRequestMessage;
+import android.aconfigd.Aconfigd.StorageRequestMessages;
+import android.aconfigd.Aconfigd.StorageReturnMessage;
+import android.aconfigd.Aconfigd.StorageReturnMessages;
+import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
@@ -185,6 +195,7 @@
"pmw",
"power",
"preload_safety",
+ "printing",
"privacy_infra_policy",
"resource_manager",
"responsible_apis",
@@ -224,6 +235,8 @@
public static final String NAMESPACE_REBOOT_STAGING = "staged";
public static final String NAMESPACE_REBOOT_STAGING_DELIMITER = "*";
+ public static final String NAMESPACE_LOCAL_OVERRIDES = "device_config_overrides";
+
private final String[] mGlobalSettings;
private final String[] mDeviceConfigScopes;
@@ -329,6 +342,7 @@
HashMap<String, HashMap<String, String>> propsToStage =
getStagedFlagsWithValueChange(properties);
+ // send prop stage request to sys prop
for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) {
String actualNamespace = entry.getKey();
HashMap<String, String> flagValuesToStage = entry.getValue();
@@ -349,7 +363,118 @@
}
}
- });
+ // send prop stage request to new storage
+ if (enableAconfigStorageDaemon()) {
+ stageFlagsInNewStorage(propsToStage);
+ }
+
+ });
+
+ // add prop sync callback for flag local overrides
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_LOCAL_OVERRIDES,
+ AsyncTask.THREAD_POOL_EXECUTOR,
+ (DeviceConfig.Properties properties) -> {
+ if (enableAconfigStorageDaemon()) {
+ setLocalOverridesInNewStorage(properties);
+ }
+ });
+ }
+
+ /**
+ * apply flag local override in aconfig new storage
+ * @param props
+ * @return aconfigd socket return
+ */
+ public static StorageReturnMessages sendAconfigdRequests(StorageRequestMessages requests) {
+ // connect to aconfigd socket
+ LocalSocket client = new LocalSocket();
+ try{
+ client.connect(new LocalSocketAddress(
+ "aconfigd", LocalSocketAddress.Namespace.RESERVED));
+ log("connected to aconfigd socket");
+ } catch (IOException ioe) {
+ log("failed to connect to aconfigd socket", ioe);
+ return null;
+ }
+
+ DataInputStream inputStream = null;
+ DataOutputStream outputStream = null;
+ try {
+ inputStream = new DataInputStream(client.getInputStream());
+ outputStream = new DataOutputStream(client.getOutputStream());
+ } catch (IOException ioe) {
+ log("failed to get local socket iostreams", ioe);
+ return null;
+ }
+
+ // send requests
+ try {
+ byte[] requests_bytes = requests.toByteArray();
+ outputStream.writeInt(requests_bytes.length);
+ outputStream.write(requests_bytes, 0, requests_bytes.length);
+ log(requests.getMsgsCount() + " flag override requests sent to aconfigd");
+ } catch (IOException ioe) {
+ log("failed to send requests to aconfigd", ioe);
+ return null;
+ }
+
+ // read return
+ StorageReturnMessages return_msgs = null;
+ try {
+ int num_bytes = inputStream.readInt();
+ byte[] buffer = new byte[num_bytes];
+ inputStream.read(buffer, 0, num_bytes);
+ return_msgs = StorageReturnMessages.parseFrom(buffer);
+ log(return_msgs.getMsgsCount() + " acknowledgement received from aconfigd");
+ } catch (IOException ioe) {
+ log("failed to read requests return from aconfigd", ioe);
+ return null;
+ }
+
+ return return_msgs;
+ }
+
+ /**
+ * apply flag local override in aconfig new storage
+ * @param props
+ */
+ public static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) {
+ StorageRequestMessages.Builder requests_builder = StorageRequestMessages.newBuilder();
+ for (String flagName : props.getKeyset()) {
+ String flagValue = props.getString(flagName, null);
+ if (flagName == null || flagValue == null) {
+ continue;
+ }
+
+ int idx = flagName.indexOf(":");
+ if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
+ log("invalid local flag override: " + flagName);
+ continue;
+ }
+ String actualNamespace = flagName.substring(0, idx);
+ String fullFlagName = flagName.substring(idx+1);
+ idx = fullFlagName.lastIndexOf(".");
+ if (idx == -1) {
+ log("invalid flag name: " + fullFlagName);
+ continue;
+ }
+ String packageName = fullFlagName.substring(0, idx);
+ String realFlagName = fullFlagName.substring(idx+1);
+
+ StorageRequestMessage.FlagOverrideMessage.Builder override_msg_builder =
+ StorageRequestMessage.FlagOverrideMessage.newBuilder();
+ override_msg_builder.setPackageName(packageName);
+ override_msg_builder.setFlagName(realFlagName);
+ override_msg_builder.setFlagValue(flagValue);
+ override_msg_builder.setIsLocal(true);
+
+ StorageRequestMessage.Builder request_builder = StorageRequestMessage.newBuilder();
+ request_builder.setFlagOverrideMessage(override_msg_builder.build());
+ requests_builder.addMsgs(request_builder.build());
+ }
+ StorageRequestMessages requests = requests_builder.build();
+ StorageReturnMessages acks = sendAconfigdRequests(requests);
}
public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
@@ -421,6 +546,43 @@
}
/**
+ * stage flags in aconfig new storage
+ * @param propsToStage
+ */
+ @VisibleForTesting
+ static void stageFlagsInNewStorage(HashMap<String, HashMap<String, String>> propsToStage) {
+ // create storage request proto
+ StorageRequestMessages.Builder requests_builder = StorageRequestMessages.newBuilder();
+ for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) {
+ String actualNamespace = entry.getKey();
+ HashMap<String, String> flagValuesToStage = entry.getValue();
+ for (String fullFlagName : flagValuesToStage.keySet()) {
+ String stagedValue = flagValuesToStage.get(fullFlagName);
+ int idx = fullFlagName.lastIndexOf(".");
+ if (idx == -1) {
+ log("invalid flag name: " + fullFlagName);
+ continue;
+ }
+ String packageName = fullFlagName.substring(0, idx);
+ String flagName = fullFlagName.substring(idx+1);
+
+ StorageRequestMessage.FlagOverrideMessage.Builder override_msg_builder =
+ StorageRequestMessage.FlagOverrideMessage.newBuilder();
+ override_msg_builder.setPackageName(packageName);
+ override_msg_builder.setFlagName(flagName);
+ override_msg_builder.setFlagValue(stagedValue);
+ override_msg_builder.setIsLocal(false);
+
+ StorageRequestMessage.Builder request_builder = StorageRequestMessage.newBuilder();
+ request_builder.setFlagOverrideMessage(override_msg_builder.build());
+ requests_builder.addMsgs(request_builder.build());
+ }
+ }
+ StorageRequestMessages requests = requests_builder.build();
+ StorageReturnMessages acks = sendAconfigdRequests(requests);
+ }
+
+ /**
* system property name constructing rule for aconfig flags:
* "persist.device_config.aconfig_flags.[category_name].[flag_name]".
* If the name contains invalid characters or substrings for system property name,
@@ -483,10 +645,10 @@
for (String flagName : flagStagedValues.keySet()) {
String stagedValue = flagStagedValues.get(flagName);
String currentValue = flagCurrentValues.get(flagName);
- if (currentValue == null) {
- currentValue = new String("false");
+ if (stagedValue == null) {
+ continue;
}
- if (stagedValue != null && !stagedValue.equalsIgnoreCase(currentValue)) {
+ if (currentValue == null || !stagedValue.equalsIgnoreCase(currentValue)) {
flagsToStage.put(flagName, stagedValue);
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 6308652..1db3483 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1255,7 +1255,9 @@
for (int uidIdx = mUidStates.size() - 1; uidIdx >= 0; uidIdx--) {
int uid = mUidStates.keyAt(uidIdx);
if (knownUids.get(uid, false)) {
- if (uid >= Process.FIRST_APPLICATION_UID) {
+ int appId = UserHandle.getAppId(uid);
+ if (appId >= Process.FIRST_APPLICATION_UID
+ && appId <= Process.LAST_APPLICATION_UID) {
ArrayMap<String, Ops> pkgOps = mUidStates.valueAt(uidIdx).pkgOps;
for (int pkgIdx = pkgOps.size() - 1; pkgIdx >= 0; pkgIdx--) {
String pkgName = pkgOps.keyAt(pkgIdx);
@@ -2910,10 +2912,12 @@
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
- final int proxiedUid = attributionSource.getNextUid();
final int proxyVirtualDeviceId = attributionSource.getDeviceId();
+
+ final int proxiedUid = attributionSource.getNextUid();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+ final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
@@ -2950,7 +2954,8 @@
final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
- Process.INVALID_UID, null, null, proxyFlags, !isProxyTrusted,
+ Process.INVALID_UID, null, null,
+ Context.DEVICE_ID_DEFAULT, proxyFlags, !isProxyTrusted,
"proxy " + message, shouldCollectMessage);
if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
@@ -2968,9 +2973,9 @@
final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
- proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolveProxyPackageName,
- proxyAttributionTag, proxiedFlags, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage);
+ proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolveProxyPackageName,
+ proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, shouldCollectAsyncNotedOp,
+ message, shouldCollectMessage);
}
@Override
@@ -3021,14 +3026,14 @@
}
return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
virtualDeviceId, Process.INVALID_UID, null, null,
- AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage);
+ Context.DEVICE_ID_DEFAULT, AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp,
+ message, shouldCollectMessage);
}
private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
@Nullable String attributionTag, int virtualDeviceId, int proxyUid,
- String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
- boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ String proxyPackageName, @Nullable String proxyAttributionTag, int proxyVirtualDeviceId,
+ @OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message,
boolean shouldCollectMessage) {
PackageVerificationResult pvr;
try {
@@ -3159,8 +3164,9 @@
}
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED);
+
attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
- uidState.getState(), flags);
+ getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags);
if (shouldCollectAsyncNotedOp) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
@@ -3526,9 +3532,9 @@
}
return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
- virtualDeviceId, Process.INVALID_UID, null, null, OP_FLAG_SELF,
- startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- attributionFlags, attributionChainId);
+ virtualDeviceId, Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT,
+ OP_FLAG_SELF, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, attributionFlags, attributionChainId);
}
/** @deprecated Use {@link #startProxyOperationWithState} instead. */
@@ -3566,18 +3572,32 @@
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
- final int proxiedUid = attributionSource.getNextUid();
final int proxyVirtualDeviceId = attributionSource.getDeviceId();
+
+ final int proxiedUid = attributionSource.getNextUid();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+ final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) {
- Slog.w(TAG, "startProxyOperationImpl returned MODE_IGNORED as virtualDeviceId "
- + proxyVirtualDeviceId + " is invalid");
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
- proxiedPackageName);
+ Slog.w(
+ TAG,
+ "startProxyOperationImpl returned MODE_IGNORED as proxyVirtualDeviceId "
+ + proxyVirtualDeviceId
+ + " is invalid");
+ return new SyncNotedAppOp(
+ AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName);
+ }
+ if (!isValidVirtualDeviceId(proxiedVirtualDeviceId)) {
+ Slog.w(
+ TAG,
+ "startProxyOperationImpl returned MODE_IGNORED as proxiedVirtualDeviceId "
+ + proxiedVirtualDeviceId
+ + " is invalid");
+ return new SyncNotedAppOp(
+ AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName);
}
if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))
|| !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) {
@@ -3619,7 +3639,7 @@
// Test if the proxied operation will succeed before starting the proxy operation
final SyncNotedAppOp testProxiedOp = startOperationDryRun(code,
proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag,
- proxyVirtualDeviceId, resolvedProxyPackageName, proxiedFlags,
+ proxiedVirtualDeviceId, resolvedProxyPackageName, proxiedFlags,
startIfModeDefault);
if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
@@ -3631,7 +3651,7 @@
final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
- Process.INVALID_UID, null, null, proxyFlags,
+ Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT, proxyFlags,
startIfModeDefault, !isProxyTrusted, "proxy " + message,
shouldCollectMessage, proxyAttributionFlags, attributionChainId);
if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
@@ -3640,9 +3660,10 @@
}
return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
- proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolvedProxyPackageName,
- proxyAttributionTag, proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp,
- message, shouldCollectMessage, proxiedAttributionFlags, attributionChainId);
+ proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolvedProxyPackageName,
+ proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, proxiedAttributionFlags,
+ attributionChainId);
}
private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
@@ -3652,9 +3673,10 @@
private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
@NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId,
int proxyUid, String proxyPackageName, @Nullable String proxyAttributionTag,
- @OpFlags int flags, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
- @Nullable String message, boolean shouldCollectMessage,
- @AttributionFlags int attributionFlags, int attributionChainId) {
+ int proxyVirtualDeviceId, @OpFlags int flags, boolean startIfModeDefault,
+ boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+ int attributionChainId) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3749,13 +3771,13 @@
+ " flags: " + AppOpsManager.flagsToString(flags));
try {
if (isRestricted) {
- attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
- attributionFlags, attributionChainId);
+ attributedOp.createPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName,
+ proxyAttributionTag, getPersistentId(proxyVirtualDeviceId),
+ uidState.getState(), flags, attributionFlags, attributionChainId);
} else {
- attributedOp.started(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
- attributionFlags, attributionChainId);
+ attributedOp.started(clientId, virtualDeviceId, proxyUid, proxyPackageName,
+ proxyAttributionTag, getPersistentId(proxyVirtualDeviceId),
+ uidState.getState(), flags, attributionFlags, attributionChainId);
startType = START_TYPE_STARTED;
}
} catch (RemoteException e) {
@@ -4944,7 +4966,7 @@
if (accessTime > 0) {
attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
- proxyAttributionTag, uidState, opFlags);
+ proxyAttributionTag, PERSISTENT_DEVICE_ID_DEFAULT, uidState, opFlags);
}
if (rejectTime > 0) {
attributedOp.rejected(rejectTime, uidState, opFlags);
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 2285826..2760ccf 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -24,7 +24,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
-import android.companion.virtual.VirtualDeviceManager;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
@@ -95,16 +94,17 @@
*
* @param proxyUid The uid of the proxy
* @param proxyPackageName The package name of the proxy
- * @param proxyAttributionTag the attributionTag in the proxies package
+ * @param proxyAttributionTag The attributionTag in the proxies package
+ * @param proxyDeviceId The device Id of the proxy
* @param uidState UID state of the app noteOp/startOp was called for
* @param flags OpFlags of the call
*/
public void accessed(int proxyUid, @Nullable String proxyPackageName,
- @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
- @AppOpsManager.OpFlags int flags) {
+ @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
+ @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
long accessTime = System.currentTimeMillis();
- accessed(accessTime, -1, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState, flags);
+ accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId,
+ uidState, flags);
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, tag, uidState, flags, accessTime,
@@ -118,14 +118,16 @@
* @param duration The duration of the event
* @param proxyUid The uid of the proxy
* @param proxyPackageName The package name of the proxy
- * @param proxyAttributionTag the attributionTag in the proxies package
+ * @param proxyAttributionTag The attributionTag in the proxies package
+ * @param proxyDeviceId The device Id of the proxy
* @param uidState UID state of the app noteOp/startOp was called for
* @param flags OpFlags of the call
*/
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
public void accessed(long noteTime, long duration, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
+ @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags) {
long key = makeKey(uidState, flags);
if (mAccessEvents == null) {
@@ -135,7 +137,7 @@
AppOpsManager.OpEventProxyInfo proxyInfo = null;
if (proxyUid != Process.INVALID_UID) {
proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
- proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ proxyAttributionTag, proxyDeviceId);
}
AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
@@ -189,35 +191,36 @@
* Update state when start was called
*
* @param clientId Id of the startOp caller
+ * @param virtualDeviceId The virtual device id of the startOp caller
* @param proxyUid The UID of the proxy app
* @param proxyPackageName The package name of the proxy app
* @param proxyAttributionTag The attribution tag of the proxy app
+ * @param proxyDeviceId The device id of the proxy app
* @param uidState UID state of the app startOp is called for
* @param flags The proxy flags
* @param attributionFlags The attribution flags associated with this operation.
- * @param attributionChainId The if of the attribution chain this operations is a part of.
+ * @param attributionChainId The if of the attribution chain this operations is a part of
*/
- public void started(@NonNull IBinder clientId, int proxyUid,
+ public void started(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
+ @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
@AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
int attributionChainId) throws RemoteException {
- startedOrPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, proxyVirtualDeviceId, uidState, flags,
- /* triggeredByUidStateChange */ false, /* isStarted */ true, attributionFlags,
- attributionChainId);
+ startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag,
+ proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false,
+ true);
}
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
- private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
+ private void startedOrPaused(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
- @AppOpsManager.OpFlags int flags, boolean triggeredByUidStateChange,
- boolean isStarted, @AppOpsManager.AttributionFlags int attributionFlags,
- int attributionChainId) throws RemoteException {
+ @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId, boolean triggeredByUidStateChange, boolean isStarted)
+ throws RemoteException {
if (!triggeredByUidStateChange && !parent.isRunning() && isStarted) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, tag, proxyVirtualDeviceId, true, attributionFlags,
+ parent.packageName, tag, virtualDeviceId, true, attributionFlags,
attributionChainId);
}
@@ -233,9 +236,9 @@
InProgressStartOpEvent event = events.get(clientId);
if (event == null) {
event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
- SystemClock.elapsedRealtime(), clientId, tag, proxyVirtualDeviceId,
+ SystemClock.elapsedRealtime(), clientId, tag, virtualDeviceId,
PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
- proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
+ proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId, uidState, flags,
attributionFlags, attributionChainId);
events.put(clientId, event);
} else {
@@ -366,15 +369,14 @@
/**
* Create an event that will be started, if the op is unpaused.
*/
- public void createPaused(@NonNull IBinder clientId, int proxyUid,
- @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
- @AppOpsManager.OpFlags int flags,
- @AppOpsManager.AttributionFlags int attributionFlags,
+ public void createPaused(@NonNull IBinder clientId, int virtualDeviceId,
+ int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+ @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
int attributionChainId) throws RemoteException {
- startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
- proxyVirtualDeviceId, uidState, flags, false, false,
- attributionFlags, attributionChainId);
+ startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag,
+ proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false,
+ false);
}
/**
@@ -496,16 +498,16 @@
// Call started() to add a new start event object and then add the
// previously removed unfinished start counts back
if (proxy != null) {
- startedOrPaused(event.getClientId(), proxy.getUid(),
- proxy.getPackageName(), proxy.getAttributionTag(),
- event.getVirtualDeviceId(), newState, event.getFlags(),
- true, isRunning,
- event.getAttributionFlags(), event.getAttributionChainId());
+ startedOrPaused(event.getClientId(), event.getVirtualDeviceId(),
+ proxy.getUid(), proxy.getPackageName(), proxy.getAttributionTag(),
+ proxy.getDeviceId(), newState, event.getFlags(),
+ event.getAttributionFlags(), event.getAttributionChainId(), true,
+ isRunning);
} else {
- startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null,
- event.getVirtualDeviceId(), newState, event.getFlags(), true,
- isRunning, event.getAttributionFlags(),
- event.getAttributionChainId());
+ startedOrPaused(event.getClientId(), event.getVirtualDeviceId(),
+ Process.INVALID_UID, null, null, null,
+ newState, event.getFlags(), event.getAttributionFlags(),
+ event.getAttributionChainId(), true, isRunning);
}
events = isRunning ? mInProgressEvents : mPausedInProgressEvents;
@@ -847,7 +849,8 @@
InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
@Nullable String attributionTag, int virtualDeviceId, @NonNull Runnable onDeath,
int proxyUid, @Nullable String proxyPackageName,
- @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
+ @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
+ @AppOpsManager.UidState int uidState,
@AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags
int attributionFlags, int attributionChainId) throws RemoteException {
@@ -856,7 +859,7 @@
AppOpsManager.OpEventProxyInfo proxyInfo = null;
if (proxyUid != Process.INVALID_UID) {
proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
- proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ proxyAttributionTag, proxyDeviceId);
}
if (recycled != null) {
@@ -880,7 +883,8 @@
super(maxUnusedPooledObjects);
}
- AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid,
+ AppOpsManager.OpEventProxyInfo acquire(
+ @IntRange(from = 0) int uid,
@Nullable String packageName,
@Nullable String attributionTag,
@Nullable String deviceId) {
@@ -890,7 +894,7 @@
return recycled;
}
- return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag);
+ return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag, deviceId);
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index add8491..c310822 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9778,9 +9778,9 @@
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.DOCK_AUDIO_MEDIA_ENABLED), false, this);
mContentResolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.MASTER_MONO), false, this);
+ Settings.System.MASTER_MONO), false, this, UserHandle.USER_ALL);
mContentResolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.MASTER_BALANCE), false, this);
+ Settings.System.MASTER_BALANCE), false, this, UserHandle.USER_ALL);
mEncodedSurroundMode = mSettings.getGlobalInt(
mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT,
diff --git a/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java
index 3e8acee..7cf2d30 100644
--- a/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java
+++ b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
@@ -63,7 +64,7 @@
intentFilter.addAction(ACTION_FACE_RE_ENROLL_LAUNCH);
intentFilter.addAction(ACTION_FACE_RE_ENROLL_DISMISS);
}
- context.registerReceiver(this, intentFilter);
+ context.registerReceiver(this, intentFilter, Context.RECEIVER_NOT_EXPORTED);
}
@Override
@@ -84,7 +85,8 @@
}
private void launchBiometricEnrollActivity(Context context, String action) {
- context.sendBroadcast(new Intent(ACTION_CLOSE_SYSTEM_DIALOGS));
+ context.sendBroadcast(
+ new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND));
final Intent intent = new Intent(action);
intent.setPackage(SETTINGS_PACKAGE);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 645a366..390ee96 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -37,6 +37,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
+import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.hardware.CameraExtensionSessionStats;
import android.hardware.CameraSessionStats;
@@ -906,6 +907,7 @@
int extensionType = FrameworkStatsLog.CAMERA_ACTION_EVENT__EXT_TYPE__EXTENSION_NONE;
boolean extensionIsAdvanced = false;
+ int extensionCaptureFormat = ImageFormat.UNKNOWN;
if (e.mExtSessionStats != null) {
switch (e.mExtSessionStats.type) {
case CameraExtensionSessionStats.Type.EXTENSION_AUTOMATIC:
@@ -932,6 +934,9 @@
Slog.w(TAG, "Unknown extension type: " + e.mExtSessionStats.type);
}
extensionIsAdvanced = e.mExtSessionStats.isAdvanced;
+ if (Flags.analytics24q3()) {
+ extensionCaptureFormat = e.mExtSessionStats.captureFormat;
+ }
}
int streamCount = 0;
@@ -945,10 +950,13 @@
String zoomOverrideDebug = Flags.logZoomOverrideUsage()
? ", zoomOverrideUsage " + e.mUsedZoomOverride
: "";
-
String mostRequestedFpsRangeDebug = Flags.analytics24q3()
? ", mostRequestedFpsRange " + e.mMostRequestedFpsRange
: "";
+ String extensionCaptureFormatDebug = Flags.analytics24q3()
+ ? " extensionCaptureFormat " + e.mExtSessionStats.captureFormat
+ : "";
+
Slog.v(TAG, "CAMERA_ACTION_EVENT: action " + e.mAction
+ " clientName " + e.mClientName
+ ", duration " + e.getDuration()
@@ -971,8 +979,10 @@
+ ", logId " + e.mLogId
+ ", sessionIndex " + e.mSessionIndex
+ ", mExtSessionStats {type " + extensionType
- + " isAdvanced " + extensionIsAdvanced + "}");
+ + " isAdvanced " + extensionIsAdvanced
+ + extensionCaptureFormatDebug + "}");
}
+
// Convert from CameraStreamStats to CameraStreamProto
CameraStreamProto[] streamProtos = new CameraStreamProto[MAX_STREAM_STATISTICS];
for (int i = 0; i < MAX_STREAM_STATISTICS; i++) {
@@ -1035,7 +1045,8 @@
e.mLogId, e.mSessionIndex,
extensionType, extensionIsAdvanced, e.mUsedUltraWide,
e.mUsedZoomOverride,
- e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper());
+ e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper(),
+ extensionCaptureFormat);
}
}
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index 22f3bbd..fa8299b 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -29,7 +29,7 @@
*/
public class DisplayControl {
private static native IBinder nativeCreateDisplay(String name, boolean secure,
- float requestedRefreshRate);
+ String uniqueId, float requestedRefreshRate);
private static native void nativeDestroyDisplay(IBinder displayToken);
private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
private static native long[] nativeGetPhysicalDisplayIds();
@@ -43,20 +43,21 @@
/**
* Create a display in SurfaceFlinger.
*
- * @param name The name of the display
+ * @param name The name of the display.
* @param secure Whether this display is secure.
* @return The token reference for the display in SurfaceFlinger.
*/
public static IBinder createDisplay(String name, boolean secure) {
Objects.requireNonNull(name, "name must not be null");
- return nativeCreateDisplay(name, secure, 0.0f);
+ return nativeCreateDisplay(name, secure, "", 0.0f);
}
/**
* Create a display in SurfaceFlinger.
*
- * @param name The name of the display
+ * @param name The name of the display.
* @param secure Whether this display is secure.
+ * @param uniqueId The unique ID for the display.
* @param requestedRefreshRate The requested refresh rate in frames per second.
* For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
* 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded
@@ -65,9 +66,10 @@
* @return The token reference for the display in SurfaceFlinger.
*/
public static IBinder createDisplay(String name, boolean secure,
- float requestedRefreshRate) {
+ String uniqueId, float requestedRefreshRate) {
Objects.requireNonNull(name, "name must not be null");
- return nativeCreateDisplay(name, secure, requestedRefreshRate);
+ Objects.requireNonNull(uniqueId, "uniqueId must not be null");
+ return nativeCreateDisplay(name, secure, uniqueId, requestedRefreshRate);
}
/**
@@ -79,7 +81,6 @@
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
}
-
nativeDestroyDisplay(displayToken);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d7a7dd4..70a1014 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1442,6 +1442,9 @@
// If there's an offload session, we need to set the initial doze brightness before
// the offload session starts controlling the brightness.
+ // During the transition DOZE_SUSPEND -> DOZE -> DOZE_SUSPEND, this brightness strategy
+ // will be selected again, meaning that no new brightness will be sent to the hardware and
+ // the display will stay at the brightness level set by the offload session.
if (Float.isNaN(brightnessState) && mFlags.isDisplayOffloadEnabled()
&& Display.isDozeState(state) && mDisplayOffloadSession != null) {
if (mAutomaticBrightnessController != null
@@ -1459,6 +1462,15 @@
if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) {
brightnessState = clampScreenBrightness(rawBrightnessState);
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL);
+
+ if (mAutomaticBrightnessController != null
+ && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
+ // Keep the brightness in the setting so that we can use it after the screen
+ // turns on, until a lux sample becomes available. We don't do this when
+ // auto-brightness is disabled - in that situation we still want to use
+ // the last brightness from when the screen was on.
+ updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index bcdb442..a29e852 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -92,8 +92,9 @@
Context context, Handler handler, Listener listener, DisplayManagerFlags featureFlags) {
this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() {
@Override
- public IBinder createDisplay(String name, boolean secure, float requestedRefreshRate) {
- return DisplayControl.createDisplay(name, secure, requestedRefreshRate);
+ public IBinder createDisplay(String name, boolean secure, String uniqueId,
+ float requestedRefreshRate) {
+ return DisplayControl.createDisplay(name, secure, uniqueId, requestedRefreshRate);
}
@Override
@@ -126,7 +127,7 @@
String name = virtualDisplayConfig.getName();
boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
- IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure,
+ IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure, uniqueId,
virtualDisplayConfig.getRequestedRefreshRate());
MediaProjectionCallback mediaProjectionCallback = null;
if (projection != null) {
@@ -653,8 +654,9 @@
/**
* Create a virtual display in SurfaceFlinger.
*
- * @param name The name of the display
+ * @param name The name of the display.
* @param secure Whether this display is secure.
+ * @param uniqueId The unique ID for the display.
* @param requestedRefreshRate
* The refresh rate, frames per second, to request on the virtual display.
* It should be a divisor of refresh rate of the leader physical display
@@ -663,8 +665,9 @@
* the refresh rate of the leader physical display.
* @return The token reference for the display in SurfaceFlinger.
*/
- IBinder createDisplay(String name, boolean secure, float requestedRefreshRate);
-
+ IBinder createDisplay(String name, boolean secure, String uniqueId,
+ float requestedRefreshRate);
+
/**
* Destroy a display in SurfaceFlinger.
*
diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java
index 2703a2c0..7e18d84 100644
--- a/services/core/java/com/android/server/hdmi/SendKeyAction.java
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -158,9 +158,11 @@
mTargetAddress, cecKeycodeAndParams), new SendMessageCallback() {
@Override
public void onSendCompleted(int error) {
- if (error != SendMessageResult.SUCCESS) {
+ // Disable System Audio Mode, if the AVR doesn't acknowledge
+ // a <User Control Pressed> message.
+ if (error == SendMessageResult.NACK) {
HdmiLogger.debug(
- "AVR did not respond to <User Control Pressed>");
+ "AVR did not acknowledge <User Control Pressed>");
localDevice().mService.setSystemAudioActivated(false);
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index cbd309e..8317991 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -156,7 +156,6 @@
private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
private static final int MSG_RELOAD_DEVICE_ALIASES = 2;
private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3;
- private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 4;
private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
private static final AdditionalDisplayInputProperties
@@ -255,7 +254,7 @@
// to {DisplayInfo#uniqueId} (String) so that events from the Input Device go to a
// specific display.
@GuardedBy("mAssociationsLock")
- private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>();
+ private final Map<String, String> mUniqueIdAssociationsByPort = new ArrayMap<>();
// The associations of input devices to displays by descriptor. Maps from
// {InputDevice#mDescriptor} to {DisplayInfo#uniqueId} (String) so that events from the
@@ -1324,11 +1323,6 @@
properties -> properties.pointerIconVisible = visible);
}
- private void handlePointerDisplayIdChanged(PointerDisplayIdChangedArgs args) {
- mWindowManagerCallbacks.notifyPointerDisplayIdChanged(
- args.mPointerDisplayId, args.mXPosition, args.mYPosition);
- }
-
private void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) {
mNative.setDisplayEligibilityForPointerCapture(displayId, isEligible);
}
@@ -1612,18 +1606,6 @@
// Binder call
@Override
- public void setPointerIconType(int iconType) {
- // TODO(b/311416205): Remove.
- }
-
- // Binder call
- @Override
- public void setCustomPointerIcon(PointerIcon icon) {
- // TODO(b/311416205): Remove.
- }
-
- // Binder call
- @Override
public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId,
IBinder inputToken) {
Objects.requireNonNull(icon);
@@ -1674,7 +1656,8 @@
}
@Override // Binder call
- public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
+ public void addUniqueIdAssociationByPort(@NonNull String inputPort,
+ @NonNull String displayUniqueId) {
if (!checkCallingPermission(
android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
"addUniqueIdAssociation()")) {
@@ -1685,13 +1668,13 @@
Objects.requireNonNull(inputPort);
Objects.requireNonNull(displayUniqueId);
synchronized (mAssociationsLock) {
- mUniqueIdAssociations.put(inputPort, displayUniqueId);
+ mUniqueIdAssociationsByPort.put(inputPort, displayUniqueId);
}
mNative.changeUniqueIdAssociation();
}
@Override // Binder call
- public void removeUniqueIdAssociation(@NonNull String inputPort) {
+ public void removeUniqueIdAssociationByPort(@NonNull String inputPort) {
if (!checkCallingPermission(
android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
"removeUniqueIdAssociation()")) {
@@ -1700,7 +1683,7 @@
Objects.requireNonNull(inputPort);
synchronized (mAssociationsLock) {
- mUniqueIdAssociations.remove(inputPort);
+ mUniqueIdAssociationsByPort.remove(inputPort);
}
mNative.changeUniqueIdAssociation();
}
@@ -2119,9 +2102,9 @@
pw.println(" display: " + v);
});
}
- if (!mUniqueIdAssociations.isEmpty()) {
+ if (!mUniqueIdAssociationsByPort.isEmpty()) {
pw.println("Unique Id Associations:");
- mUniqueIdAssociations.forEach((k, v) -> {
+ mUniqueIdAssociationsByPort.forEach((k, v) -> {
pw.print(" port: " + k);
pw.println(" uniqueId: " + v);
});
@@ -2548,10 +2531,10 @@
// Native callback
@SuppressWarnings("unused")
- private String[] getInputUniqueIdAssociations() {
+ private String[] getInputUniqueIdAssociationsByPort() {
final Map<String, String> associations;
synchronized (mAssociationsLock) {
- associations = new HashMap<>(mUniqueIdAssociations);
+ associations = new HashMap<>(mUniqueIdAssociationsByPort);
}
return flatten(associations);
@@ -2703,9 +2686,7 @@
@SuppressWarnings("unused")
@VisibleForTesting
void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) {
- mHandler.obtainMessage(MSG_POINTER_DISPLAY_ID_CHANGED,
- new PointerDisplayIdChangedArgs(pointerDisplayId, xPosition,
- yPosition)).sendToTarget();
+ // TODO(b/311416205): Remove.
}
@Override
@@ -2860,14 +2841,6 @@
*/
@Nullable
SurfaceControl createSurfaceForGestureMonitor(String name, int displayId);
-
- /**
- * Notify WindowManagerService when the display of the mouse pointer changes.
- * @param displayId The display on which the mouse pointer is shown.
- * @param x The x coordinate of the mouse pointer.
- * @param y The y coordinate of the mouse pointer.
- */
- void notifyPointerDisplayIdChanged(int displayId, float x, float y);
}
/**
@@ -2911,9 +2884,6 @@
boolean inTabletMode = (boolean) args.arg1;
deliverTabletModeChanged(whenNanos, inTabletMode);
break;
- case MSG_POINTER_DISPLAY_ID_CHANGED:
- handlePointerDisplayIdChanged((PointerDisplayIdChangedArgs) msg.obj);
- break;
}
}
}
diff --git a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
index 6eae9a4..d7d57df 100644
--- a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
+++ b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
@@ -240,7 +240,8 @@
return;
}
- post(() -> handleRotaryInput(MotionEvent.obtain((MotionEvent) event)));
+ MotionEvent motionEvent = MotionEvent.obtain(event);
+ post(() -> handleRotaryInput(motionEvent));
}
private void handleKeyEvent(KeyEvent keyEvent) {
diff --git a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
index 035a748..00bc751 100644
--- a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
+++ b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
@@ -40,6 +40,13 @@
@NonNull private final InputMethodManagerService mService;
+ /**
+ * The host input token of the input method that is currently associated with this controller.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ private IBinder mCurHostInputToken;
+
private static final class CreateInlineSuggestionsRequest {
@NonNull final InlineSuggestionsRequestInfo mRequestInfo;
@NonNull final IInlineSuggestionsRequestCallback mCallback;
@@ -78,6 +85,17 @@
}
@GuardedBy("ImfLock.class")
+ void onResetSystemUi() {
+ mCurHostInputToken = null;
+ }
+
+ @Nullable
+ @GuardedBy("ImfLock.class")
+ IBinder getCurHostInputToken() {
+ return mCurHostInputToken;
+ }
+
+ @GuardedBy("ImfLock.class")
void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback,
boolean touchExplorationEnabled) {
@@ -124,8 +142,7 @@
mPendingInlineSuggestionsRequest.mCallback,
mPendingInlineSuggestionsRequest.mPackageName,
mService.getCurTokenDisplayIdLocked(),
- mService.getCurTokenLocked(),
- mService);
+ mService.getCurTokenLocked());
curMethod.onCreateInlineSuggestionsRequest(
mPendingInlineSuggestionsRequest.mRequestInfo, callback);
} else {
@@ -161,22 +178,20 @@
* The decorator which validates the host package name in the
* {@link InlineSuggestionsRequest} argument to make sure it matches the IME package name.
*/
- private static final class InlineSuggestionsRequestCallbackDecorator
+ private final class InlineSuggestionsRequestCallbackDecorator
extends IInlineSuggestionsRequestCallback.Stub {
@NonNull private final IInlineSuggestionsRequestCallback mCallback;
@NonNull private final String mImePackageName;
private final int mImeDisplayId;
@NonNull private final IBinder mImeToken;
- @NonNull private final InputMethodManagerService mImms;
InlineSuggestionsRequestCallbackDecorator(
@NonNull IInlineSuggestionsRequestCallback callback, @NonNull String imePackageName,
- int displayId, @NonNull IBinder imeToken, @NonNull InputMethodManagerService imms) {
+ int displayId, @NonNull IBinder imeToken) {
mCallback = callback;
mImePackageName = imePackageName;
mImeDisplayId = displayId;
mImeToken = imeToken;
- mImms = imms;
}
@Override
@@ -195,7 +210,12 @@
+ "].");
}
request.setHostDisplayId(mImeDisplayId);
- mImms.setCurHostInputToken(mImeToken, request.getHostInputToken());
+ synchronized (ImfLock.class) {
+ final IBinder curImeToken = mService.getCurTokenLocked();
+ if (mImeToken == curImeToken) {
+ mCurHostInputToken = request.getHostInputToken();
+ }
+ }
mCallback.onInlineSuggestionsRequest(request, callback);
}
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 7956e03..79f1a9c 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -330,14 +330,10 @@
mHandwritingSurface.startIntercepting(imePid, imeUid);
// Unset the pointer icon for the stylus in case the app had set it.
- if (com.android.input.flags.Flags.enablePointerChoreographer()) {
- Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon(
- PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED),
- downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0),
- mHandwritingSurface.getInputChannel().getToken());
- } else {
- InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
- }
+ Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon(
+ PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED),
+ downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0),
+ mHandwritingSurface.getInputChannel().getToken());
return new HandwritingSession(mCurrentRequestId, mHandwritingSurface.getInputChannel(),
mHandwritingBuffer);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 1d048cb..e8543f2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -150,10 +150,11 @@
*
* @param sourceInputToken the source token.
* @param displayId the display hosting the IME window
+ * @param userId the user ID this request is about
* @return {@code true} if the transfer is successful
*/
public abstract boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
- int displayId);
+ int displayId, @UserIdInt int userId);
/**
* Reports that IME control has transferred to the given window token, or if null that
@@ -287,7 +288,7 @@
@Override
public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
- int displayId) {
+ int displayId, @UserIdInt int userId) {
return false;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 0fde760..848f74e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -495,16 +495,6 @@
return userData.mBindingController.getSequenceNumber();
}
- /**
- * Increase the current binding sequence number by one.
- * Reset to 1 on overflow.
- */
- @GuardedBy("ImfLock.class")
- private void advanceSequenceNumberLocked() {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- userData.mBindingController.advanceSequenceNumber();
- }
-
@GuardedBy("ImfLock.class")
@Nullable
InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) {
@@ -583,16 +573,6 @@
private final WeakHashMap<IBinder, Boolean> mFocusedWindowPerceptible = new WeakHashMap<>();
/**
- * Set to true if our ServiceConnection is currently actively bound to
- * a service (whether or not we have gotten its IBinder back yet).
- */
- @GuardedBy("ImfLock.class")
- private boolean hasConnectionLocked() {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- return userData.mBindingController.hasMainConnection();
- }
-
- /**
* The token tracking the current IME show request that is waiting for a connection to an IME,
* otherwise {@code null}.
*/
@@ -645,14 +625,6 @@
private int mCurTokenDisplayId = INVALID_DISPLAY;
/**
- * The host input token of the current active input method.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- @MultiUserUnawareField
- private IBinder mCurHostInputToken;
-
- /**
* The display ID of the input method indicates the fallback display which returned by
* {@link #computeImeDisplayIdForTarget}.
*/
@@ -679,16 +651,6 @@
}
/**
- * Time that we last initiated a bind to the input method, to determine
- * if we should try to disconnect and reconnect to it.
- */
- @GuardedBy("ImfLock.class")
- private long getLastBindTimeLocked() {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- return userData.mBindingController.getLastBindTime();
- }
-
- /**
* Have we called mCurMethod.bindInput()?
*/
@MultiUserUnawareField
@@ -1840,21 +1802,6 @@
}
/**
- * Sets current host input token.
- *
- * @param callerImeToken the token has been made for the current active input method
- * @param hostInputToken the host input token of the current active input method
- */
- void setCurHostInputToken(@NonNull IBinder callerImeToken, @Nullable IBinder hostInputToken) {
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(callerImeToken)) {
- return;
- }
- mCurHostInputToken = hostInputToken;
- }
- }
-
- /**
* Gets enabled subtypes of the specified {@link InputMethodInfo}.
*
* @param imiId if null, returns enabled subtypes for the current {@link InputMethodInfo}.
@@ -2170,7 +2117,8 @@
@NonNull EditorInfo editorInfo, @StartInputFlags int startInputFlags,
@StartInputReason int startInputReason,
int unverifiedTargetSdkVersion,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher,
+ @NonNull UserDataRepository.UserData userData) {
// Compute the final shown display ID with validated cs.selfReportedDisplayId for this
// session & other conditions.
@@ -2211,7 +2159,8 @@
final boolean connectionWasActive = mCurInputConnection != null;
// Bump up the sequence for this client and attach it.
- advanceSequenceNumberLocked();
+ userData.mBindingController.advanceSequenceNumber();
+
mCurClient = cs;
mCurInputConnection = inputConnection;
mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection;
@@ -2233,7 +2182,6 @@
if (connectionIsActive != connectionWasActive) {
mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive);
}
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
final var bindingController = userData.mBindingController;
// If configured, we want to avoid starting up the IME if it is not supposed to be showing
@@ -2250,7 +2198,9 @@
// Check if the input method is changing.
// We expect the caller has already verified that the client is allowed to access this
// display ID.
- if (isSelectedMethodBoundLocked()) {
+ final String curId = bindingController.getCurId();
+ if (curId != null && curId.equals(bindingController.getSelectedMethodId())
+ && mDisplayIdToShowIme == mCurTokenDisplayId) {
if (cs.mCurSession != null) {
// Fast case: if we are already connected to the input method,
// then just return it.
@@ -2269,7 +2219,7 @@
(startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
}
- InputBindResult bindResult = tryReuseConnectionLocked(cs);
+ InputBindResult bindResult = tryReuseConnectionLocked(userData, cs);
if (bindResult != null) {
return bindResult;
}
@@ -2369,13 +2319,6 @@
}
@GuardedBy("ImfLock.class")
- private boolean isSelectedMethodBoundLocked() {
- String curId = getCurIdLocked();
- return curId != null && curId.equals(getSelectedMethodIdLocked())
- && mDisplayIdToShowIme == mCurTokenDisplayId;
- }
-
- @GuardedBy("ImfLock.class")
private void prepareClientSwitchLocked(ClientState cs) {
// If the client is changing, we need to switch over to the new
// one.
@@ -2388,8 +2331,9 @@
@GuardedBy("ImfLock.class")
@Nullable
- private InputBindResult tryReuseConnectionLocked(@NonNull ClientState cs) {
- if (hasConnectionLocked()) {
+ private InputBindResult tryReuseConnectionLocked(@NonNull UserDataRepository.UserData userData,
+ @NonNull ClientState cs) {
+ if (userData.mBindingController.hasMainConnection()) {
if (getCurMethodLocked() != null) {
// Return to client, and we will get back with it when
// we have had a session made for it.
@@ -2399,7 +2343,8 @@
InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
null, null, null, getCurIdLocked(), getSequenceNumberLocked(), false);
} else {
- long bindingDuration = SystemClock.uptimeMillis() - getLastBindTimeLocked();
+ final long lastBindTime = userData.mBindingController.getLastBindTime();
+ long bindingDuration = SystemClock.uptimeMillis() - lastBindTime;
if (bindingDuration < TIME_TO_RECONNECT) {
// In this case we have connected to the service, but
// don't yet have its interface. If it hasn't been too
@@ -2532,7 +2477,7 @@
mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
updateSystemUiLocked(mImeWindowVis, mBackDisposition);
mCurTokenDisplayId = INVALID_DISPLAY;
- mCurHostInputToken = null;
+ mAutofillController.onResetSystemUi();
}
@GuardedBy("ImfLock.class")
@@ -3779,6 +3724,7 @@
startInputByWinGainedFocus, toolType);
mVisibilityStateComputer.setWindowState(windowToken, windowState);
+ final var userData = mUserDataRepository.getOrCreate(userId);
if (sameWindowFocused && isTextEditor) {
if (DEBUG) {
Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
@@ -3789,7 +3735,7 @@
if (editorInfo != null) {
return startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, editorInfo, startInputFlags,
- startInputReason, unverifiedTargetSdkVersion, imeDispatcher);
+ startInputReason, unverifiedTargetSdkVersion, imeDispatcher, userData);
}
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
@@ -3821,7 +3767,7 @@
res = startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, editorInfo, startInputFlags,
startInputReason, unverifiedTargetSdkVersion,
- imeDispatcher);
+ imeDispatcher, userData);
didStart = true;
}
break;
@@ -3836,7 +3782,6 @@
// Note that we can trust client's display ID as long as it matches
// to the display ID obtained from the window.
if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) {
- final var userData = mUserDataRepository.getOrCreate(userId);
userData.mBindingController.unbindCurrentMethod();
}
}
@@ -3846,7 +3791,7 @@
res = startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, editorInfo, startInputFlags,
startInputReason, unverifiedTargetSdkVersion,
- imeDispatcher);
+ imeDispatcher, userData);
} else {
res = InputBindResult.NULL_EDITOR_INFO;
}
@@ -4481,6 +4426,7 @@
private void dumpDebug(ProtoOutputStream proto, long fieldId) {
synchronized (ImfLock.class) {
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
final long token = proto.start(fieldId);
proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
proto.write(CUR_SEQ, getSequenceNumberLocked());
@@ -4499,7 +4445,7 @@
proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked()));
proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
proto.write(SYSTEM_READY, mSystemReady);
- proto.write(HAVE_CONNECTION, hasConnectionLocked());
+ proto.write(HAVE_CONNECTION, userData.mBindingController.hasMainConnection());
proto.write(BOUND_TO_METHOD, mBoundToMethod);
proto.write(IS_INTERACTIVE, mIsInteractive);
proto.write(BACK_DISPOSITION, mBackDisposition);
@@ -5625,14 +5571,17 @@
@Override
public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
- int displayId) {
+ int displayId, @UserIdInt int userId) {
//TODO(b/150843766): Check if Input Token is valid.
final IBinder curHostInputToken;
synchronized (ImfLock.class) {
- if (displayId != mCurTokenDisplayId || mCurHostInputToken == null) {
+ if (displayId != mCurTokenDisplayId) {
return false;
}
- curHostInputToken = mCurHostInputToken;
+ curHostInputToken = mAutofillController.getCurHostInputToken();
+ if (curHostInputToken == null) {
+ return false;
+ }
}
return mInputManagerInternal.transferTouchGesture(sourceInputToken, curHostInputToken);
}
@@ -5947,14 +5896,27 @@
client = mCurClient;
p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
- mImeBindingState.dump(" ", p);
+ mImeBindingState.dump(/* prefix= */ " ", p);
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- p.println(" mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked()
+ p.println(" mCurId=" + getCurIdLocked()
+ + " mHaveConnection=" + userData.mBindingController.hasMainConnection()
+ " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
+ userData.mBindingController.isVisibleBound());
+
+ p.println(" mUserDataRepository=");
+ // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
+ @SuppressWarnings("GuardedBy") Consumer<UserDataRepository.UserData> userDataDump =
+ u -> {
+ p.println(" mUserId=" + u.mUserId);
+ p.println(" hasMainConnection="
+ + u.mBindingController.hasMainConnection());
+ p.println(" isVisibleBound=" + u.mBindingController.isVisibleBound());
+ };
+ mUserDataRepository.forAllUserData(userDataDump);
+
p.println(" mCurToken=" + getCurTokenLocked());
p.println(" mCurTokenDisplayId=" + mCurTokenDisplayId);
- p.println(" mCurHostInputToken=" + mCurHostInputToken);
+ p.println(" mCurHostInputToken=" + mAutofillController.getCurHostInputToken());
p.println(" mCurIntent=" + getCurIntentLocked());
method = getCurMethodLocked();
p.println(" mCurMethod=" + getCurMethodLocked());
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 0049213..d932bd4 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -32,6 +32,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.os.Bundle;
import android.os.Environment;
import android.os.HandlerThread;
import android.os.LocaleList;
@@ -101,6 +102,11 @@
// the application setting the app-locale itself.
private final SharedPreferences mDelegateAppLocalePackages;
private final BroadcastReceiver mUserMonitor;
+ // To determine whether an app is pre-archived, check for Intent.EXTRA_ARCHIVAL upon receiving
+ // the initial PACKAGE_ADDED broadcast. If it is indeed pre-archived, perform the data
+ // restoration during the second PACKAGE_ADDED broadcast, which is sent subsequently when the
+ // app is installed.
+ private final Set<String> mPkgsToRestore;
LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
PackageManager packageManager, HandlerThread broadcastHandlerThread) {
@@ -119,6 +125,7 @@
mStagedData = stagedData;
mDelegateAppLocalePackages = delegateAppLocalePackages != null ? delegateAppLocalePackages
: createPersistedInfo();
+ mPkgsToRestore = new ArraySet<>();
mUserMonitor = new UserMonitor();
IntentFilter filter = new IntentFilter();
@@ -251,6 +258,9 @@
LocalesInfo localesInfo = pkgStates.get(pkgName);
// Check if the application is already installed for the concerned user.
if (isPackageInstalledForUser(pkgName, userId)) {
+ if (mPkgsToRestore != null) {
+ mPkgsToRestore.remove(pkgName);
+ }
// Don't apply the restore if the locales have already been set for the app.
checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId);
} else {
@@ -279,23 +289,18 @@
/**
* <p><b>Note:</b> This is invoked by service's common monitor
- * {@link LocaleManagerServicePackageMonitor#onPackageAdded} when a new package is
+ * {@link LocaleManagerServicePackageMonitor#onPackageAddedWithExtras} when a new package is
* added on device.
*/
- void onPackageAdded(String packageName, int uid) {
- try {
- synchronized (mStagedDataLock) {
- cleanStagedDataForOldEntriesLocked();
-
- int userId = UserHandle.getUserId(uid);
- if (mStagedData.contains(userId)) {
- // Perform lazy restore only if the staged data exists.
- doLazyRestoreLocked(packageName, userId);
- }
+ void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) {
+ boolean archived = false;
+ if (extras != null) {
+ archived = extras.getBoolean(Intent.EXTRA_ARCHIVAL, false);
+ if (archived && mPkgsToRestore != null) {
+ mPkgsToRestore.add(packageName);
}
- } catch (Exception e) {
- Slog.e(TAG, "Exception in onPackageAdded.", e);
}
+ checkStageDataAndApplyRestore(packageName, uid);
}
/**
@@ -305,6 +310,10 @@
*/
void onPackageUpdateFinished(String packageName, int uid) {
int userId = UserHandle.getUserId(uid);
+ if (mPkgsToRestore != null && mPkgsToRestore.contains(packageName)) {
+ mPkgsToRestore.remove(packageName);
+ checkStageDataAndApplyRestore(packageName, uid);
+ }
cleanApplicationLocalesIfNeeded(packageName, userId);
}
@@ -338,6 +347,25 @@
}
}
+ private void checkStageDataAndApplyRestore(String packageName, int uid) {
+ try {
+ synchronized (mStagedDataLock) {
+ cleanStagedDataForOldEntriesLocked();
+
+ int userId = UserHandle.getUserId(uid);
+ if (mStagedData.contains(userId)) {
+ if (mPkgsToRestore != null) {
+ mPkgsToRestore.remove(packageName);
+ }
+ // Perform lazy restore only if the staged data exists.
+ doLazyRestoreLocked(packageName, userId);
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception in onPackageAdded.", e);
+ }
+ }
+
private boolean isPackageInstalledForUser(String packageName, int userId) {
PackageInfo pkgInfo = null;
try {
diff --git a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
index ecd3614..e0a050f 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
@@ -17,6 +17,7 @@
package com.android.server.locales;
import android.annotation.NonNull;
+import android.os.Bundle;
import android.os.UserHandle;
import com.android.internal.content.PackageMonitor;
@@ -48,8 +49,8 @@
}
@Override
- public void onPackageAdded(String packageName, int uid) {
- mBackupHelper.onPackageAdded(packageName, uid);
+ public void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) {
+ mBackupHelper.onPackageAddedWithExtras(packageName, uid, extras);
}
@Override
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index d6d134d..17f8abe 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -333,7 +333,12 @@
return false;
}
- if (didEventHappen(MESSAGE_DUPLICATION_PROBABILITY_PERCENT)) {
+ if (Flags.reliableMessageDuplicateDetectionService()
+ && didEventHappen(MESSAGE_DUPLICATION_PROBABILITY_PERCENT)) {
+ Log.i(TAG, "[TEST MODE] Duplicating message ("
+ + NUM_MESSAGES_TO_DUPLICATE
+ + " sends) with message sequence number: "
+ + message.getMessageSequenceNumber());
for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) {
handleClientMessageCallback(contextHubId, hostEndpointId,
message, nanoappPermissions, messagePermissions);
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 77a60289..bf1b3c3 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -168,6 +168,9 @@
}
private void syncKeys() throws RemoteException {
+ if (mCredentialUpdated && mRecoverableKeyStoreDb.getBadRemoteGuessCounter(mUserId) != 0) {
+ mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 0);
+ }
int generation = mPlatformKeyManager.getGenerationId(mUserId);
if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
// Application keys for the user will not be available for sync.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 42ec1c3..61054a9 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -11271,6 +11271,9 @@
// Lifetime extended notifications don't need to alert on state change.
record.setPostSilently(true);
+ // We also set FLAG_ONLY_ALERT_ONCE to avoid the notification from HUN-ing again.
+ record.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
+
mHandler.post(new EnqueueNotificationRunnable(record.getUser().getIdentifier(),
record, isAppForeground,
mPostNotificationTrackerFactory.newTracker(null)));
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1309e44..41d6288 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2139,10 +2139,17 @@
continue;
}
+ ComponentName unflattenOriginalComponentName = ComponentName.unflattenFromString(
+ originalComponentName);
+ if (unflattenOriginalComponentName == null) {
+ Slog.d(TAG, "Incorrect component name from the attributes");
+ continue;
+ }
+
activityInfos.add(
new ArchiveState.ArchiveActivityInfo(
title,
- ComponentName.unflattenFromString(originalComponentName),
+ unflattenOriginalComponentName,
iconPath,
monochromeIconPath));
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f6487ce..b1976cd 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1371,7 +1371,7 @@
for (int i = 0; i < userSize; i++) {
UserInfo ui = mUsers.valueAt(i).info;
if ((excludePartial && ui.partial)
- || (excludeDying && isDyingLU(ui))
+ || (excludeDying && mRemovingUserIds.get(ui.id))
|| (excludePreCreated && ui.preCreated)) {
continue;
}
@@ -1381,17 +1381,6 @@
}
}
- @GuardedBy("mUsersLock")
- private boolean isDyingLU(UserInfo ui) {
- if (mRemovingUserIds.get(ui.id)) {
- return true;
- }
- if (ui.isEphemeral() && ui.isInitialized() && ui.id != getCurrentUserId()) {
- return true;
- }
- return false;
- }
-
@Override
public List<UserInfo> getProfiles(@UserIdInt int userId, boolean enabledOnly) {
boolean returnFullInfo;
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 4e02470..483d308 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -859,7 +859,6 @@
break;
case android.provider.Settings.System.SCREEN_BRIGHTNESS:
- case android.provider.Settings.System.SCREEN_BRIGHTNESS_FLOAT:
case android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE:
if (callingUid == Process.SYSTEM_UID) {
return false;
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 93f26ae..c85ceac 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -642,6 +642,8 @@
.getPackages()
.get(0)
.getVersionRolledBackFrom();
+ Slog.i(TAG, "Rolling back high impact rollback for package: "
+ + firstRollback.getPackageName());
rollbackPackage(sortedHighImpactRollbacks.get(0), firstRollback, rollbackReason);
}
diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
index 519c0ed..7fc0292 100644
--- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -293,6 +293,8 @@
return "REASON_APP_NOT_RESPONDING";
case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT:
return "REASON_NATIVE_CRASH_DURING_BOOT";
+ case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING:
+ return "REASON_BOOT_LOOP";
default:
return "UNKNOWN";
}
diff --git a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
index 2bf0b2c..55f85ea2 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
@@ -22,6 +22,8 @@
import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.os.IBinder;
@@ -31,12 +33,20 @@
import android.security.attestationverification.IAttestationVerificationManagerService;
import android.security.attestationverification.IVerificationResult;
import android.security.attestationverification.VerificationToken;
+import android.text.TextUtils;
import android.util.ExceptionUtils;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
+import android.util.TimeUtils;
import com.android.internal.infra.AndroidFuture;
+import com.android.internal.util.DumpUtils;
import com.android.server.SystemService;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayDeque;
+
/**
* A {@link SystemService} which provides functionality related to verifying attestations of
* (usually) remote computing environments.
@@ -46,11 +56,13 @@
public class AttestationVerificationManagerService extends SystemService {
private static final String TAG = "AVF";
+ private static final int DUMP_EVENT_LOG_SIZE = 10;
private final AttestationVerificationPeerDeviceVerifier mPeerDeviceVerifier;
+ private final DumpLogger mDumpLogger = new DumpLogger();
public AttestationVerificationManagerService(final Context context) throws Exception {
super(context);
- mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context);
+ mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context, mDumpLogger);
}
private final IBinder mService = new IAttestationVerificationManagerService.Stub() {
@@ -83,6 +95,28 @@
private void enforceUsePermission() {
getContext().enforceCallingOrSelfPermission(USE_ATTESTATION_VERIFICATION_SERVICE, null);
}
+
+ @Override
+ protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
+ @Nullable String[] args) {
+ if (!android.security.Flags.dumpAttestationVerifications()) {
+ super.dump(fd, writer, args);
+ return;
+ }
+
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, writer)) return;
+
+ final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " ");
+
+ fout.print("AttestationVerificationManagerService");
+ fout.println();
+ fout.increaseIndent();
+
+ fout.println("Event Log:");
+ fout.increaseIndent();
+ mDumpLogger.dumpTo(fout);
+ fout.decreaseIndent();
+ }
};
private void verifyAttestationForAllVerifiers(
@@ -119,4 +153,45 @@
Slog.d(TAG, "Started");
publishBinderService(Context.ATTESTATION_VERIFICATION_SERVICE, mService);
}
+
+
+ static class DumpLogger {
+ private final ArrayDeque<DumpData> mData = new ArrayDeque<>(DUMP_EVENT_LOG_SIZE);
+ private int mEventsLogged = 0;
+
+ void logAttempt(DumpData data) {
+ synchronized (mData) {
+ if (mData.size() == DUMP_EVENT_LOG_SIZE) {
+ mData.removeFirst();
+ }
+
+ mEventsLogged++;
+ data.mEventNumber = mEventsLogged;
+
+ data.mEventTimeMs = System.currentTimeMillis();
+
+ mData.add(data);
+ }
+ }
+
+ void dumpTo(IndentingPrintWriter writer) {
+ synchronized (mData) {
+ for (DumpData data : mData.reversed()) {
+ writer.println(
+ TextUtils.formatSimple("Verification #%d [%s]", data.mEventNumber,
+ TimeUtils.formatForLogging(data.mEventTimeMs)));
+ writer.increaseIndent();
+ data.dumpTo(writer);
+ writer.decreaseIndent();
+ }
+ }
+ }
+ }
+
+ abstract static class DumpData {
+ protected int mEventNumber = -1;
+ protected long mEventTimeMs = -1;
+
+ abstract void dumpTo(IndentingPrintWriter writer);
+ }
}
diff --git a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
index 72a402d..945a340 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
@@ -30,15 +30,19 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
+import android.security.attestationverification.AttestationVerificationManager;
import android.security.attestationverification.AttestationVerificationManager.LocalBindingType;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.security.AttestationVerificationManagerService.DumpLogger;
import org.json.JSONObject;
@@ -71,7 +75,9 @@
/**
* Verifies Android key attestation according to the
- * {@link android.security.attestationverification.AttestationVerificationManager#PROFILE_PEER_DEVICE PROFILE_PEER_DEVICE}
+ * {@link
+ * android.security.attestationverification.AttestationVerificationManager#PROFILE_PEER_DEVICE
+ * PROFILE_PEER_DEVICE}
* profile.
*
* <p>
@@ -118,9 +124,12 @@
private final LocalDate mTestLocalPatchDate;
private final CertificateFactory mCertificateFactory;
private final CertPathValidator mCertPathValidator;
+ private final DumpLogger mDumpLogger;
- AttestationVerificationPeerDeviceVerifier(@NonNull Context context) throws Exception {
+ AttestationVerificationPeerDeviceVerifier(@NonNull Context context,
+ @NonNull DumpLogger dumpLogger) throws Exception {
mContext = Objects.requireNonNull(context);
+ mDumpLogger = dumpLogger;
mCertificateFactory = CertificateFactory.getInstance("X.509");
mCertPathValidator = CertPathValidator.getInstance("PKIX");
mTrustAnchors = getTrustAnchors();
@@ -132,9 +141,10 @@
// Use ONLY for hermetic unit testing.
@VisibleForTesting
AttestationVerificationPeerDeviceVerifier(@NonNull Context context,
- Set<TrustAnchor> trustAnchors, boolean revocationEnabled,
+ DumpLogger dumpLogger, Set<TrustAnchor> trustAnchors, boolean revocationEnabled,
LocalDate systemDate, LocalDate localPatchDate) throws Exception {
mContext = Objects.requireNonNull(context);
+ mDumpLogger = dumpLogger;
mCertificateFactory = CertificateFactory.getInstance("X.509");
mCertPathValidator = CertPathValidator.getInstance("PKIX");
mTrustAnchors = trustAnchors;
@@ -153,63 +163,90 @@
* bounded at the end by {@code -----END CERTIFICATE-----}.
*
* @param localBindingType Only {@code TYPE_PUBLIC_KEY} and {@code TYPE_CHALLENGE} supported.
- * @param requirements Only {@code PARAM_PUBLIC_KEY} and {@code PARAM_CHALLENGE} supported.
- * @param attestation Certificates should be DER encoded with leaf certificate appended first.
+ * @param requirements Only {@code PARAM_PUBLIC_KEY} and {@code PARAM_CHALLENGE} supported.
+ * @param attestation Certificates should be DER encoded with leaf certificate appended
+ * first.
*/
int verifyAttestation(
@LocalBindingType int localBindingType,
@NonNull Bundle requirements,
@NonNull byte[] attestation) {
+
+ MyDumpData dumpData = new MyDumpData();
+
+ int result =
+ verifyAttestationInternal(localBindingType, requirements, attestation, dumpData);
+ dumpData.mResult = result;
+ mDumpLogger.logAttempt(dumpData);
+ return result;
+ }
+
+ private int verifyAttestationInternal(
+ @LocalBindingType int localBindingType,
+ @NonNull Bundle requirements,
+ @NonNull byte[] attestation,
+ @NonNull MyDumpData dumpData) {
if (mCertificateFactory == null) {
debugVerboseLog("Unable to access CertificateFactory");
return RESULT_FAILURE;
}
+ dumpData.mCertificationFactoryAvailable = true;
if (mCertPathValidator == null) {
debugVerboseLog("Unable to access CertPathValidator");
return RESULT_FAILURE;
}
+ dumpData.mCertPathValidatorAvailable = true;
+
// Check if the provided local binding type is supported and if the provided requirements
// "match" the binding type.
if (!validateAttestationParameters(localBindingType, requirements)) {
return RESULT_FAILURE;
}
+ dumpData.mAttestationParametersOk = true;
+
+ // To provide the most information in the dump logs, we track the failure state but keep
+ // verifying the rest of the attestation. For code safety, there are no transitions past
+ // here to set failed = false
+ boolean failed = false;
try {
// First: parse and validate the certificate chain.
final List<X509Certificate> certificateChain = getCertificates(attestation);
// (returns void, but throws CertificateException and other similar Exceptions)
validateCertificateChain(certificateChain);
+ dumpData.mCertChainOk = true;
final var leafCertificate = certificateChain.get(0);
final var attestationExtension = fromCertificate(leafCertificate);
// Second: verify if the attestation satisfies the "peer device" profile.
- if (!checkAttestationForPeerDeviceProfile(attestationExtension)) {
- return RESULT_FAILURE;
+ if (!checkAttestationForPeerDeviceProfile(attestationExtension, dumpData)) {
+ failed = true;
}
// Third: check if the attestation satisfies local binding requirements.
if (!checkLocalBindingRequirements(
- leafCertificate, attestationExtension, localBindingType, requirements)) {
- return RESULT_FAILURE;
+ leafCertificate, attestationExtension, localBindingType, requirements,
+ dumpData)) {
+ failed = true;
}
-
- return RESULT_SUCCESS;
} catch (CertificateException | CertPathValidatorException
- | InvalidAlgorithmParameterException | IOException e) {
+ | InvalidAlgorithmParameterException | IOException e) {
// Catch all non-RuntimeExpceptions (all of these are thrown by either getCertificates()
// or validateCertificateChain() or
// AndroidKeystoreAttestationVerificationAttributes.fromCertificate())
debugVerboseLog("Unable to parse/validate Android Attestation certificate(s)", e);
- return RESULT_FAILURE;
+ failed = true;
} catch (RuntimeException e) {
// Catch everyting else (RuntimeExpcetions), since we don't want to throw any exceptions
// out of this class/method.
debugVerboseLog("Unexpected error", e);
- return RESULT_FAILURE;
+ failed = true;
}
+
+ return failed ? RESULT_FAILURE : RESULT_SUCCESS;
}
@NonNull
@@ -255,7 +292,7 @@
private void validateCertificateChain(List<X509Certificate> certificates)
throws CertificateException, CertPathValidatorException,
- InvalidAlgorithmParameterException {
+ InvalidAlgorithmParameterException {
if (certificates.size() < 2) {
debugVerboseLog("Certificate chain less than 2 in size.");
throw new CertificateException("Certificate chain less than 2 in size.");
@@ -277,7 +314,7 @@
private Set<TrustAnchor> getTrustAnchors() throws CertPathValidatorException {
Set<TrustAnchor> modifiableSet = new HashSet<>();
try {
- for (String certString: getTrustAnchorResources()) {
+ for (String certString : getTrustAnchorResources()) {
modifiableSet.add(
new TrustAnchor((X509Certificate) mCertificateFactory.generateCertificate(
new ByteArrayInputStream(getCertificateBytes(certString))), null));
@@ -307,8 +344,9 @@
@NonNull X509Certificate leafCertificate,
@NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes,
@LocalBindingType int localBindingType,
- @NonNull Bundle requirements) {
+ @NonNull Bundle requirements, MyDumpData dumpData) {
// First: check non-optional (for the given local binding type) requirements.
+ dumpData.mBindingType = localBindingType;
switch (localBindingType) {
case TYPE_PUBLIC_KEY:
// Verify leaf public key matches provided public key.
@@ -336,9 +374,11 @@
throw new IllegalArgumentException("Unsupported local binding type "
+ localBindingTypeToString(localBindingType));
}
+ dumpData.mBindingOk = true;
// Second: check specified optional requirements.
if (requirements.containsKey(PARAM_OWNED_BY_SYSTEM)) {
+ dumpData.mSystemOwnershipChecked = true;
if (requirements.getBoolean(PARAM_OWNED_BY_SYSTEM)) {
// Verify key is owned by the system.
final boolean ownedBySystem = checkOwnedBySystem(
@@ -347,6 +387,7 @@
debugVerboseLog("Certificate public key is not owned by the AndroidSystem.");
return false;
}
+ dumpData.mSystemOwned = true;
} else {
throw new IllegalArgumentException("The value of the requirement key "
+ PARAM_OWNED_BY_SYSTEM
@@ -359,73 +400,98 @@
}
private boolean checkAttestationForPeerDeviceProfile(
- @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes) {
+ @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes,
+ MyDumpData dumpData) {
+ boolean result = true;
+
// Checks for support of Keymaster 4.
if (attestationAttributes.getAttestationVersion() < 3) {
debugVerboseLog("Attestation version is not at least 3 (Keymaster 4).");
- return false;
+ result = false;
+ } else {
+ dumpData.mAttestationVersionAtLeast3 = true;
}
// Checks for support of Keymaster 4.
if (attestationAttributes.getKeymasterVersion() < 4) {
debugVerboseLog("Keymaster version is not at least 4.");
- return false;
+ result = false;
+ } else {
+ dumpData.mKeymasterVersionAtLeast4 = true;
}
// First two characters are Android OS version.
if (attestationAttributes.getKeyOsVersion() < 100000) {
debugVerboseLog("Android OS version is not 10+.");
- return false;
+ result = false;
+ } else {
+ dumpData.mOsVersionAtLeast10 = true;
}
if (!attestationAttributes.isAttestationHardwareBacked()) {
debugVerboseLog("Key is not HW backed.");
- return false;
+ result = false;
+ } else {
+ dumpData.mKeyHwBacked = true;
}
if (!attestationAttributes.isKeymasterHardwareBacked()) {
debugVerboseLog("Keymaster is not HW backed.");
- return false;
+ result = false;
+ } else {
+ dumpData.mKeymasterHwBacked = true;
}
if (attestationAttributes.getVerifiedBootState() != VERIFIED) {
debugVerboseLog("Boot state not Verified.");
- return false;
+ result = false;
+ } else {
+ dumpData.mBootStateIsVerified = true;
}
try {
if (!attestationAttributes.isVerifiedBootLocked()) {
debugVerboseLog("Verified boot state is not locked.");
- return false;
+ result = false;
+ } else {
+ dumpData.mVerifiedBootStateLocked = true;
}
} catch (IllegalStateException e) {
debugVerboseLog("VerifiedBootLocked is not set.", e);
- return false;
+ result = false;
}
// Patch level integer YYYYMM is expected to be within 1 year of today.
if (!isValidPatchLevel(attestationAttributes.getKeyOsPatchLevel())) {
debugVerboseLog("OS patch level is not within valid range.");
- return false;
+ result = false;
+ } else {
+ dumpData.mOsPatchLevelInRange = true;
}
// Patch level integer YYYYMMDD is expected to be within 1 year of today.
if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) {
debugVerboseLog("Boot patch level is not within valid range.");
- return false;
+ result = false;
+ } else {
+ dumpData.mKeyBootPatchLevelInRange = true;
}
if (!isValidPatchLevel(attestationAttributes.getKeyVendorPatchLevel())) {
debugVerboseLog("Vendor patch level is not within valid range.");
- return false;
+ result = false;
+ } else {
+ dumpData.mKeyVendorPatchLevelInRange = true;
}
if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) {
debugVerboseLog("Boot patch level is not within valid range.");
- return false;
+ result = false;
+ } else {
+ dumpData.mKeyBootPatchLevelInRange = true;
}
- return true;
+ return result;
}
private boolean checkPublicKey(
@@ -609,4 +675,99 @@
Slog.v(TAG, str);
}
}
+
+ /* Mutable data class for tracking dump data from verifications. */
+ private static class MyDumpData extends AttestationVerificationManagerService.DumpData {
+
+ // Top-Level Result
+ int mResult = -1;
+
+ // Configuration/Setup preconditions
+ boolean mCertificationFactoryAvailable = false;
+ boolean mCertPathValidatorAvailable = false;
+
+ // AttestationParameters (Valid Input Only)
+ boolean mAttestationParametersOk = false;
+
+ // Certificate Chain (Structure & Chaining Conditions)
+ boolean mCertChainOk = false;
+
+ // Binding
+ boolean mBindingOk = false;
+ int mBindingType = -1;
+
+ // System Ownership
+ boolean mSystemOwnershipChecked = false;
+ boolean mSystemOwned = false;
+
+ // Android Keystore attestation properties
+ boolean mOsVersionAtLeast10 = false;
+ boolean mKeyHwBacked = false;
+ boolean mAttestationVersionAtLeast3 = false;
+ boolean mKeymasterVersionAtLeast4 = false;
+ boolean mKeymasterHwBacked = false;
+ boolean mBootStateIsVerified = false;
+ boolean mVerifiedBootStateLocked = false;
+ boolean mOsPatchLevelInRange = false;
+ boolean mKeyBootPatchLevelInRange = false;
+ boolean mKeyVendorPatchLevelInRange = false;
+
+ @SuppressLint("WrongConstant")
+ @Override
+ public void dumpTo(IndentingPrintWriter writer) {
+ writer.println(
+ "Result: " + AttestationVerificationManager.verificationResultCodeToString(
+ mResult));
+ if (!mCertificationFactoryAvailable) {
+ writer.println("Certificate Factory Unavailable");
+ return;
+ }
+ if (!mCertPathValidatorAvailable) {
+ writer.println("Cert Path Validator Unavailable");
+ return;
+ }
+ if (!mAttestationParametersOk) {
+ writer.println("Attestation parameters set incorrectly.");
+ return;
+ }
+
+ writer.println("Certificate Chain Valid (inc. Trust Anchor): " + booleanToOkFail(
+ mCertChainOk));
+ if (!mCertChainOk) {
+ return;
+ }
+
+ // Binding
+ writer.println("Local Binding: " + booleanToOkFail(mBindingOk));
+ writer.increaseIndent();
+ writer.println("Binding Type: " + mBindingType);
+ writer.decreaseIndent();
+
+ if (mSystemOwnershipChecked) {
+ writer.println("System Ownership: " + booleanToOkFail(mSystemOwned));
+ }
+
+ // Keystore Attestation params
+ writer.println("KeyStore Attestation Parameters");
+ writer.increaseIndent();
+ writer.println("OS Version >= 10: " + booleanToOkFail(mOsVersionAtLeast10));
+ writer.println("OS Patch Level in Range: " + booleanToOkFail(mOsPatchLevelInRange));
+ writer.println(
+ "Attestation Version >= 3: " + booleanToOkFail(mAttestationVersionAtLeast3));
+ writer.println("Keymaster Version >= 4: " + booleanToOkFail(mKeymasterVersionAtLeast4));
+ writer.println("Keymaster HW-Backed: " + booleanToOkFail(mKeymasterHwBacked));
+ writer.println("Key is HW Backed: " + booleanToOkFail(mKeyHwBacked));
+ writer.println("Boot State is VERIFIED: " + booleanToOkFail(mBootStateIsVerified));
+ writer.println("Verified Boot is LOCKED: " + booleanToOkFail(mVerifiedBootStateLocked));
+ writer.println(
+ "Key Boot Level in Range: " + booleanToOkFail(mKeyBootPatchLevelInRange));
+ writer.println("Key Vendor Patch Level in Range: " + booleanToOkFail(
+ mKeyVendorPatchLevelInRange));
+ writer.decreaseIndent();
+ }
+
+ private String booleanToOkFail(boolean value) {
+ return value ? "OK" : "FAILURE";
+ }
+ }
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 2c67207..cdd456c 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -20,9 +20,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
-import static android.app.StatusBarManager.DISABLE2_MASK;
import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE;
-import static android.app.StatusBarManager.DISABLE_MASK;
import static android.app.StatusBarManager.NAV_BAR_MODE_DEFAULT;
import static android.app.StatusBarManager.NAV_BAR_MODE_KIDS;
import static android.app.StatusBarManager.NavBarMode;
@@ -222,9 +220,8 @@
int what1;
int what2;
IBinder token;
- private String mReason;
- DisableRecord(int userId, IBinder token) {
+ public DisableRecord(int userId, IBinder token) {
this.userId = userId;
this.token = token;
try {
@@ -237,12 +234,12 @@
@Override
public void binderDied() {
Slog.i(TAG, "binder died for pkg=" + pkg);
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- disableForUser(info, token, pkg, userId, "Binder Died");
+ disableForUser(0, token, pkg, userId);
+ disable2ForUser(0, token, pkg, userId);
token.unlinkToDeath(this, 0);
}
- public void setFlags(int what, int which, String pkg, String reason) {
+ public void setFlags(int what, int which, String pkg) {
switch (which) {
case 1:
what1 = what;
@@ -256,7 +253,6 @@
break;
}
this.pkg = pkg;
- this.mReason = reason;
}
public int getFlags(int which) {
@@ -275,8 +271,8 @@
@Override
public String toString() {
- return String.format("userId=%d what1=0x%08X what2=0x%08X pkg=%s token=%s reason=%s",
- userId, what1, what2, pkg, token, mReason);
+ return String.format("userId=%d what1=0x%08X what2=0x%08X pkg=%s token=%s",
+ userId, what1, what2, pkg, token);
}
}
@@ -1184,59 +1180,57 @@
return mTracingEnabled;
}
- /**
- * @deprecated
- * Disable some features in the status bar.
- *
- * This method is deprecated and callers should use
- * {@link #disableForUser(StatusBarManager.DisableInfo, IBinder, String, int, String)}
- *
- * @hide
- */
- @Deprecated
+ // TODO(b/117478341): make it aware of multi-display if needed.
@Override
public void disable(int what, IBinder token, String pkg) {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(what & DISABLE_MASK,
- what & DISABLE2_MASK);
- disableForUser(info, token, pkg, mCurrentUserId, null);
- }
-
- /**
- * @deprecated
- * Disable some features in the status bar.
- *
- * This method is deprecated and callers should use
- * {@link #disableForUser(StatusBarManager.DisableInfo, IBinder, String, int, String)}
- *
- * @hide
- */
- @Deprecated
- @Override
- public void disable2(int what, IBinder token, String pkg) {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(what & DISABLE_MASK,
- what & DISABLE2_MASK);
- disableForUser(info, token, pkg, mCurrentUserId, null);
+ disableForUser(what, token, pkg, mCurrentUserId);
}
// TODO(b/117478341): make it aware of multi-display if needed.
@Override
- public void disableForUser(StatusBarManager.DisableInfo disableInfo, IBinder token, String pkg,
- int userId, String reason) {
+ public void disableForUser(int what, IBinder token, String pkg, int userId) {
enforceStatusBar();
+
synchronized (mLock) {
- Pair<Integer, Integer> flags = disableInfo.toFlags();
- disableLocked(DEFAULT_DISPLAY, userId, flags.first, token, pkg, 1, reason);
- disableLocked(DEFAULT_DISPLAY, userId, flags.second, token, pkg, 2, reason);
+ disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 1);
+ }
+ }
+
+ // TODO(b/117478341): make it aware of multi-display if needed.
+ /**
+ * Disable additional status bar features. Pass the bitwise-or of the DISABLE2_* flags.
+ * To re-enable everything, pass {@link #DISABLE2_NONE}.
+ *
+ * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+ */
+ @Override
+ public void disable2(int what, IBinder token, String pkg) {
+ disable2ForUser(what, token, pkg, mCurrentUserId);
+ }
+
+ // TODO(b/117478341): make it aware of multi-display if needed.
+ /**
+ * Disable additional status bar features for a given user. Pass the bitwise-or of the
+ * DISABLE2_* flags. To re-enable everything, pass {@link #DISABLE_NONE}.
+ *
+ * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+ */
+ @Override
+ public void disable2ForUser(int what, IBinder token, String pkg, int userId) {
+ enforceStatusBar();
+
+ synchronized (mLock) {
+ disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 2);
}
}
private void disableLocked(int displayId, int userId, int what, IBinder token, String pkg,
- int whichFlag, String reason) {
+ int whichFlag) {
// It's important that the the callback and the call to mBar get done
// in the same order when multiple threads are calling this function
// so they are paired correctly. The messages on the handler will be
// handled in the order they were enqueued, but will be outside the lock.
- manageDisableListLocked(userId, what, token, pkg, whichFlag, reason);
+ manageDisableListLocked(userId, what, token, pkg, whichFlag);
// Ensure state for the current user is applied, even if passed a non-current user.
final int net1 = gatherDisableActionsLocked(mCurrentUserId, 1);
@@ -1385,7 +1379,7 @@
// also allows calls from window manager which is in this process.
enforceStatusBarService();
- final int unknownFlags = flags & ~DISABLE_MASK;
+ final int unknownFlags = flags & ~StatusBarManager.DISABLE_MASK;
if (unknownFlags != 0) {
Slog.e(TAG, "Unknown disable flags: 0x" + Integer.toHexString(unknownFlags),
new RuntimeException());
@@ -1394,8 +1388,7 @@
if (SPEW) Slog.d(TAG, "setDisableFlags(0x" + Integer.toHexString(flags) + ")");
synchronized (mLock) {
- disableLocked(displayId, mCurrentUserId, flags, mSysUiVisToken, cause, 1,
- "setDisableFlags");
+ disableLocked(displayId, mCurrentUserId, flags, mSysUiVisToken, cause, 1);
}
}
@@ -2450,8 +2443,7 @@
// ================================================================================
// lock on mDisableRecords
- void manageDisableListLocked(int userId, int what, IBinder token, String pkg, int which,
- String reason) {
+ void manageDisableListLocked(int userId, int what, IBinder token, String pkg, int which) {
if (SPEW) {
Slog.d(TAG, "manageDisableList userId=" + userId
+ " what=0x" + Integer.toHexString(what) + " pkg=" + pkg);
@@ -2473,7 +2465,7 @@
// Update existing record
if (record != null) {
- record.setFlags(what, which, pkg, reason);
+ record.setFlags(what, which, pkg);
if (record.isEmpty()) {
mDisableRecords.remove(i);
record.token.unlinkToDeath(record, 0);
@@ -2483,7 +2475,7 @@
// Record doesn't exist, so we create a new one
record = new DisableRecord(userId, token);
- record.setFlags(what, which, pkg, reason);
+ record.setFlags(what, which, pkg);
mDisableRecords.add(record);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
index adb55b4..d6bf02f 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
@@ -16,6 +16,8 @@
import static android.app.StatusBarManager.DEFAULT_SETUP_DISABLE2_FLAGS;
import static android.app.StatusBarManager.DEFAULT_SETUP_DISABLE_FLAGS;
+import static android.app.StatusBarManager.DISABLE2_NONE;
+import static android.app.StatusBarManager.DISABLE_NONE;
import android.app.StatusBarManager.DisableInfo;
import android.content.ComponentName;
@@ -25,6 +27,7 @@
import android.os.RemoteException;
import android.os.ShellCommand;
import android.service.quicksettings.TileService;
+import android.util.Pair;
import java.io.PrintWriter;
@@ -141,17 +144,25 @@
String arg = getNextArgRequired();
String pkg = mContext.getPackageName();
boolean disable = Boolean.parseBoolean(arg);
- int userId = Binder.getCallingUserHandle().getIdentifier();
- DisableInfo info = disable ? new DisableInfo(DEFAULT_SETUP_DISABLE_FLAGS,
- DEFAULT_SETUP_DISABLE2_FLAGS) : new DisableInfo();
- mInterface.disableForUser(info, sToken, pkg, userId, "runDisableForSetup");
+
+ if (disable) {
+ mInterface.disable(DEFAULT_SETUP_DISABLE_FLAGS, sToken, pkg);
+ mInterface.disable2(DEFAULT_SETUP_DISABLE2_FLAGS, sToken, pkg);
+ } else {
+ mInterface.disable(DISABLE_NONE, sToken, pkg);
+ mInterface.disable2(DISABLE2_NONE, sToken, pkg);
+ }
+
return 0;
}
private int runSendDisableFlag() {
String pkg = mContext.getPackageName();
- int userId = Binder.getCallingUserHandle().getIdentifier();
+ int disable1 = DISABLE_NONE;
+ int disable2 = DISABLE2_NONE;
+
DisableInfo info = new DisableInfo();
+
String arg = getNextArg();
while (arg != null) {
switch (arg) {
@@ -159,7 +170,7 @@
info.setSearchDisabled(true);
break;
case "home":
- info.setNavigationHomeDisabled(true);
+ info.setNagivationHomeDisabled(true);
break;
case "recents":
info.setRecentsDisabled(true);
@@ -186,7 +197,10 @@
arg = getNextArg();
}
- mInterface.disableForUser(info, sToken, pkg, userId, "Shell Commands");
+ Pair<Integer, Integer> flagPair = info.toFlags();
+
+ mInterface.disable(flagPair.first, sToken, pkg);
+ mInterface.disable2(flagPair.second, sToken, pkg);
return 0;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e814f17..21e4c96 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -10862,8 +10862,10 @@
final Rect filledContainerBounds = mIsInFixedOrientationOrAspectRatioLetterbox
? letterboxedContainerBounds
: task != null ? task.getBounds() : display.getBounds();
- final int filledContainerRotation = task != null
- ? task.getConfiguration().windowConfiguration.getRotation()
+ final boolean useActivityRotation = container.hasFixedRotationTransform()
+ && mIsInFixedOrientationOrAspectRatioLetterbox;
+ final int filledContainerRotation = useActivityRotation
+ ? container.getWindowConfiguration().getRotation()
: display.getConfiguration().windowConfiguration.getRotation();
final Point dimensions = getRotationZeroDimensions(
filledContainerBounds, filledContainerRotation);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 5e95a4b..237003a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -184,6 +184,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
+import android.content.pm.FeatureInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
@@ -266,6 +267,7 @@
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.UiThread;
@@ -7400,7 +7402,25 @@
}
}
+ /** Cache the return value for {@link #isPip2ExperimentEnabled()} */
+ private static Boolean sIsPip2ExperimentEnabled = null;
+
+ /**
+ * @return {@code true} if PiP2 implementation should be used. Besides the trunk stable flag,
+ * system property can be used to override this read only flag during development.
+ * It's currently limited to phone form factor, i.e., not enabled on ARC / TV.
+ */
static boolean isPip2ExperimentEnabled() {
- return Flags.enablePip2Implementation();
+ if (sIsPip2ExperimentEnabled == null) {
+ final FeatureInfo arcFeature = SystemConfig.getInstance().getAvailableFeatures().get(
+ "org.chromium.arc");
+ final FeatureInfo tvFeature = SystemConfig.getInstance().getAvailableFeatures().get(
+ FEATURE_LEANBACK);
+ final boolean isArc = arcFeature != null && arcFeature.version >= 0;
+ final boolean isTv = tvFeature != null && tvFeature.version >= 0;
+ sIsPip2ExperimentEnabled = SystemProperties.getBoolean("wm_shell.pip2", false)
+ || (Flags.enablePip2Implementation() && !isArc && !isTv);
+ }
+ return sIsPip2ExperimentEnabled;
}
}
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 25885ed..e8faff6 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -96,6 +96,7 @@
interface TransactionReadyListener {
void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction);
default void onTransactionCommitTimeout() {}
+ default void onReadyTimeout() {}
}
/**
@@ -410,6 +411,7 @@
if (allFinished && !mReady) {
Slog.w(TAG, "Sync group " + mSyncId + " timed-out because not ready. If you see "
+ "this, please file a bug.");
+ mListener.onReadyTimeout();
}
finishNow();
removeFromDependencies(this);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4147249..c9a5e71 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -550,15 +550,6 @@
// TODO(multi-display): remove some of the usages.
boolean isDefaultDisplay;
- /** Detect user tapping outside of current focused task bounds .*/
- // TODO(b/315321016): Remove once pointer event detection is removed from WM.
- @VisibleForTesting
- final TaskTapPointerEventListener mTapDetector;
-
- /** Detect user tapping outside of current focused root task bounds .*/
- // TODO(b/315321016): Remove once pointer event detection is removed from WM.
- private Region mTouchExcludeRegion = new Region();
-
/** Save allocating when calculating rects */
private final Rect mTmpRect = new Rect();
private final Rect mTmpRect2 = new Rect();
@@ -571,10 +562,6 @@
final PinnedTaskController mPinnedTaskController;
- final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
- /** A collection of windows that provide tap exclude regions inside of them. */
- final ArraySet<WindowState> mTapExcludeProvidingWindows = new ArraySet<>();
-
private final LinkedList<ActivityRecord> mTmpUpdateAllDrawn = new LinkedList();
private final TaskForResizePointSearchResult mTmpTaskForResizePointSearchResult =
@@ -1193,18 +1180,6 @@
"PointerEventDispatcher" + mDisplayId, mDisplayId);
mPointerEventDispatcher = new PointerEventDispatcher(inputChannel);
- if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
- mTapDetector = null;
- } else {
- // Tap Listeners are supported for:
- // 1. All physical displays (multi-display).
- // 2. VirtualDisplays on VR, AA (and everything else).
- mTapDetector = new TaskTapPointerEventListener(mWmService, this);
- registerPointerEventListener(mTapDetector);
- }
- if (mWmService.mMousePositionTracker != null) {
- registerPointerEventListener(mWmService.mMousePositionTracker);
- }
if (mWmService.mAtmService.getRecentTasks() != null) {
registerPointerEventListener(
mWmService.mAtmService.getRecentTasks().getInputListener());
@@ -3304,117 +3279,6 @@
mTmpTaskForResizePointSearchResult.process(taskDisplayArea, x, y, delta));
}
- void updateTouchExcludeRegion() {
- if (mTapDetector == null) {
- // The touch exclude region is used to detect the region outside of the focused task
- // so that the tap detector can detect outside touches. Don't calculate the exclude
- // region when the tap detector is disabled.
- return;
- }
- final Task focusedTask = (mFocusedApp != null ? mFocusedApp.getTask() : null);
- if (focusedTask == null) {
- mTouchExcludeRegion.setEmpty();
- } else {
- mTouchExcludeRegion.set(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
- final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
- mTmpRect.setEmpty();
- mTmpRect2.setEmpty();
-
- forAllTasks(t -> { processTaskForTouchExcludeRegion(t, focusedTask, delta); });
-
- // If we removed the focused task above, add it back and only leave its
- // outside touch area in the exclusion. TapDetector is not interested in
- // any touch inside the focused task itself.
- if (!mTmpRect2.isEmpty()) {
- mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION);
- }
- }
- if (mInputMethodWindow != null && mInputMethodWindow.isVisible()) {
- // If the input method is visible and the user is typing, we don't want these touch
- // events to be intercepted and used to change focus. This would likely cause a
- // disappearance of the input method.
- mInputMethodWindow.getTouchableRegion(mTmpRegion);
- mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
- }
- for (int i = mTapExcludedWindows.size() - 1; i >= 0; i--) {
- final WindowState win = mTapExcludedWindows.get(i);
- if (!win.isVisible()) {
- continue;
- }
- win.getTouchableRegion(mTmpRegion);
- mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
- }
- amendWindowTapExcludeRegion(mTouchExcludeRegion);
- mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion);
- }
-
- private void processTaskForTouchExcludeRegion(Task task, Task focusedTask, int delta) {
- if (mTapDetector == null) {
- // The touch exclude region is used to detect the region outside of the focused task
- // so that the tap detector can detect outside touches. Don't calculate the exclude
- // region when the tap detector is disabled.
- }
- final ActivityRecord topVisibleActivity = task.getTopVisibleActivity();
-
- if (topVisibleActivity == null || !topVisibleActivity.hasContentToDisplay()) {
- return;
- }
-
- // Exclusion region is the region that TapDetector doesn't care about.
- // Here we want to remove all non-focused tasks from the exclusion region.
- // We also remove the outside touch area for resizing for all freeform
- // tasks (including the focused).
- // We save the focused task region once we find it, and add it back at the end.
- // If the task is root home task and it is resizable and visible (top of its root task),
- // we want to exclude the root docked task from touch so we need the entire screen area
- // and not just a small portion which the root home task currently is resized to.
- if (task.isActivityTypeHome() && task.isVisible() && task.isResizeable()) {
- task.getDisplayArea().getBounds(mTmpRect);
- } else {
- task.getDimBounds(mTmpRect);
- }
-
- if (task == focusedTask) {
- // Add the focused task rect back into the exclude region once we are done
- // processing root tasks.
- // NOTE: this *looks* like a no-op, but this usage of mTmpRect2 is expected by
- // updateTouchExcludeRegion.
- mTmpRect2.set(mTmpRect);
- }
-
- final boolean isFreeformed = task.inFreeformWindowingMode();
- if (task != focusedTask || isFreeformed) {
- if (isFreeformed) {
- // If the task is freeformed, enlarge the area to account for outside
- // touch area for resize.
- mTmpRect.inset(-delta, -delta);
- // Intersect with display content frame. If we have system decor (status bar/
- // navigation bar), we want to exclude that from the tap detection.
- // Otherwise, if the app is partially placed under some system button (eg.
- // Recents, Home), pressing that button would cause a full series of
- // unwanted transfer focus/resume/pause, before we could go home.
- mTmpRect.inset(getInsetsStateController().getRawInsetsState().calculateInsets(
- mTmpRect, systemBars() | ime(), false /* ignoreVisibility */));
- }
- mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
- }
- }
-
- /**
- * Union the region with all the tap exclude region provided by windows on this display.
- *
- * @param inOutRegion The region to be amended.
- */
- private void amendWindowTapExcludeRegion(Region inOutRegion) {
- final Region region = Region.obtain();
- for (int i = mTapExcludeProvidingWindows.size() - 1; i >= 0; i--) {
- final WindowState win = mTapExcludeProvidingWindows.valueAt(i);
- win.getTapExcludeRegion(region);
- inOutRegion.op(region, Op.UNION);
- }
- region.recycle();
- }
-
@Override
void switchUser(int userId) {
super.switchUser(userId);
@@ -3771,7 +3635,6 @@
pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight);
pw.print(subPrefix + "deferred=" + mDeferredRemoval
+ " mLayoutNeeded=" + mLayoutNeeded);
- pw.println(" mTouchExcludeRegion=" + mTouchExcludeRegion);
pw.println();
super.dump(pw, prefix, dumpAll);
@@ -4120,7 +3983,6 @@
}
getInputMonitor().setFocusedAppLw(newFocus);
- updateTouchExcludeRegion();
return true;
}
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 8116f68..30f2d0d 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -21,13 +21,11 @@
import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
import static android.view.View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG;
-import static com.android.input.flags.Flags.enablePointerChoreographer;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
-import android.app.ActivityManager;
import android.content.ClipData;
import android.content.Context;
import android.hardware.input.InputManagerGlobal;
@@ -266,16 +264,12 @@
final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
mDragState.broadcastDragStartedLocked(touchX, touchY);
- if (enablePointerChoreographer()) {
- if ((touchSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
- InputManagerGlobal.getInstance().setPointerIcon(
- PointerIcon.getSystemIcon(
- mService.mContext, PointerIcon.TYPE_GRABBING),
- mDragState.mDisplayContent.getDisplayId(), touchDeviceId,
- touchPointerId, mDragState.getInputToken());
- }
- } else {
- mDragState.overridePointerIconLocked(touchSource);
+ if ((touchSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
+ InputManagerGlobal.getInstance().setPointerIcon(
+ PointerIcon.getSystemIcon(
+ mService.mContext, PointerIcon.TYPE_GRABBING),
+ mDragState.mDisplayContent.getDisplayId(), touchDeviceId,
+ touchPointerId, mDragState.getInputToken());
}
// remember the thumb offsets for later
mDragState.mThumbOffsetX = thumbCenterX;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 5ed343a..72ae64c 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -45,7 +45,6 @@
import android.content.ClipDescription;
import android.graphics.Point;
import android.graphics.Rect;
-import android.hardware.input.InputManagerGlobal;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
@@ -58,9 +57,7 @@
import android.view.DragEvent;
import android.view.InputApplicationHandle;
import android.view.InputChannel;
-import android.view.InputDevice;
import android.view.InputWindowHandle;
-import android.view.PointerIcon;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
@@ -110,7 +107,6 @@
boolean mCrossProfileCopyAllowed;
ClipData mData;
ClipDescription mDataDescription;
- int mTouchSource;
boolean mDragResult;
boolean mRelinquishDragSurfaceToDropTarget;
float mAnimatedScale = 1.0f;
@@ -263,12 +259,6 @@
Trace.instant(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DRAG_ENDED");
}
- // Take the cursor back if it has been changed.
- if (isFromSource(InputDevice.SOURCE_MOUSE)) {
- mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
- mTouchSource = 0;
- }
-
// Clear the internal variables.
if (mInputSurface != null) {
mTransaction.remove(mInputSurface).apply();
@@ -762,18 +752,6 @@
return animator;
}
- private boolean isFromSource(int source) {
- return (mTouchSource & source) == source;
- }
-
- void overridePointerIconLocked(int touchSource) {
- mTouchSource = touchSource;
- if (isFromSource(InputDevice.SOURCE_MOUSE)) {
- // TODO(b/293587049): Pointer Icon Refactor: Set the pointer icon from the drag window.
- InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING);
- }
- }
-
private class AnimationListener
implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
@Override
diff --git a/services/core/java/com/android/server/wm/InputConfigAdapter.java b/services/core/java/com/android/server/wm/InputConfigAdapter.java
index 119fafd..ae6e724 100644
--- a/services/core/java/com/android/server/wm/InputConfigAdapter.java
+++ b/services/core/java/com/android/server/wm/InputConfigAdapter.java
@@ -20,8 +20,6 @@
import android.view.InputWindowHandle.InputConfigFlags;
import android.view.WindowManager.LayoutParams;
-import java.util.List;
-
/**
* A helper to determine the {@link InputConfigFlags} that control the behavior of an input window
* from several WM attributes.
@@ -47,7 +45,7 @@
* input configurations that can be mapped directly from a corresponding LayoutParams input
* feature.
*/
- private static final List<FlagMapping> INPUT_FEATURE_TO_CONFIG_MAP = List.of(
+ private static final FlagMapping[] INPUT_FEATURE_TO_CONFIG_MAP = {
new FlagMapping(
LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL,
InputConfig.NO_INPUT_CHANNEL, false /* inverted */),
@@ -59,7 +57,8 @@
InputConfig.SPY, false /* inverted */),
new FlagMapping(
LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY,
- InputConfig.SENSITIVE_FOR_PRIVACY, false /* inverted */));
+ InputConfig.SENSITIVE_FOR_PRIVACY, false /* inverted */)
+ };
@InputConfigFlags
private static final int INPUT_FEATURE_TO_CONFIG_MASK =
@@ -72,7 +71,7 @@
* NOTE: The layout params flag {@link LayoutParams#FLAG_NOT_FOCUSABLE} is not handled by this
* adapter, and must be handled explicitly.
*/
- private static final List<FlagMapping> LAYOUT_PARAM_FLAG_TO_CONFIG_MAP = List.of(
+ private static final FlagMapping[] LAYOUT_PARAM_FLAG_TO_CONFIG_MAP = {
new FlagMapping(
LayoutParams.FLAG_NOT_TOUCHABLE,
InputConfig.NOT_TOUCHABLE, false /* inverted */),
@@ -84,7 +83,8 @@
InputConfig.WATCH_OUTSIDE_TOUCH, false /* inverted */),
new FlagMapping(
LayoutParams.FLAG_SLIPPERY,
- InputConfig.SLIPPERY, false /* inverted */));
+ InputConfig.SLIPPERY, false /* inverted */)
+ };
@InputConfigFlags
private static final int LAYOUT_PARAM_FLAG_TO_CONFIG_MASK =
@@ -119,7 +119,7 @@
}
@InputConfigFlags
- private static int applyMapping(int flags, List<FlagMapping> flagToConfigMap) {
+ private static int applyMapping(int flags, FlagMapping[] flagToConfigMap) {
int inputConfig = 0;
for (final FlagMapping mapping : flagToConfigMap) {
final boolean flagSet = (flags & mapping.mFlag) != 0;
@@ -131,7 +131,7 @@
}
@InputConfigFlags
- private static int computeMask(List<FlagMapping> flagToConfigMap) {
+ private static int computeMask(FlagMapping[] flagToConfigMap) {
int mask = 0;
for (final FlagMapping mapping : flagToConfigMap) {
mask |= mapping.mInputConfig;
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index a84ebd9..22ca82a 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -290,22 +290,6 @@
}
}
- @Override
- public void notifyPointerDisplayIdChanged(int displayId, float x, float y) {
- synchronized (mService.mGlobalLock) {
- mService.setMousePointerDisplayId(displayId);
- if (displayId == Display.INVALID_DISPLAY) return;
-
- final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
- if (dc == null) {
- Slog.wtf(TAG, "The mouse pointer was moved to display " + displayId
- + " that does not have a valid DisplayContent.");
- return;
- }
- mService.restorePointerIconLocked(dc, x, y);
- }
- }
-
/** Waits until the built-in input devices have been configured. */
public boolean waitForInputDevicesReady(long timeoutMillis) {
synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index ce1a72d..b5af806 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -19,6 +19,7 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import android.annotation.DimenRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -266,10 +267,10 @@
private boolean mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox;
// Supplier for the value in pixel to consider when detecting vertical thin letterboxing
- private final IntSupplier mThinLetterboxWidthFn;
+ private final DimenPxIntSupplier mThinLetterboxWidthPxSupplier;
// Supplier for the value in pixel to consider when detecting horizontal thin letterboxing
- private final IntSupplier mThinLetterboxHeightFn;
+ private final DimenPxIntSupplier mThinLetterboxHeightPxSupplier;
// Allows to enable letterboxing strategy for translucent activities ignoring flags.
private boolean mTranslucentLetterboxingOverrideEnabled;
@@ -307,6 +308,34 @@
// Flags dynamically updated with {@link android.provider.DeviceConfig}.
@NonNull private final SynchedDeviceConfig mDeviceConfig;
+ // Cached version of IntSupplier customised to evaluate new dimen in pixels
+ // when density changes
+ private static class DimenPxIntSupplier implements IntSupplier {
+
+ @NonNull
+ private final Context mContext;
+
+ private final int mResourceId;
+
+ private float mLastDensity = Float.MIN_VALUE;
+ private int mValue = 0;
+
+ private DimenPxIntSupplier(@NonNull Context context, @DimenRes int resourceId) {
+ mContext = context;
+ mResourceId = resourceId;
+ }
+
+ @Override
+ public int getAsInt() {
+ final float newDensity = mContext.getResources().getDisplayMetrics().density;
+ if (newDensity != mLastDensity) {
+ mLastDensity = newDensity;
+ mValue = mContext.getResources().getDimensionPixelSize(mResourceId);
+ }
+ return mValue;
+ }
+ }
+
LetterboxConfiguration(@NonNull final Context systemUiContext) {
this(systemUiContext, new LetterboxConfigurationPersister(
() -> readLetterboxHorizontalReachabilityPositionFromConfig(
@@ -364,9 +393,10 @@
R.bool.config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled);
mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled);
- mThinLetterboxWidthFn = () -> mContext.getResources().getDimensionPixelSize(
+
+ mThinLetterboxWidthPxSupplier = new DimenPxIntSupplier(mContext,
R.dimen.config_letterboxThinLetterboxWidthDp);
- mThinLetterboxHeightFn = () -> mContext.getResources().getDimensionPixelSize(
+ mThinLetterboxHeightPxSupplier = new DimenPxIntSupplier(mContext,
R.dimen.config_letterboxThinLetterboxHeightDp);
mLetterboxConfigurationPersister = letterboxConfigurationPersister;
@@ -1144,7 +1174,7 @@
* is the maximum value for (W - w) / 2 to be considered for a thin letterboxed app.
*/
int getThinLetterboxWidthPx() {
- return mThinLetterboxWidthFn.getAsInt();
+ return mThinLetterboxWidthPxSupplier.getAsInt();
}
/**
@@ -1153,7 +1183,7 @@
* value for (H - h) / 2 to be considered for a thin letterboxed app.
*/
int getThinLetterboxHeightPx() {
- return mThinLetterboxHeightFn.getAsInt();
+ return mThinLetterboxHeightPxSupplier.getAsInt();
}
/**
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6003c1b..be8c2ae 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -911,7 +911,6 @@
dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
dc.updateSystemGestureExclusion();
dc.updateKeepClearAreas();
- dc.updateTouchExcludeRegion();
});
// Check to see if we are now in a state where the screen should
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index bb86460..3b3eeb4 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -740,16 +740,6 @@
}
@Override
- public void updatePointerIcon(IWindow window) {
- final long identity = Binder.clearCallingIdentity();
- try {
- mService.updatePointerIcon(window);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
public void updateTapExcludeRegion(IWindow window, Region region) {
final long identity = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
deleted file mode 100644
index ac244c7..0000000
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_NOT_SPECIFIED;
-import static android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
-
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.hardware.input.InputManagerGlobal;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.WindowManagerPolicyConstants.PointerEventListener;
-
-import com.android.server.wm.WindowManagerService.H;
-
-/**
- * 1. Adjust the top most focus display if touch down on some display.
- * 2. Adjust the pointer icon when cursor moves to the task bounds.
- */
-public class TaskTapPointerEventListener implements PointerEventListener {
-
- private final Region mTouchExcludeRegion = new Region();
- private final WindowManagerService mService;
- private final DisplayContent mDisplayContent;
- private final Rect mTmpRect = new Rect();
- private int mPointerIconType = TYPE_NOT_SPECIFIED;
-
- public TaskTapPointerEventListener(WindowManagerService service,
- DisplayContent displayContent) {
- // TODO(b/315321016): Remove this class when the flag rollout is complete.
- if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
- throw new IllegalStateException("TaskTapPointerEventListener should not be used!");
- }
- mService = service;
- mDisplayContent = displayContent;
- }
-
- private void restorePointerIcon(int x, int y) {
- if (mPointerIconType != TYPE_NOT_SPECIFIED) {
- mPointerIconType = TYPE_NOT_SPECIFIED;
- // Find the underlying window and ask it to restore the pointer icon.
- mService.mH.removeMessages(H.RESTORE_POINTER_ICON);
- mService.mH.obtainMessage(H.RESTORE_POINTER_ICON,
- x, y, mDisplayContent).sendToTarget();
- }
- }
-
- @Override
- public void onPointerEvent(MotionEvent motionEvent) {
- switch (motionEvent.getActionMasked()) {
- case MotionEvent.ACTION_DOWN: {
- final int x;
- final int y;
- if (motionEvent.getSource() == InputDevice.SOURCE_MOUSE) {
- x = (int) motionEvent.getXCursorPosition();
- y = (int) motionEvent.getYCursorPosition();
- } else {
- x = (int) motionEvent.getX();
- y = (int) motionEvent.getY();
- }
-
- synchronized (this) {
- if (!mTouchExcludeRegion.contains(x, y)) {
- mService.mTaskPositioningController.handleTapOutsideTask(
- mDisplayContent, x, y);
- }
- }
- }
- break;
- case MotionEvent.ACTION_HOVER_ENTER:
- case MotionEvent.ACTION_HOVER_MOVE: {
- final int x = (int) motionEvent.getX();
- final int y = (int) motionEvent.getY();
- if (mTouchExcludeRegion.contains(x, y)) {
- restorePointerIcon(x, y);
- break;
- }
- final Task task = mDisplayContent.findTaskForResizePoint(x, y);
- int iconType = TYPE_NOT_SPECIFIED;
- if (task != null) {
- task.getDimBounds(mTmpRect);
- if (!mTmpRect.isEmpty() && !mTmpRect.contains(x, y)) {
- if (x < mTmpRect.left) {
- iconType =
- (y < mTmpRect.top) ? TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW :
- (y > mTmpRect.bottom) ? TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW :
- TYPE_HORIZONTAL_DOUBLE_ARROW;
- } else if (x > mTmpRect.right) {
- iconType =
- (y < mTmpRect.top) ? TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW :
- (y > mTmpRect.bottom) ? TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW :
- TYPE_HORIZONTAL_DOUBLE_ARROW;
- } else if (y < mTmpRect.top || y > mTmpRect.bottom) {
- iconType = TYPE_VERTICAL_DOUBLE_ARROW;
- }
- }
- }
- if (mPointerIconType != iconType) {
- mPointerIconType = iconType;
- if (mPointerIconType == TYPE_NOT_SPECIFIED) {
- // Find the underlying window and ask it restore the pointer icon.
- mService.mH.removeMessages(H.RESTORE_POINTER_ICON);
- mService.mH.obtainMessage(H.RESTORE_POINTER_ICON,
- x, y, mDisplayContent).sendToTarget();
- } else {
- InputManagerGlobal.getInstance()
- .setPointerIconType(mPointerIconType);
- }
- }
- }
- break;
- case MotionEvent.ACTION_HOVER_EXIT: {
- final int x = (int) motionEvent.getX();
- final int y = (int) motionEvent.getY();
- restorePointerIcon(x, y);
- }
- break;
- }
- }
-
- void setTouchExcludeRegion(Region newRegion) {
- synchronized (this) {
- mTouchExcludeRegion.set(newRegion);
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1543263..7ec31d5 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1648,14 +1648,6 @@
}
if (mController.useFullReadyTracking()) {
- if (mReadyTracker.mMet.isEmpty()) {
- Slog.e(TAG, "#" + mSyncId + ": No conditions provided");
- } else {
- for (int i = 0; i < mReadyTracker.mConditions.size(); ++i) {
- Slog.e(TAG, "#" + mSyncId + ": unmet condition at ready: "
- + mReadyTracker.mConditions.get(i));
- }
- }
for (int i = 0; i < mReadyTracker.mMet.size(); ++i) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "#%d: Met condition: %s",
mSyncId, mReadyTracker.mMet.get(i));
@@ -3360,6 +3352,18 @@
applyReady();
}
+ @Override
+ public void onReadyTimeout() {
+ if (!mController.useFullReadyTracking()) {
+ Slog.e(TAG, "#" + mSyncId + " readiness timeout, used=" + mReadyTrackerOld.mUsed
+ + " deferReadyDepth=" + mReadyTrackerOld.mDeferReadyDepth
+ + " group=" + mReadyTrackerOld.mReadyGroups);
+ return;
+ }
+ Slog.e(TAG, "#" + mSyncId + " met conditions: " + mReadyTracker.mMet);
+ Slog.e(TAG, "#" + mSyncId + " unmet conditions: " + mReadyTracker.mConditions);
+ }
+
/**
* Represents a condition that must be met before an associated transition can be considered
* ready.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index dbe3d36..e02e5be 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -81,15 +81,11 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
-import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_NONE;
@@ -203,7 +199,6 @@
import android.hardware.configstore.V1_1.ISurfaceFlingerConfigs;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
-import android.hardware.input.InputManager;
import android.hardware.input.InputSettings;
import android.net.Uri;
import android.os.Binder;
@@ -289,8 +284,6 @@
import android.view.InsetsState;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
-import android.view.MotionEvent;
-import android.view.PointerIcon;
import android.view.RemoteAnimationAdapter;
import android.view.ScrollCaptureResponse;
import android.view.Surface;
@@ -335,6 +328,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
+import com.android.internal.os.TransferPipe;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IKeyguardLockedStateListener;
import com.android.internal.policy.IShortcutService;
@@ -545,13 +539,16 @@
if (asProto) {
return;
}
+
+ final long timeoutMs = 1000L;
mAtmService.dumpActivity(fd, pw, /* name= */ "all", /* args= */ new String[]{},
/* opti= */ 0,
/* dumpAll= */ true,
/* dumpVisibleRootTasksOnly= */ true,
/* dumpFocusedRootTaskOnly= */ false, INVALID_DISPLAY, UserHandle.USER_ALL,
- /* timeout= */ 1000
+ timeoutMs
);
+ dumpVisibleWindowClients(fd, pw, timeoutMs);
}
@Override
@@ -1523,18 +1520,6 @@
}
}
- static boolean excludeWindowTypeFromTapOutTask(int windowType) {
- switch (windowType) {
- case TYPE_STATUS_BAR:
- case TYPE_NOTIFICATION_SHADE:
- case TYPE_NAVIGATION_BAR:
- case TYPE_INPUT_METHOD_DIALOG:
- case TYPE_VOLUME_OVERLAY:
- return true;
- }
- return false;
- }
-
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
@@ -1833,10 +1818,6 @@
displayContent.mWinAddedSinceNullFocus.add(win);
}
- if (excludeWindowTypeFromTapOutTask(type)) {
- displayContent.mTapExcludedWindows.add(win);
- }
-
win.mSession.onWindowAdded(win);
mWindowMap.put(client.asBinder(), win);
win.initAppOpsState();
@@ -5716,7 +5697,6 @@
public static final int UPDATE_ANIMATION_SCALE = 51;
public static final int WINDOW_HIDE_TIMEOUT = 52;
- public static final int RESTORE_POINTER_ICON = 55;
public static final int SET_HAS_OVERLAY_UI = 58;
public static final int ANIMATION_FAILSAFE = 60;
public static final int RECOMPUTE_FOCUS = 61;
@@ -5949,12 +5929,6 @@
}
break;
}
- case RESTORE_POINTER_ICON: {
- synchronized (mGlobalLock) {
- restorePointerIconLocked((DisplayContent)msg.obj, msg.arg1, msg.arg2);
- }
- break;
- }
case SET_HAS_OVERLAY_UI: {
mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1);
break;
@@ -7574,144 +7548,6 @@
}
}
- // The mouse position tracker will be obsolete after the Pointer Icon Refactor.
- // TODO(b/293587049): Remove after the refactoring is fully rolled out.
- @Nullable
- final MousePositionTracker mMousePositionTracker =
- com.android.input.flags.Flags.enablePointerChoreographer() ? null
- : new MousePositionTracker();
-
- private static class MousePositionTracker implements PointerEventListener {
- private boolean mLatestEventWasMouse;
- private float mLatestMouseX;
- private float mLatestMouseY;
-
- /**
- * The display that the pointer (mouse cursor) is currently shown on. This is updated
- * directly by InputManagerService when the pointer display changes.
- */
- private int mPointerDisplayId = INVALID_DISPLAY;
-
- /**
- * Update the mouse cursor position as a result of a mouse movement.
- * @return true if the position was successfully updated, false otherwise.
- */
- boolean updatePosition(int displayId, float x, float y) {
- synchronized (this) {
- mLatestEventWasMouse = true;
-
- if (displayId != mPointerDisplayId) {
- // The display of the position update does not match the display on which the
- // mouse pointer is shown, so do not update the position.
- return false;
- }
- mLatestMouseX = x;
- mLatestMouseY = y;
- return true;
- }
- }
-
- void setPointerDisplayId(int displayId) {
- synchronized (this) {
- mPointerDisplayId = displayId;
- }
- }
-
- @Override
- public void onPointerEvent(MotionEvent motionEvent) {
- if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)) {
- updatePosition(motionEvent.getDisplayId(), motionEvent.getRawX(),
- motionEvent.getRawY());
- } else {
- synchronized (this) {
- mLatestEventWasMouse = false;
- }
- }
- }
- };
-
- void updatePointerIcon(IWindow client) {
- if (mMousePositionTracker == null) {
- return;
- }
- int pointerDisplayId;
- float mouseX, mouseY;
-
- synchronized(mMousePositionTracker) {
- if (!mMousePositionTracker.mLatestEventWasMouse) {
- return;
- }
- mouseX = mMousePositionTracker.mLatestMouseX;
- mouseY = mMousePositionTracker.mLatestMouseY;
- pointerDisplayId = mMousePositionTracker.mPointerDisplayId;
- }
-
- synchronized (mGlobalLock) {
- if (mDragDropController.dragDropActiveLocked()) {
- // Drag cursor overrides the app cursor.
- return;
- }
- WindowState callingWin = windowForClientLocked(null, client, false);
- if (callingWin == null) {
- ProtoLog.w(WM_ERROR, "Bad requesting window %s", client);
- return;
- }
- final DisplayContent displayContent = callingWin.getDisplayContent();
- if (displayContent == null) {
- return;
- }
- if (pointerDisplayId != displayContent.getDisplayId()) {
- // Do not let the pointer icon be updated by a window on a different display.
- return;
- }
- WindowState windowUnderPointer =
- displayContent.getTouchableWinAtPointLocked(mouseX, mouseY);
- if (windowUnderPointer != callingWin) {
- return;
- }
- try {
- windowUnderPointer.mClient.updatePointerIcon(
- windowUnderPointer.translateToWindowX(mouseX),
- windowUnderPointer.translateToWindowY(mouseY));
- } catch (RemoteException e) {
- ProtoLog.w(WM_ERROR, "unable to update pointer icon");
- }
- }
- }
-
- void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
- if (mMousePositionTracker == null) {
- return;
- }
- // Mouse position tracker has not been getting updates while dragging, update it now.
- if (!mMousePositionTracker.updatePosition(
- displayContent.getDisplayId(), latestX, latestY)) {
- // The mouse position could not be updated, so ignore this request.
- return;
- }
-
- WindowState windowUnderPointer =
- displayContent.getTouchableWinAtPointLocked(latestX, latestY);
- if (windowUnderPointer != null) {
- try {
- windowUnderPointer.mClient.updatePointerIcon(
- windowUnderPointer.translateToWindowX(latestX),
- windowUnderPointer.translateToWindowY(latestY));
- } catch (RemoteException e) {
- ProtoLog.w(WM_ERROR, "unable to restore pointer icon");
- }
- } else {
- mContext.getSystemService(InputManager.class)
- .setPointerIconType(PointerIcon.TYPE_DEFAULT);
- }
- }
- void setMousePointerDisplayId(int displayId) {
- if (mMousePositionTracker == null) {
- return;
- }
- mMousePositionTracker.setPointerDisplayId(displayId);
- }
-
/**
* Update a tap exclude region in the window identified by the provided id. Touches down on this
* region will not:
@@ -10350,4 +10186,32 @@
}
return true;
}
+
+ /**
+ * Dump ViewRootImpl for visible non-activity windows.
+ */
+ private void dumpVisibleWindowClients(FileDescriptor fd, PrintWriter pw, long timeout) {
+ final ArrayList<WindowState> systemWindows = new ArrayList<>();
+ synchronized (mGlobalLock) {
+ mRoot.forAllWindows(w -> {
+ if (!w.isActivityWindow() && w.isVisibleNow()) {
+ systemWindows.add(w);
+ }
+ }, false /* traverseTopToBottom */);
+ }
+
+ systemWindows.forEach(w -> {
+ pw.println("---------------------------------");
+ pw.println(w.toString());
+ pw.flush();
+ try (TransferPipe tp = new TransferPipe()) {
+ w.mClient.dumpWindow(tp.getWriteFd());
+ tp.go(fd, timeout);
+ } catch (IOException e) {
+ pw.println("Failure while dumping the window: " + e);
+ } catch (RemoteException e) {
+ pw.println("Got a RemoteException while dumping the window");
+ }
+ });
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index e90c845..8fb83fa 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1447,14 +1447,17 @@
+ " last=" + mWindowFrames.mLastFrame + " frame=" + mWindowFrames.mFrame);
}
+ final boolean contentChanged = didFrameInsetsChange || configChanged
+ || dragResizingChanged || attachedFrameChanged;
+ // Cancel unchanged non-sync-buffer redraw request to avoid unnecessary reportResized().
+ if (!contentChanged && !mRedrawForSyncReported && mPrepareSyncSeqId <= 0
+ && mDrawHandlers.isEmpty()) {
+ mRedrawForSyncReported = true;
+ }
+
// Add a window that is using blastSync to the resizing list if it hasn't been reported
// already. This because the window is waiting on a finishDrawing from the client.
- if (didFrameInsetsChange
- || configChanged
- || insetsChanged
- || dragResizingChanged
- || shouldSendRedrawForSync()
- || attachedFrameChanged) {
+ if (contentChanged || insetsChanged || shouldSendRedrawForSync()) {
ProtoLog.v(WM_DEBUG_RESIZE,
"Resize reasons for w=%s: %s configChanged=%b didFrameInsetsChange=%b",
this, mWindowFrames.getInsetsChangedInfo(),
@@ -2359,18 +2362,12 @@
}
final int type = mAttrs.type;
- if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) {
- dc.mTapExcludedWindows.remove(this);
- }
if (type == TYPE_PRESENTATION || type == TYPE_PRIVATE_PRESENTATION) {
mWmService.mDisplayManagerInternal.onPresentation(dc.getDisplay().getDisplayId(),
/*isShown=*/ false);
}
- // Remove this window from mTapExcludeProvidingWindows. If it was not registered, this will
- // not do anything.
- dc.mTapExcludeProvidingWindows.remove(this);
dc.getDisplayPolicy().removeWindowLw(this);
disposeInputChannel();
@@ -5526,18 +5523,10 @@
// Clear the tap excluded region if the region passed in is null or empty.
if (region == null || region.isEmpty()) {
mTapExcludeRegion.setEmpty();
- // Remove this window from mTapExcludeProvidingWindows since it won't be providing
- // tap exclude regions.
- currentDisplay.mTapExcludeProvidingWindows.remove(this);
} else {
mTapExcludeRegion.set(region);
- // Make sure that this window is registered as one that provides a tap exclude region
- // for its containing display.
- currentDisplay.mTapExcludeProvidingWindows.add(this);
}
- // Trigger touch exclude region update on current display.
- currentDisplay.updateTouchExcludeRegion();
// Trigger touchable region update for this window.
currentDisplay.getInputMonitor().updateInputWindowsLw(true /* force */);
}
@@ -6074,6 +6063,10 @@
return mPrepareSyncSeqId > 0;
}
+ public boolean isActivityWindow() {
+ return mActivityRecord != null;
+ }
+
void setSecureLocked(boolean isSecure) {
ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isSecure=%b: %s", isSecure, getName());
if (secureWindowState()) {
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
index e65b903..22c0f73 100644
--- a/services/core/jni/com_android_server_display_DisplayControl.cpp
+++ b/services/core/jni/com_android_server_display_DisplayControl.cpp
@@ -24,9 +24,11 @@
namespace android {
static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure,
- jfloat requestedRefreshRate) {
- ScopedUtfChars name(env, nameObj);
+ jstring uniqueIdStr, jfloat requestedRefreshRate) {
+ const ScopedUtfChars name(env, nameObj);
+ const ScopedUtfChars uniqueId(env, uniqueIdStr);
sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure),
+ std::string(uniqueId.c_str()),
requestedRefreshRate));
return javaObjectForIBinder(env, token);
}
@@ -178,7 +180,7 @@
static const JNINativeMethod sDisplayMethods[] = {
// clang-format off
- {"nativeCreateDisplay", "(Ljava/lang/String;ZF)Landroid/os/IBinder;",
+ {"nativeCreateDisplay", "(Ljava/lang/String;ZLjava/lang/String;F)Landroid/os/IBinder;",
(void*)nativeCreateDisplay },
{"nativeDestroyDisplay", "(Landroid/os/IBinder;)V",
(void*)nativeDestroyDisplay },
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 62f5b89..a01c123 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -127,7 +127,7 @@
jmethodID getVirtualKeyQuietTimeMillis;
jmethodID getExcludedDeviceNames;
jmethodID getInputPortAssociations;
- jmethodID getInputUniqueIdAssociations;
+ jmethodID getInputUniqueIdAssociationsByPort;
jmethodID getInputUniqueIdAssociationsByDescriptor;
jmethodID getDeviceTypeAssociations;
jmethodID getKeyboardLayoutAssociations;
@@ -272,22 +272,23 @@
void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name);
- base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
+ base::Result<std::unique_ptr<InputChannel>> createInputMonitor(ui::LogicalDisplayId displayId,
const std::string& name,
gui::Pid pid);
status_t removeInputChannel(const sp<IBinder>& connectionToken);
status_t pilferPointers(const sp<IBinder>& token);
- void displayRemoved(JNIEnv* env, int32_t displayId);
- void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj);
- void setFocusedDisplay(int32_t displayId);
+ void displayRemoved(JNIEnv* env, ui::LogicalDisplayId displayId);
+ void setFocusedApplication(JNIEnv* env, ui::LogicalDisplayId displayId,
+ jobject applicationHandleObj);
+ void setFocusedDisplay(ui::LogicalDisplayId displayId);
void setMinTimeBetweenUserActivityPokes(int64_t intervalMillis);
void setInputDispatchMode(bool enabled, bool frozen);
void setSystemUiLightsOut(bool lightsOut);
- void setPointerDisplayId(int32_t displayId);
+ void setPointerDisplayId(ui::LogicalDisplayId displayId);
int32_t getMousePointerSpeed();
void setPointerSpeed(int32_t speed);
- void setMousePointerAccelerationEnabled(int32_t displayId, bool enabled);
+ void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled);
void setTouchpadPointerSpeed(int32_t speed);
void setTouchpadNaturalScrollingEnabled(bool enabled);
void setTouchpadTapToClickEnabled(bool enabled);
@@ -300,13 +301,13 @@
void reloadPointerIcons();
void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
- int32_t displayId, DeviceId deviceId, int32_t pointerId,
+ ui::LogicalDisplayId displayId, DeviceId deviceId, int32_t pointerId,
const sp<IBinder>& inputToken);
- void setPointerIconVisibility(int32_t displayId, bool visible);
+ void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible);
void setMotionClassifierEnabled(bool enabled);
std::optional<std::string> getBluetoothAddress(int32_t deviceId);
void setStylusButtonMotionEventsEnabled(bool enabled);
- FloatPoint getMouseCursorPosition(int32_t displayId);
+ FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId);
void setStylusPointerIconEnabled(bool enabled);
void setInputMethodConnectionIsActive(bool isActive);
@@ -325,7 +326,7 @@
void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override;
bool isInputMethodConnectionActive() override;
std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
- int32_t associatedDisplayId) override;
+ ui::LogicalDisplayId associatedDisplayId) override;
/* --- InputDispatcherPolicyInterface implementation --- */
@@ -348,13 +349,15 @@
void notifyVibratorState(int32_t deviceId, bool isOn) override;
bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override;
void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) override;
- void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action,
- nsecs_t when, uint32_t& policyFlags) override;
+ void interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId, uint32_t source,
+ int32_t action, nsecs_t when,
+ uint32_t& policyFlags) override;
nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent,
uint32_t policyFlags) override;
std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent& keyEvent,
uint32_t policyFlags) override;
- void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
+ void pokeUserActivity(nsecs_t eventTime, int32_t eventType,
+ ui::LogicalDisplayId displayId) override;
void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
void setPointerCapture(const PointerCaptureRequest& request) override;
void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
@@ -363,11 +366,13 @@
/* --- PointerControllerPolicyInterface implementation --- */
- virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId);
- virtual void loadPointerResources(PointerResources* outResources, int32_t displayId);
+ virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId);
+ virtual void loadPointerResources(PointerResources* outResources,
+ ui::LogicalDisplayId displayId);
virtual void loadAdditionalMouseResources(
std::map<PointerIconStyle, SpriteIcon>* outResources,
- std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t displayId);
+ std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
+ ui::LogicalDisplayId displayId);
virtual PointerIconStyle getDefaultPointerIconId();
virtual PointerIconStyle getDefaultStylusIconId();
virtual PointerIconStyle getCustomPointerIconId();
@@ -375,7 +380,8 @@
/* --- PointerChoreographerPolicyInterface implementation --- */
std::shared_ptr<PointerControllerInterface> createPointerController(
PointerControllerInterface::ControllerType type) override;
- void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override;
+ void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
+ const FloatPoint& position) override;
/* --- InputFilterPolicyInterface implementation --- */
void notifyStickyModifierStateChanged(uint32_t modifierState,
@@ -399,7 +405,7 @@
int32_t pointerSpeed{0};
// Displays on which its associated mice will have pointer acceleration disabled.
- std::set<int32_t> displaysWithMousePointerAccelerationDisabled{};
+ std::set<ui::LogicalDisplayId> displaysWithMousePointerAccelerationDisabled{};
// True if pointer gestures are enabled.
bool pointerGesturesEnabled{true};
@@ -417,7 +423,7 @@
std::set<int32_t> disabledInputDevices{};
// Associated Pointer controller display.
- int32_t pointerDisplayId{ADISPLAY_ID_DEFAULT};
+ ui::LogicalDisplayId pointerDisplayId{ui::ADISPLAY_ID_DEFAULT};
// True if stylus button reporting through motion events is enabled.
bool stylusButtonMotionEventsEnabled{true};
@@ -450,7 +456,7 @@
void updateInactivityTimeoutLocked();
void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
void ensureSpriteControllerLocked();
- sp<SurfaceControl> getParentSurfaceForPointers(int displayId);
+ sp<SurfaceControl> getParentSurfaceForPointers(ui::LogicalDisplayId displayId);
static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
template <typename T>
std::unordered_map<std::string, T> readMapFromInterleavedJavaArray(
@@ -459,7 +465,7 @@
void forEachPointerControllerLocked(std::function<void(PointerController&)> apply)
REQUIRES(mLock);
- PointerIcon loadPointerIcon(JNIEnv* env, int32_t displayId, PointerIconStyle type);
+ PointerIcon loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId, PointerIconStyle type);
static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); }
};
@@ -490,7 +496,9 @@
toString(mLocked.systemUiLightsOut));
dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
dump += StringPrintf(INDENT "Display with Mouse Pointer Acceleration Disabled: %s\n",
- dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled).c_str());
+ dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled,
+ streamableToString)
+ .c_str());
dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
@@ -552,7 +560,7 @@
}
base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor(
- int32_t displayId, const std::string& name, gui::Pid pid) {
+ ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid) {
ATRACE_CALL();
return mInputManager->getDispatcher().createInputMonitor(displayId, name, pid);
}
@@ -616,10 +624,9 @@
env->DeleteLocalRef(portAssociations);
}
- outConfig->uniqueIdAssociationsByPort =
- readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo
- .getInputUniqueIdAssociations,
- "getInputUniqueIdAssociations");
+ outConfig->uniqueIdAssociationsByPort = readMapFromInterleavedJavaArray<
+ std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByPort,
+ "getInputUniqueIdAssociationsByPort");
outConfig->uniqueIdAssociationsByDescriptor = readMapFromInterleavedJavaArray<
std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor,
@@ -735,7 +742,7 @@
}
}
-PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, int32_t displayId,
+PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId,
PointerIconStyle type) {
if (type == PointerIconStyle::TYPE_CUSTOM) {
LOG(FATAL) << __func__ << ": Cannot load non-system icon type";
@@ -766,7 +773,7 @@
return pc;
}
-void NativeInputManager::notifyPointerDisplayIdChanged(int32_t pointerDisplayId,
+void NativeInputManager::notifyPointerDisplayIdChanged(ui::LogicalDisplayId pointerDisplayId,
const FloatPoint& position) {
// Notify the Reader so that devices can be reconfigured.
{ // acquire lock
@@ -775,7 +782,7 @@
return;
}
mLocked.pointerDisplayId = pointerDisplayId;
- ALOGI("%s: pointer displayId set to: %d", __func__, pointerDisplayId);
+ ALOGI("%s: pointer displayId set to: %s", __func__, pointerDisplayId.toString().c_str());
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
InputReaderConfiguration::Change::DISPLAY_INFO);
@@ -795,7 +802,7 @@
checkAndClearExceptionFromCallback(env, "notifyStickyModifierStateChanged");
}
-sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(int displayId) {
+sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(ui::LogicalDisplayId displayId) {
JNIEnv* env = jniEnv();
jlong nativeSurfaceControlPtr =
env->CallLongMethod(mServiceObj, gServiceClassInfo.getParentSurfaceForPointers,
@@ -817,9 +824,10 @@
layer = -1;
}
mLocked.spriteController =
- std::make_shared<SpriteController>(mLooper, layer, [this](int displayId) {
- return getParentSurfaceForPointers(displayId);
- });
+ std::make_shared<SpriteController>(mLooper, layer,
+ [this](ui::LogicalDisplayId displayId) {
+ return getParentSurfaceForPointers(displayId);
+ });
// The SpriteController needs to be shared pointer because the handler callback needs to hold
// a weak reference so that we can avoid racy conditions when the controller is being destroyed.
mLocked.spriteController->setHandlerController(mLocked.spriteController);
@@ -1021,8 +1029,7 @@
jobject tokenObj = javaObjectForIBinder(env, token);
if (tokenObj) {
- env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken,
- tokenObj);
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken, tokenObj);
checkAndClearExceptionFromCallback(env, "notifyInputChannelBroken");
}
}
@@ -1108,12 +1115,12 @@
checkAndClearExceptionFromCallback(env, "notifyVibratorState");
}
-void NativeInputManager::displayRemoved(JNIEnv* env, int32_t displayId) {
+void NativeInputManager::displayRemoved(JNIEnv* env, ui::LogicalDisplayId displayId) {
mInputManager->getDispatcher().displayRemoved(displayId);
}
-void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId,
- jobject applicationHandleObj) {
+void NativeInputManager::setFocusedApplication(JNIEnv* env, ui::LogicalDisplayId displayId,
+ jobject applicationHandleObj) {
if (!applicationHandleObj) {
return;
}
@@ -1123,7 +1130,7 @@
mInputManager->getDispatcher().setFocusedApplication(displayId, applicationHandle);
}
-void NativeInputManager::setFocusedDisplay(int32_t displayId) {
+void NativeInputManager::setFocusedDisplay(ui::LogicalDisplayId displayId) {
mInputManager->getDispatcher().setFocusedDisplay(displayId);
}
@@ -1151,7 +1158,7 @@
});
}
-void NativeInputManager::setPointerDisplayId(int32_t displayId) {
+void NativeInputManager::setPointerDisplayId(ui::LogicalDisplayId displayId) {
mInputManager->getChoreographer().setDefaultMouseDisplayId(displayId);
}
@@ -1176,7 +1183,8 @@
InputReaderConfiguration::Change::POINTER_SPEED);
}
-void NativeInputManager::setMousePointerAccelerationEnabled(int32_t displayId, bool enabled) {
+void NativeInputManager::setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId,
+ bool enabled) {
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -1186,8 +1194,8 @@
return;
}
- ALOGI("Setting mouse pointer acceleration to %s on display %d", toString(enabled),
- displayId);
+ ALOGI("Setting mouse pointer acceleration to %s on display %s", toString(enabled),
+ displayId.toString().c_str());
if (enabled) {
mLocked.displaysWithMousePointerAccelerationDisabled.erase(displayId);
} else {
@@ -1326,8 +1334,9 @@
}
bool NativeInputManager::setPointerIcon(
- std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId,
- DeviceId deviceId, int32_t pointerId, const sp<IBinder>& inputToken) {
+ std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
+ ui::LogicalDisplayId displayId, DeviceId deviceId, int32_t pointerId,
+ const sp<IBinder>& inputToken) {
if (!mInputManager->getDispatcher().isPointerInWindow(inputToken, displayId, deviceId,
pointerId)) {
LOG(WARNING) << "Attempted to change the pointer icon for deviceId " << deviceId
@@ -1339,7 +1348,7 @@
return mInputManager->getChoreographer().setPointerIcon(std::move(icon), displayId, deviceId);
}
-void NativeInputManager::setPointerIconVisibility(int32_t displayId, bool visible) {
+void NativeInputManager::setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) {
mInputManager->getChoreographer().setPointerIconVisibility(displayId, visible);
}
@@ -1394,7 +1403,7 @@
}
std::optional<DisplayViewport> NativeInputManager::getPointerViewportForAssociatedDisplay(
- int32_t associatedDisplayId) {
+ ui::LogicalDisplayId associatedDisplayId) {
return mInputManager->getChoreographer().getViewportForPointerDevice(associatedDisplayId);
}
@@ -1469,9 +1478,9 @@
handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
}
-void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, uint32_t source,
- int32_t action, nsecs_t when,
- uint32_t& policyFlags) {
+void NativeInputManager::interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId,
+ uint32_t source, int32_t action,
+ nsecs_t when, uint32_t& policyFlags) {
ATRACE_CALL();
// Policy:
// - Ignore untrusted events and pass them along.
@@ -1590,7 +1599,8 @@
return fallbackEvent;
}
-void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) {
+void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType,
+ ui::LogicalDisplayId displayId) {
ATRACE_CALL();
android_server_PowerManagerService_userActivity(eventTime, eventType, displayId);
}
@@ -1621,13 +1631,14 @@
InputReaderConfiguration::Change::POINTER_CAPTURE);
}
-void NativeInputManager::loadPointerIcon(SpriteIcon* icon, int32_t displayId) {
+void NativeInputManager::loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) {
ATRACE_CALL();
JNIEnv* env = jniEnv();
*icon = toSpriteIcon(loadPointerIcon(env, displayId, PointerIconStyle::TYPE_ARROW));
}
-void NativeInputManager::loadPointerResources(PointerResources* outResources, int32_t displayId) {
+void NativeInputManager::loadPointerResources(PointerResources* outResources,
+ ui::LogicalDisplayId displayId) {
ATRACE_CALL();
JNIEnv* env = jniEnv();
@@ -1641,7 +1652,8 @@
void NativeInputManager::loadAdditionalMouseResources(
std::map<PointerIconStyle, SpriteIcon>* outResources,
- std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t displayId) {
+ std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
+ ui::LogicalDisplayId displayId) {
ATRACE_CALL();
JNIEnv* env = jniEnv();
@@ -1708,7 +1720,7 @@
InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING);
}
-FloatPoint NativeInputManager::getMouseCursorPosition(int32_t displayId) {
+FloatPoint NativeInputManager::getMouseCursorPosition(ui::LogicalDisplayId displayId) {
return mInputManager->getChoreographer().getMouseCursorPosition(displayId);
}
@@ -1874,7 +1886,7 @@
jstring nameObj, jint pid) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- if (displayId == ADISPLAY_ID_NONE) {
+ if (displayId == ui::ADISPLAY_ID_NONE.val()) {
std::string message = "InputChannel used as a monitor must be associated with a display";
jniThrowRuntimeException(env, message.c_str());
return nullptr;
@@ -1884,7 +1896,7 @@
std::string name = nameChars.c_str();
base::Result<std::unique_ptr<InputChannel>> inputChannel =
- im->createInputMonitor(displayId, name, gui::Pid{pid});
+ im->createInputMonitor(ui::LogicalDisplayId{displayId}, name, gui::Pid{pid});
if (!inputChannel.ok()) {
std::string message = inputChannel.error().message();
@@ -1931,7 +1943,8 @@
return im->getInputManager()->getDispatcher().setInTouchMode(inTouchMode, gui::Pid{pid},
gui::Uid{static_cast<uid_t>(uid)},
- hasPermission, displayId);
+ hasPermission,
+ ui::LogicalDisplayId{displayId});
}
static void nativeSetMaximumObscuringOpacityForTouch(JNIEnv* env, jobject nativeImplObj,
@@ -2023,20 +2036,20 @@
static void nativeDisplayRemoved(JNIEnv* env, jobject nativeImplObj, jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->displayRemoved(env, displayId);
+ im->displayRemoved(env, ui::LogicalDisplayId{displayId});
}
static void nativeSetFocusedApplication(JNIEnv* env, jobject nativeImplObj, jint displayId,
jobject applicationHandleObj) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setFocusedApplication(env, displayId, applicationHandleObj);
+ im->setFocusedApplication(env, ui::LogicalDisplayId{displayId}, applicationHandleObj);
}
static void nativeSetFocusedDisplay(JNIEnv* env, jobject nativeImplObj, jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setFocusedDisplay(displayId);
+ im->setFocusedDisplay(ui::LogicalDisplayId{displayId});
}
static void nativeSetUserActivityPokeInterval(JNIEnv* env, jobject nativeImplObj,
@@ -2092,8 +2105,8 @@
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
if (im->getInputManager()->getDispatcher().transferTouchOnDisplay(destChannelToken,
- static_cast<int32_t>(
- displayId))) {
+ ui::LogicalDisplayId{
+ displayId})) {
return JNI_TRUE;
} else {
return JNI_FALSE;
@@ -2116,7 +2129,7 @@
jint displayId, jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setMousePointerAccelerationEnabled(displayId, enabled);
+ im->setMousePointerAccelerationEnabled(ui::LogicalDisplayId{displayId}, enabled);
}
static void nativeSetTouchpadPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
@@ -2486,7 +2499,7 @@
icon = pointerIcon.style;
}
- return im->setPointerIcon(std::move(icon), displayId, deviceId, pointerId,
+ return im->setPointerIcon(std::move(icon), ui::LogicalDisplayId{displayId}, deviceId, pointerId,
ibinderForJavaObject(env, inputTokenObj));
}
@@ -2494,13 +2507,14 @@
jboolean visible) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setPointerIconVisibility(displayId, visible);
+ im->setPointerIconVisibility(ui::LogicalDisplayId{displayId}, visible);
}
static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- return im->getInputManager()->getReader().canDispatchToDisplay(deviceId, displayId);
+ return im->getInputManager()->getReader().canDispatchToDisplay(deviceId,
+ ui::LogicalDisplayId{displayId});
}
static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplObj) {
@@ -2512,8 +2526,9 @@
static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj,
jint displayId, jboolean isEligible) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->getInputManager()->getDispatcher().setDisplayEligibilityForPointerCapture(displayId,
- isEligible);
+ im->getInputManager()
+ ->getDispatcher()
+ .setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId{displayId}, isEligible);
}
static void nativeChangeUniqueIdAssociation(JNIEnv* env, jobject nativeImplObj) {
@@ -2649,7 +2664,7 @@
static void nativeSetPointerDisplayId(JNIEnv* env, jobject nativeImplObj, jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setPointerDisplayId(displayId);
+ im->setPointerDisplayId(ui::LogicalDisplayId{displayId});
}
static jstring nativeGetBluetoothAddress(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
@@ -2667,7 +2682,7 @@
static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj,
jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- const auto p = im->getMouseCursorPosition(displayId);
+ const auto p = im->getMouseCursorPosition(ui::LogicalDisplayId{displayId});
const std::array<float, 2> arr = {{p.x, p.y}};
jfloatArray outArr = env->NewFloatArray(2);
env->SetFloatArrayRegion(outArr, 0, arr.size(), arr.data());
@@ -2930,8 +2945,8 @@
GET_METHOD_ID(gServiceClassInfo.getInputPortAssociations, clazz,
"getInputPortAssociations", "()[Ljava/lang/String;");
- GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociations, clazz,
- "getInputUniqueIdAssociations", "()[Ljava/lang/String;");
+ GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociationsByPort, clazz,
+ "getInputUniqueIdAssociationsByPort", "()[Ljava/lang/String;");
GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor, clazz,
"getInputUniqueIdAssociationsByDescriptor", "()[Ljava/lang/String;");
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index d0b290c..0733968 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -99,7 +99,7 @@
}
void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType,
- int32_t displayId) {
+ ui::LogicalDisplayId displayId) {
if (gPowerManagerServiceObj) {
// Throttle calls into user activity by event type.
// We're a little conservative about argument checking here in case the caller
@@ -124,8 +124,8 @@
JNIEnv* env = AndroidRuntime::getJNIEnv();
env->CallVoidMethod(gPowerManagerServiceObj,
- gPowerManagerServiceClassInfo.userActivityFromNative,
- nanoseconds_to_milliseconds(eventTime), eventType, displayId, 0);
+ gPowerManagerServiceClassInfo.userActivityFromNative,
+ nanoseconds_to_milliseconds(eventTime), eventType, displayId.val(), 0);
checkAndClearExceptionFromCallback(env, "userActivityFromNative");
}
}
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.h b/services/core/jni/com_android_server_power_PowerManagerService.h
index 36aaceb..ed7fa7c 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.h
+++ b/services/core/jni/com_android_server_power_PowerManagerService.h
@@ -19,6 +19,7 @@
#include <nativehelper/JNIHelp.h>
#include <powermanager/PowerManager.h>
+#include <ui/LogicalDisplayId.h>
#include <utils/Timers.h>
#include "jni.h"
@@ -26,7 +27,7 @@
namespace android {
extern void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType,
- int32_t displayId);
+ ui::LogicalDisplayId displayId);
} // namespace android
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 0c83e8e..2e126f1 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -295,7 +295,7 @@
}
}
- private static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) {
+ static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) {
final int resolvedUserId = ActivityManager.handleIncomingUser(
Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, false,
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index 38ad5b6..b86db06 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -16,6 +16,8 @@
package com.android.server.credentials;
+import static com.android.server.credentials.CredentialManagerService.getPrimaryProvidersForUserId;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
@@ -30,6 +32,7 @@
import com.android.server.infra.AbstractPerUserSystemService;
import java.util.List;
+import java.util.Set;
/**
@@ -80,9 +83,12 @@
Slog.i(TAG, "newServiceInfoLocked, mInfo null, "
+ serviceComponent.flattenToString());
}
+ Set<ComponentName> primaryProviders =
+ getPrimaryProvidersForUserId(mMaster.getContext(), mUserId);
mInfo = CredentialProviderInfoFactory.create(
getContext(), serviceComponent,
- mUserId, /*isSystemProvider=*/false);
+ mUserId, /*isSystemProvider=*/false,
+ primaryProviders.contains(serviceComponent));
return mInfo.getServiceInfo();
}
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index b1673e2..7a026d5 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -67,6 +67,9 @@
private final ResultReceiver mAutofillCallback;
+ @Nullable
+ private ComponentName mPrimaryProviderComponentName = null;
+
public GetCandidateRequestSession(
Context context, SessionLifetime sessionCallback,
Object lock, int userId, int callingUid,
@@ -104,8 +107,12 @@
if (providerGetCandidateSessions != null) {
Slog.d(TAG, "In startProviderSession - provider session created and "
+ "being added for: " + providerInfo.getComponentName());
- mProviders.put(providerGetCandidateSessions.getComponentName().flattenToString(),
- providerGetCandidateSessions);
+ ComponentName componentName = providerGetCandidateSessions
+ .getComponentName();
+ if (providerInfo.isPrimary()) {
+ mPrimaryProviderComponentName = componentName;
+ }
+ mProviders.put(componentName.flattenToString(), providerGetCandidateSessions);
}
return providerGetCandidateSessions;
}
@@ -138,7 +145,7 @@
try {
invokeClientCallbackSuccess(new GetCandidateCredentialsResponse(
- candidateProviderDataList, intent));
+ candidateProviderDataList, intent, mPrimaryProviderComponentName));
} catch (RemoteException e) {
Slog.e(TAG, "Issue while responding to client with error : " + e);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 375fc5a..f75803f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -15172,10 +15172,8 @@
if (statusBarService != null) {
int flags1 = disabled ? STATUS_BAR_DISABLE_MASK : StatusBarManager.DISABLE_NONE;
int flags2 = disabled ? STATUS_BAR_DISABLE2_MASK : StatusBarManager.DISABLE2_NONE;
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(flags1,
- flags2);
- statusBarService.disableForUser(info, mToken, mContext.getPackageName(), userId,
- "setStatusBarDisabledInternal");
+ statusBarService.disableForUser(flags1, mToken, mContext.getPackageName(), userId);
+ statusBarService.disable2ForUser(flags2, mToken, mContext.getPackageName(), userId);
return true;
}
} catch (RemoteException e) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 4bf3ff4..09eef45 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -196,19 +196,27 @@
Binder.withCleanCallingIdentity(() -> {
PackageManagerInternal pmi =
LocalServices.getService(PackageManagerInternal.class);
+ AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+
pmi.setOwnerProtectedPackages(userId,
packages == null ? null : packages.stream().toList());
LocalServices.getService(UsageStatsManagerInternal.class)
.setAdminProtectedPackages(
packages == null ? null : new ArraySet<>(packages), userId);
- if (Flags.disallowUserControlBgUsageFix()) {
- if (packages == null) {
- return;
+ if (packages == null || packages.isEmpty()) {
+ return;
+ }
+
+ for (int user : resolveUsers(userId)) {
+ if (Flags.disallowUserControlBgUsageFix()) {
+ setBgUsageAppOp(packages, pmi, user, appOpsManager);
}
- final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
- resolveUsers(userId).forEach(
- user -> setBgUsageAppOp(packages, pmi, user, appOpsManager));
+ if (Flags.disallowUserControlStoppedStateFix()) {
+ for (String packageName : packages) {
+ pmi.setPackageStoppedState(packageName, false, user);
+ }
+ }
}
});
return true;
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index b155829..6499556 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -2046,8 +2046,20 @@
writer.println("Unknown app ID $appId.")
}
}
+ } else if (args[0] == "--package" && args.size == 2) {
+ val packageName = args[1]
+ service.getState {
+ val packageState = state.externalState.packageStates[packageName]
+ if (packageState != null) {
+ writer.dumpAppIdState(packageState.appId, state, indexedSetOf(packageName))
+ } else {
+ writer.println("Unknown package $packageName.")
+ }
+ }
} else {
- writer.println("Usage: dumpsys permission [--app-id APP_ID]")
+ writer.println(
+ "Usage: dumpsys permissionmgr [--app-id <APP_ID>] [--package <PACKAGE_NAME>]"
+ )
}
}
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 622e702..54d101a 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -243,12 +243,12 @@
return;
}
sSelfService.mIProfcollect.process();
- jobFinished(params, false);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Failed to process profiles in background: "
+ e.getMessage());
}
});
+ jobFinished(params, false);
return true;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 1666fef..54b2d4d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -282,7 +282,7 @@
return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
new VirtualDisplayAdapter.SurfaceControlDisplayFactory() {
@Override
- public IBinder createDisplay(String name, boolean secure,
+ public IBinder createDisplay(String name, boolean secure, String uniqueId,
float requestedRefreshRate) {
return mMockDisplayToken;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 80f38eb..e5685c7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1790,6 +1790,7 @@
verify(mHolder.animator).animateTo(eq(brightness),
/* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
/* ignoreAnimationLimits= */ anyBoolean());
+ verify(mHolder.brightnessSetting).setBrightness(brightness);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 4a21645..42814e7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -239,6 +239,9 @@
@Test
public void testBootLoopDetectionWithExecutionForAllRescueLevels() {
+ // this is old test where the flag needs to be disabled
+ mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+
RescueParty.onSettingsProviderPublished(mMockContext);
verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
any(Executor.class),
@@ -449,6 +452,9 @@
@Test
public void testNonPersistentAppCrashDetectionWithScopedResets() {
+ // this is old test where the flag needs to be disabled
+ mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+
RescueParty.onSettingsProviderPublished(mMockContext);
verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
any(Executor.class),
@@ -506,6 +512,9 @@
@Test
public void testNonDeviceConfigSettingsOnlyResetOncePerLevel() {
+ // this is old test where the flag needs to be disabled
+ mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+
RescueParty.onSettingsProviderPublished(mMockContext);
verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
any(Executor.class),
@@ -879,6 +888,9 @@
@Test
public void testBootLoopLevels() {
+ // this is old test where the flag needs to be disabled
+ mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+
RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
index 599b9cd..8e1e339 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
@@ -273,10 +273,8 @@
keyValueMap.put("namespace_1*flag_1", "true");
// case 2: existing prop, stage a different value
keyValueMap.put("namespace_1*flag_2", "false");
- // case 3: new prop, stage the non default value
+ // case 3: new prop
keyValueMap.put("namespace_2*flag_1", "true");
- // case 4: new prop, stage the default value
- keyValueMap.put("namespace_2*flag_2", "false");
Properties props = new Properties(namespace, keyValueMap);
HashMap<String, HashMap<String, String>> toStageProps =
@@ -290,11 +288,9 @@
String namespace_1_flag_1 = namespace_1_to_stage.get("flag_1");
String namespace_1_flag_2 = namespace_1_to_stage.get("flag_2");
String namespace_2_flag_1 = namespace_2_to_stage.get("flag_1");
- String namespace_2_flag_2 = namespace_2_to_stage.get("flag_2");
Assert.assertTrue(namespace_1_flag_1 == null);
Assert.assertTrue(namespace_1_flag_2 != null);
Assert.assertTrue(namespace_2_flag_1 != null);
- Assert.assertTrue(namespace_2_flag_2 == null);
Assert.assertTrue(namespace_1_flag_2.equals("false"));
Assert.assertTrue(namespace_2_flag_1.equals("true"));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 7d58a2e..79f1574 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -778,49 +778,6 @@
}
@Test
- public void testGetAliveUsers_shouldExcludeInitialisedEphemeralNonCurrentUsers() {
- assertWithMessage("Ephemeral user should not exist at all initially")
- .that(mUmi.getUsers(false).stream().anyMatch(u -> u.id == USER_ID))
- .isFalse();
-
- // add an ephemeral full user
- TestUserData userData = new TestUserData(USER_ID);
- userData.info.flags = UserInfo.FLAG_FULL | UserInfo.FLAG_EPHEMERAL;
- addUserData(userData);
-
- assertWithMessage("Ephemeral user should exist as alive after being created")
- .that(mUmi.getUsers(true).stream().anyMatch(u -> u.id == USER_ID))
- .isTrue();
-
- // mock switch to the user (mark it as initialized & make it the current user)
- userData.info.flags |= UserInfo.FLAG_INITIALIZED;
- mockCurrentUser(USER_ID);
-
- assertWithMessage("Ephemeral user should still exist as alive after being switched to")
- .that(mUmi.getUsers(true).stream().anyMatch(u -> u.id == USER_ID))
- .isTrue();
-
- // switch away from the user
- mockCurrentUser(OTHER_USER_ID);
-
- assertWithMessage("Ephemeral user should not exist as alive after getting switched away")
- .that(mUmi.getUsers(true).stream().anyMatch(u -> u.id == USER_ID))
- .isFalse();
-
- assertWithMessage("Ephemeral user should still exist as dying after getting switched away")
- .that(mUmi.getUsers(false).stream().anyMatch(u -> u.id == USER_ID))
- .isTrue();
-
- // finally remove the user
- mUms.removeUserInfo(USER_ID);
-
- assertWithMessage("Ephemeral user should not exist at all after cleanup")
- .that(mUmi.getUsers(false).stream().anyMatch(u -> u.id == USER_ID))
- .isFalse();
- }
-
-
- @Test
@RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() {
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
new file mode 100644
index 0000000..7f2327aa
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.AttributionSource;
+import android.os.Process;
+import android.permission.PermissionManager;
+import android.permission.flags.Flags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.testing.TestableContext;
+import android.virtualdevice.cts.common.VirtualDeviceRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Objects;
+
+@RunWith(AndroidJUnit4.class)
+public class AppOpsDeviceAwareServiceTest {
+
+ @Rule
+ public final TestableContext mContext =
+ new TestableContext(InstrumentationRegistry.getInstrumentation().getTargetContext());
+
+ @Rule
+ public VirtualDeviceRule virtualDeviceRule =
+ VirtualDeviceRule.withAdditionalPermissions(
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ Manifest.permission.GET_APP_OPS_STATS);
+
+ private static final String ATTRIBUTION_TAG_1 = "attributionTag1";
+ private static final String ATTRIBUTION_TAG_2 = "attributionTag2";
+ private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+ private final PermissionManager mPermissionManager =
+ mContext.getSystemService(PermissionManager.class);
+
+ private VirtualDeviceManager.VirtualDevice mVirtualDevice;
+
+ @Before
+ public void setUp() {
+ mVirtualDevice =
+ virtualDeviceRule.createManagedVirtualDevice(
+ new VirtualDeviceParams.Builder()
+ .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_CUSTOM)
+ .build());
+
+ mPermissionManager.grantRuntimePermission(
+ mContext.getOpPackageName(),
+ Manifest.permission.CAMERA,
+ mVirtualDevice.getPersistentDeviceId());
+
+ mPermissionManager.grantRuntimePermission(
+ mContext.getOpPackageName(),
+ Manifest.permission.CAMERA,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED)
+ @Test
+ public void noteProxyOp_proxyAppOnDefaultDevice() {
+ AppOpsManager.OpEventProxyInfo proxyInfo =
+ noteProxyOpWithDeviceId(TestableContext.DEVICE_ID_DEFAULT);
+ assertThat(proxyInfo.getDeviceId())
+ .isEqualTo(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED)
+ @Test
+ public void noteProxyOp_proxyAppOnRemoteDevice() {
+ AppOpsManager.OpEventProxyInfo proxyInfo =
+ noteProxyOpWithDeviceId(mVirtualDevice.getDeviceId());
+ assertThat(proxyInfo.getDeviceId()).isEqualTo(mVirtualDevice.getPersistentDeviceId());
+ }
+
+ private AppOpsManager.OpEventProxyInfo noteProxyOpWithDeviceId(int proxyAppDeviceId) {
+ AttributionSource proxiedAttributionSource =
+ new AttributionSource.Builder(Process.myUid())
+ .setPackageName(mContext.getOpPackageName())
+ .setAttributionTag(ATTRIBUTION_TAG_2)
+ .setDeviceId(mVirtualDevice.getDeviceId())
+ .build();
+
+ AttributionSource proxyAttributionSource =
+ new AttributionSource.Builder(Process.myUid())
+ .setPackageName(mContext.getOpPackageName())
+ .setAttributionTag(ATTRIBUTION_TAG_1)
+ .setDeviceId(proxyAppDeviceId)
+ .setNextAttributionSource(proxiedAttributionSource)
+ .build();
+
+ int mode = mAppOpsManager.noteProxyOp(OP_CAMERA, proxyAttributionSource, null, false);
+ assertThat(mode).isEqualTo(AppOpsManager.MODE_ALLOWED);
+
+ List<AppOpsManager.PackageOps> packagesOps =
+ mAppOpsManager.getPackagesForOps(
+ new String[] {AppOpsManager.OPSTR_CAMERA},
+ mVirtualDevice.getPersistentDeviceId());
+
+ AppOpsManager.PackageOps packageOps =
+ packagesOps.stream()
+ .filter(
+ pkg ->
+ Objects.equals(
+ pkg.getPackageName(), mContext.getOpPackageName()))
+ .findFirst()
+ .orElseThrow();
+
+ AppOpsManager.OpEntry opEntry =
+ packageOps.getOps().stream()
+ .filter(op -> op.getOp() == OP_CAMERA)
+ .findFirst()
+ .orElseThrow();
+
+ return opEntry.getLastProxyInfo(OP_FLAGS_ALL_TRUSTED);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java
index 0716a5c..3698d6f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.when;
import android.app.NotificationManager;
+import android.content.Context;
import android.content.Intent;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.UserHandle;
@@ -75,13 +76,15 @@
@Test
public void testFingerprintRegisterReceiver() {
initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FINGERPRINT);
- verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any());
+ verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any(),
+ eq(Context.RECEIVER_NOT_EXPORTED));
}
@Test
public void testFaceRegisterReceiver() {
initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FACE);
- verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any());
+ verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any(),
+ eq(Context.RECEIVER_NOT_EXPORTED));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
index b33a8aa..00c8ed1 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
@@ -53,7 +53,7 @@
private IInputDevicesChangedListener mDevicesChangedListener;
private final Map<String /* uniqueId */, Integer /* displayId */> mDisplayIdMapping =
new HashMap<>();
- private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociation =
+ private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociationByPort =
new HashMap<>();
InputManagerMockHelper(TestableLooper testableLooper,
@@ -79,11 +79,11 @@
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
doAnswer(inv -> mDevices.get(inv.getArgument(0)))
.when(mIInputManagerMock).getInputDevice(anyInt());
- doAnswer(inv -> mUniqueIdAssociation.put(inv.getArgument(0),
- inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociation(
+ doAnswer(inv -> mUniqueIdAssociationByPort.put(inv.getArgument(0),
+ inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociationByPort(
anyString(), anyString());
- doAnswer(inv -> mUniqueIdAssociation.remove(inv.getArgument(0))).when(
- mIInputManagerMock).removeUniqueIdAssociation(anyString());
+ doAnswer(inv -> mUniqueIdAssociationByPort.remove(inv.getArgument(0))).when(
+ mIInputManagerMock).removeUniqueIdAssociationByPort(anyString());
// Set a new instance of InputManager for testing that uses the IInputManager mock as the
// interface to the server.
@@ -113,7 +113,7 @@
.setDescriptor(phys)
.setExternal(true)
.setAssociatedDisplayId(
- mDisplayIdMapping.getOrDefault(mUniqueIdAssociation.get(phys),
+ mDisplayIdMapping.getOrDefault(mUniqueIdAssociationByPort.get(phys),
Display.INVALID_DISPLAY))
.build();
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index 40ecaf1..7dd1847 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -42,6 +42,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.Bundle;
import android.os.HandlerThread;
import android.os.LocaleList;
import android.os.Process;
@@ -488,7 +489,7 @@
setUpPackageInstalled(pkgNameA);
- mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
+ mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, new Bundle());
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog
@@ -504,7 +505,7 @@
setUpPackageInstalled(pkgNameB);
- mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
+ mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, new Bundle());
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog
@@ -518,6 +519,66 @@
}
@Test
+ public void testRestore_appInstalledAfterSUW_restoresFromStage_ArchiveEnabled()
+ throws Exception {
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ HashMap<String, LocalesInfo> pkgLocalesMap = new HashMap<>();
+ String pkgNameA = "com.android.myAppA";
+ String pkgNameB = "com.android.myAppB";
+ String langTagsA = "ru";
+ String langTagsB = "hi,fr";
+ LocalesInfo localesInfoA = new LocalesInfo(langTagsA, false);
+ LocalesInfo localesInfoB = new LocalesInfo(langTagsB, true);
+ pkgLocalesMap.put(pkgNameA, localesInfoA);
+ pkgLocalesMap.put(pkgNameB, localesInfoB);
+ writeTestPayload(out, pkgLocalesMap);
+ setUpPackageNotInstalled(pkgNameA);
+ setUpPackageNotInstalled(pkgNameB);
+ setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
+ setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList());
+ setUpPackageNamesForSp(new ArraySet<>());
+
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(Intent.EXTRA_ARCHIVAL, true);
+ mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, bundle);
+ mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, bundle);
+
+ mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+ verifyNothingRestored();
+
+ setUpPackageInstalled(pkgNameA);
+
+ mPackageMonitor.onPackageUpdateFinished(pkgNameA, DEFAULT_UID);
+
+ verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
+ LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+
+ mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameA, false, false);
+
+ verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
+
+ pkgLocalesMap.remove(pkgNameA);
+
+ verifyStageDataForUser(pkgLocalesMap, DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
+
+ setUpPackageInstalled(pkgNameB);
+
+ mPackageMonitor.onPackageUpdateFinished(pkgNameB, DEFAULT_UID);
+
+ verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
+ LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+
+ mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameB, true, false);
+
+ verify(mMockSpEditor, times(1)).putStringSet(Integer.toString(DEFAULT_USER_ID),
+ new ArraySet<>(Arrays.asList(pkgNameB)));
+ checkStageDataDoesNotExist(DEFAULT_USER_ID);
+ }
+
+ @Test
public void testRestore_appInstalledAfterSUWAndLocalesAlreadySet_restoresNothing()
throws Exception {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -535,7 +596,7 @@
setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.forLanguageTags("hi,mr"));
- mPackageMonitor.onPackageAdded(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+ mPackageMonitor.onPackageAddedWithExtras(DEFAULT_PACKAGE_NAME, DEFAULT_UID, new Bundle());
// Since locales are already set, we should not restore anything for it.
verifyNothingRestored();
@@ -612,7 +673,7 @@
DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
setUpPackageInstalled(pkgNameA);
- mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
+ mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, new Bundle());
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(
pkgNameA, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsA), false,
@@ -627,7 +688,7 @@
DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
setUpPackageInstalled(pkgNameB);
- mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
+ mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, new Bundle());
verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameB), anyInt(),
any(), anyBoolean(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index 80fb5e3..1514de0 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -89,6 +89,7 @@
private static final String WRAPPING_KEY_ALIAS = "KeySyncTaskTest/WrappingKey";
private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
private static final int TEST_USER_ID = 1000;
+ private static final int TEST_USER_ID_2 = 1002;
private static final int TEST_RECOVERY_AGENT_UID = 10009;
private static final int TEST_RECOVERY_AGENT_UID2 = 10010;
private static final byte[] TEST_VAULT_HANDLE =
@@ -824,6 +825,48 @@
}
@Test
+ public void run_unlock_keepsRemoteLskfVerificationCounter() throws Exception {
+ mRecoverableKeyStoreDb.setBadRemoteGuessCounter(TEST_USER_ID, 5);
+ mRecoverableKeyStoreDb.setBadRemoteGuessCounter(TEST_USER_ID_2, 4);
+ mKeySyncTask = new KeySyncTask(
+ mRecoverableKeyStoreDb,
+ mRecoverySnapshotStorage,
+ mSnapshotListenersStorage,
+ TEST_USER_ID,
+ CREDENTIAL_TYPE_PIN,
+ "12345".getBytes(),
+ /*credentialUpdated=*/ false,
+ mPlatformKeyManager,
+ mTestOnlyInsecureCertificateHelper,
+ mMockScrypt);
+ mKeySyncTask.run();
+
+ assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(TEST_USER_ID)).isEqualTo(5);
+ assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(TEST_USER_ID_2)).isEqualTo(4);
+ }
+
+ @Test
+ public void run_secretChange_resetsRemoteLskfVerificationCounter() throws Exception {
+ mRecoverableKeyStoreDb.setBadRemoteGuessCounter(TEST_USER_ID, 5);
+ mRecoverableKeyStoreDb.setBadRemoteGuessCounter(TEST_USER_ID_2, 4);
+ mKeySyncTask = new KeySyncTask(
+ mRecoverableKeyStoreDb,
+ mRecoverySnapshotStorage,
+ mSnapshotListenersStorage,
+ TEST_USER_ID,
+ CREDENTIAL_TYPE_PIN,
+ "12345".getBytes(),
+ /*credentialUpdated=*/ true,
+ mPlatformKeyManager,
+ mTestOnlyInsecureCertificateHelper,
+ mMockScrypt);
+ mKeySyncTask.run();
+
+ assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(TEST_USER_ID)).isEqualTo(0);
+ assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(TEST_USER_ID_2)).isEqualTo(4);
+ }
+
+ @Test
public void run_customLockScreen_RecoveryStatusFailure() throws Exception {
mKeySyncTask = new KeySyncTask(
mRecoverableKeyStoreDb,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 5e2fe6a..2d672b8 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5969,6 +5969,8 @@
assertThat(captor.getValue().getNotification().flags
& FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+ assertThat(captor.getValue().getNotification().flags
+ & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE);
assertThat(captor.getValue().shouldPostSilently()).isTrue();
}
@@ -8798,6 +8800,8 @@
assertThat(captor.getValue().getNotification().flags
& FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+ assertThat(captor.getValue().getNotification().flags
+ & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE);
assertThat(captor.getValue().shouldPostSilently()).isTrue();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 44d1b54..87395a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -113,15 +113,10 @@
import android.metrics.LogMaker;
import android.os.Binder;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.ArraySet;
-import android.util.DisplayMetrics;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
@@ -131,7 +126,6 @@
import android.view.ISystemGestureExclusionListener;
import android.view.IWindowManager;
import android.view.InsetsState;
-import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -151,7 +145,6 @@
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.utils.WmDisplayCutout;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -178,10 +171,6 @@
@RunWith(WindowTestRunner.class)
public class DisplayContentTests extends WindowTestsBase {
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
@SetupWindows(addAllCommonWindows = true)
@Test
public void testForAllWindows() {
@@ -514,44 +503,6 @@
assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
}
- /**
- * Tests tapping on a root task in different display results in window gaining focus.
- */
- @Test
- @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_REMOVE_POINTER_EVENT_TRACKING_IN_WM)
- public void testInputEventBringsCorrectDisplayInFocus() {
- DisplayContent dc0 = mWm.getDefaultDisplayContentLocked();
- // Create a second display
- final DisplayContent dc1 = createNewDisplay();
-
- // Add root task with activity.
- final Task rootTask0 = createTask(dc0);
- final Task task0 = createTaskInRootTask(rootTask0, 0 /* userId */);
- final ActivityRecord activity = createNonAttachedActivityRecord(dc0);
- task0.addChild(activity, 0);
- dc0.configureDisplayPolicy();
- assertNotNull(dc0.mTapDetector);
-
- final Task rootTask1 = createTask(dc1);
- final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */);
- final ActivityRecord activity1 = createNonAttachedActivityRecord(dc0);
- task1.addChild(activity1, 0);
- dc1.configureDisplayPolicy();
- assertNotNull(dc1.mTapDetector);
-
- // tap on primary display.
- tapOnDisplay(dc0);
- // Check focus is on primary display.
- assertEquals(mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus,
- dc0.findFocusedWindow());
-
- // Tap on secondary display.
- tapOnDisplay(dc1);
- // Check focus is on secondary.
- assertEquals(mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus,
- dc1.findFocusedWindow());
- }
-
@Test
public void testFocusedWindowMultipleDisplays() {
doTestFocusedWindowMultipleDisplays(false /* perDisplayFocusEnabled */, Q);
@@ -2959,33 +2910,4 @@
throw new RuntimeException(e);
}
}
-
- private void tapOnDisplay(final DisplayContent dc) {
- final DisplayMetrics dm = dc.getDisplayMetrics();
- final float x = dm.widthPixels / 2;
- final float y = dm.heightPixels / 2;
- final long downTime = SystemClock.uptimeMillis();
- final long eventTime = SystemClock.uptimeMillis() + 100;
- // sending ACTION_DOWN
- final MotionEvent downEvent = MotionEvent.obtain(
- downTime,
- downTime,
- MotionEvent.ACTION_DOWN,
- x,
- y,
- 0 /*metaState*/);
- downEvent.setDisplayId(dc.getDisplayId());
- dc.mTapDetector.onPointerEvent(downEvent);
-
- // sending ACTION_UP
- final MotionEvent upEvent = MotionEvent.obtain(
- downTime,
- eventTime,
- MotionEvent.ACTION_UP,
- x,
- y,
- 0 /*metaState*/);
- upEvent.setDisplayId(dc.getDisplayId());
- dc.mTapDetector.onPointerEvent(upEvent);
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index b90fa21..79e401c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -18,15 +18,17 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
-
import static com.android.server.wm.testing.Assert.assertThrows;
+import static junit.framework.Assert.assertEquals;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -35,11 +37,13 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.res.Resources;
import android.platform.test.annotations.Presubmit;
+import android.util.DisplayMetrics;
import androidx.test.filters.SmallTest;
-import com.android.server.wm.testing.Assert;
+import com.android.internal.R;
import org.junit.Before;
import org.junit.Test;
@@ -277,7 +281,7 @@
}
@Test
- public void test_lettterboxPositionWhenReachabilityEnabledIsSet() {
+ public void test_letterboxPositionWhenReachabilityEnabledIsSet() {
// Check that horizontal reachability is set with correct arguments
mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability(
false /* forBookMode */, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
@@ -344,4 +348,48 @@
mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0.5f);
mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(1);
}
+
+ @Test
+ public void test_evaluateThinLetterboxWhenDensityChanges() {
+ final Resources rs = mock(Resources.class);
+ final DisplayMetrics dm = mock(DisplayMetrics.class);
+ final LetterboxConfigurationPersister lp = mock(LetterboxConfigurationPersister.class);
+ spyOn(mContext);
+ when(rs.getDisplayMetrics()).thenReturn(dm);
+ when(mContext.getResources()).thenReturn(rs);
+ when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp))
+ .thenReturn(100);
+ when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp))
+ .thenReturn(200);
+ final LetterboxConfiguration configuration = new LetterboxConfiguration(mContext, lp);
+
+ // Verify the values are the expected ones
+ dm.density = 100;
+ when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp))
+ .thenReturn(100);
+ when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp))
+ .thenReturn(200);
+ final int thinWidthPx = configuration.getThinLetterboxWidthPx();
+ final int thinHeightPx = configuration.getThinLetterboxHeightPx();
+ assertEquals(100, thinWidthPx);
+ assertEquals(200, thinHeightPx);
+
+ // We change the values in the resources but not the update condition (density) and the
+ // result should not change
+ when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp))
+ .thenReturn(300);
+ when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp))
+ .thenReturn(400);
+ final int thinWidthPx2 = configuration.getThinLetterboxWidthPx();
+ final int thinHeightPx2 = configuration.getThinLetterboxHeightPx();
+ assertEquals(100, thinWidthPx2);
+ assertEquals(200, thinHeightPx2);
+
+ // We update the condition (density) so the new resource values should be read
+ dm.density = 150;
+ final int thinWidthPx3 = configuration.getThinLetterboxWidthPx();
+ final int thinHeightPx3 = configuration.getThinLetterboxHeightPx();
+ assertEquals(300, thinWidthPx3);
+ assertEquals(400, thinHeightPx3);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index afa6698..d9fd312 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -1894,11 +1894,7 @@
public void testApplyTransaction_createTaskFragmentDecorSurface() {
mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG);
- // TODO(b/293654166) remove system organizer requirement once security review is cleared.
- mController.unregisterOrganizer(mIOrganizer);
- registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */);
final Task task = createTask(mDisplayContent);
-
final TaskFragment tf = createTaskFragment(task);
final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE).build();
@@ -1913,9 +1909,6 @@
public void testApplyTransaction_removeTaskFragmentDecorSurface() {
mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG);
- // TODO(b/293654166) remove system organizer requirement once security review is cleared.
- mController.unregisterOrganizer(mIOrganizer);
- registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */);
final Task task = createTask(mDisplayContent);
final TaskFragment tf = createTaskFragment(task);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 37de51e..4fc222b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -95,10 +95,6 @@
}
@Override
- public void updatePointerIcon(float x, float y) throws RemoteException {
- }
-
- @Override
public void dispatchWindowShown() throws RemoteException {
}
@@ -128,4 +124,9 @@
public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)
throws RemoteException {
}
+
+ @Override
+ public void dumpWindow(ParcelFileDescriptor pfd) {
+
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index fbbb9a2..b152c3e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -819,17 +819,20 @@
assertFalse(win.getOrientationChanging());
}
- @SetupWindows(addWindows = W_ABOVE_ACTIVITY)
@Test
public void testRequestResizeForBlastSync() {
- final WindowState win = mChildAppWindowAbove;
- makeWindowVisible(win, win.getParentWindow());
+ final WindowState win = createWindow(null, TYPE_APPLICATION, "window");
+ makeWindowVisible(win);
+ makeLastConfigReportedToClient(win, true /* visible */);
win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
win.reportResized();
win.updateResizingWindowIfNeeded();
assertThat(mWm.mResizingWindows).doesNotContain(win);
// Check that the window is in resizing if using blast sync.
+ final BLASTSyncEngine.SyncGroup syncGroup = mock(BLASTSyncEngine.SyncGroup.class);
+ syncGroup.mSyncMethod = BLASTSyncEngine.METHOD_BLAST;
+ win.mSyncGroup = syncGroup;
win.reportResized();
win.prepareSync();
assertEquals(SYNC_STATE_WAITING_FOR_DRAW, win.mSyncState);
@@ -842,6 +845,20 @@
mWm.mResizingWindows.remove(win);
win.updateResizingWindowIfNeeded();
assertThat(mWm.mResizingWindows).doesNotContain(win);
+
+ // Non blast sync doesn't require to force resizing, because it won't use syncSeqId.
+ // And if the window is already drawn, it can report sync finish immediately so that the
+ // sync group won't be blocked.
+ win.finishSync(mTransaction, syncGroup, false /* cancel */);
+ syncGroup.mSyncMethod = BLASTSyncEngine.METHOD_NONE;
+ win.mSyncGroup = syncGroup;
+ win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
+ win.prepareSync();
+ assertEquals(SYNC_STATE_WAITING_FOR_DRAW, win.mSyncState);
+ win.updateResizingWindowIfNeeded();
+ assertThat(mWm.mResizingWindows).doesNotContain(win);
+ assertTrue(win.isSyncFinished(syncGroup));
+ assertEquals(WindowContainer.SYNC_STATE_READY, win.mSyncState);
}
@Test
diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
index dfbbda6c..afb3593 100644
--- a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
+++ b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
@@ -9,21 +9,28 @@
import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS
import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE
import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY
+import android.util.IndentingPrintWriter
+import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.security.AttestationVerificationManagerService.DumpLogger
import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
import java.io.ByteArrayOutputStream
+import java.io.PrintWriter
+import java.io.StringWriter
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.security.cert.TrustAnchor
import java.security.cert.X509Certificate
import java.time.LocalDate
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
/** Test for Peer Device attestation verifier. */
@SmallTest
@@ -31,6 +38,7 @@
class AttestationVerificationPeerDeviceVerifierTest {
private val certificateFactory = CertificateFactory.getInstance("X.509")
@Mock private lateinit var context: Context
+ private val dumpLogger = DumpLogger()
private lateinit var trustAnchors: HashSet<TrustAnchor>
@Before
@@ -44,37 +52,50 @@
}
}
+ @After
+ fun dumpAndLog() {
+ val dump = dumpLogger.getDump()
+ Log.d(TAG, "$dump")
+ }
+
@Test
fun verifyAttestation_returnsSuccessTypeChallenge() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, trustAnchors, false, LocalDate.of(2022, 2, 1),
- LocalDate.of(2021, 8, 1))
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+ LocalDate.of(2021, 8, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_SUCCESS)
}
@Test
fun verifyAttestation_returnsSuccessLocalPatchOlderThanOneYear() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, trustAnchors, false, LocalDate.of(2022, 2, 1),
- LocalDate.of(2021, 1, 1))
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+ LocalDate.of(2021, 1, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_SUCCESS)
}
@Test
fun verifyAttestation_returnsSuccessTypePublicKey() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, trustAnchors, false, LocalDate.of(2022, 2, 1),
- LocalDate.of(2021, 8, 1))
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+ LocalDate.of(2021, 8, 1)
+ )
val leafCert =
(TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToCerts() as List)[0]
@@ -84,61 +105,75 @@
val result = verifier.verifyAttestation(
TYPE_PUBLIC_KEY, pkRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_SUCCESS)
}
@Test
fun verifyAttestation_returnsSuccessOwnedBySystem() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, trustAnchors, false, LocalDate.of(2022, 2, 1),
- LocalDate.of(2021, 1, 1))
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+ LocalDate.of(2021, 1, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "activeUnlockValid".encodeToByteArray())
challengeRequirements.putBoolean("android.key_owned_by_system", true)
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray()
+ )
+
assertThat(result).isEqualTo(RESULT_SUCCESS)
}
@Test
fun verifyAttestation_returnsFailureOwnedBySystem() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, trustAnchors, false, LocalDate.of(2022, 2, 1),
- LocalDate.of(2021, 1, 1))
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+ LocalDate.of(2021, 1, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
challengeRequirements.putBoolean("android.key_owned_by_system", true)
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_FAILURE)
}
@Test
fun verifyAttestation_returnsFailurePatchDateNotWithinOneYearLocalPatch() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, trustAnchors, false, LocalDate.of(2023, 3, 1),
- LocalDate.of(2023, 2, 1))
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2023, 3, 1),
+ LocalDate.of(2023, 2, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_FAILURE)
}
@Test
fun verifyAttestation_returnsFailureTrustedAnchorEmpty() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, HashSet(), false, LocalDate.of(2022, 1, 1),
- LocalDate.of(2022, 1, 1))
+ context, dumpLogger, HashSet(), false, LocalDate.of(2022, 1, 1),
+ LocalDate.of(2022, 1, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_FAILURE)
}
@@ -151,32 +186,39 @@
}
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, badTrustAnchors, false, LocalDate.of(2022, 1, 1),
- LocalDate.of(2022, 1, 1))
+ context, dumpLogger, badTrustAnchors, false, LocalDate.of(2022, 1, 1),
+ LocalDate.of(2022, 1, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_FAILURE)
}
fun verifyAttestation_returnsFailureChallenge() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, trustAnchors, false, LocalDate.of(2022, 1, 1),
- LocalDate.of(2022, 1, 1))
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 1, 1),
+ LocalDate.of(2022, 1, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "wrong".encodeToByteArray())
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_FAILURE)
}
private fun String.fromPEMFileToCerts(): Collection<Certificate> {
return certificateFactory.generateCertificates(
InstrumentationRegistry.getInstrumentation().getContext().getResources().getAssets()
- .open(this))
+ .open(this)
+ )
}
private fun String.fromPEMFileToByteArray(): ByteArray {
@@ -188,6 +230,12 @@
return bos.toByteArray()
}
+ private fun DumpLogger.getDump(): String {
+ val sw = StringWriter()
+ this.dumpTo(IndentingPrintWriter(PrintWriter(sw), " "))
+ return sw.toString()
+ }
+
class TestActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -195,6 +243,7 @@
}
companion object {
+ private const val TAG = "AVFTest"
private const val TEST_ROOT_CERT_FILENAME = "test_root_certs.pem"
private const val TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME =
"test_attestation_with_root_certs.pem"
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 3b9ee80..3c72498 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -169,12 +169,6 @@
localService.setDisplayViewports(viewports)
verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java))
verify(native).setPointerDisplayId(displayId)
-
- val x = 42f
- val y = 314f
- service.onPointerDisplayIdChanged(displayId, x, y)
- testLooper.dispatchNext()
- verify(wmCallbacks).notifyPointerDisplayIdChanged(displayId, x, y)
}
@Test
@@ -317,9 +311,8 @@
verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent)
}
- // TODO(b/324075859): Rename this method to addUniqueIdAssociationByPort_verifyAssociations
@Test
- fun addUniqueIdAssociation_verifyAssociations() {
+ fun addUniqueIdAssociationByPort_verifyAssociations() {
// Overall goal is to have 2 displays and verify that events from the InputDevice are
// sent only to the view that is on the associated display.
// So, associate the InputDevice with display 1, then send and verify KeyEvents.
@@ -340,7 +333,7 @@
val inputDevice = createInputDevice()
// Associate input device with display
- service.addUniqueIdAssociation(
+ service.addUniqueIdAssociationByPort(
inputDevice.name,
virtualDisplays[0].display.displayId.toString()
)
@@ -364,10 +357,10 @@
verify(mockOnKeyListener, never()).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent)
// Remove association
- service.removeUniqueIdAssociation(inputDevice.name)
+ service.removeUniqueIdAssociationByPort(inputDevice.name)
// Associate with Display 2
- service.addUniqueIdAssociation(
+ service.addUniqueIdAssociationByPort(
inputDevice.name,
virtualDisplays[1].display.displayId.toString()
)
diff --git a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java
index 3ab8d37..6bcfebc 100644
--- a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java
+++ b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java
@@ -176,25 +176,19 @@
},
new Test("Disable Alerts") {
public void run() {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- info.setNotificationPeekingDisabled(true);
- mStatusBarManager.requestDisabledComponent(info, "test");
+ mStatusBarManager.disable(StatusBarManager.DISABLE_NOTIFICATION_ALERTS);
}
},
new Test("Disable Ticker") {
public void run() {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- info.setNotificationTickerDisabled(true);
- mStatusBarManager.requestDisabledComponent(info, "test");
+ mStatusBarManager.disable(StatusBarManager.DISABLE_NOTIFICATION_TICKER);
}
},
new Test("Disable Expand in 3 sec.") {
public void run() {
mHandler.postDelayed(new Runnable() {
public void run() {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- info.setStatusBarExpansionDisabled(true);
- mStatusBarManager.requestDisabledComponent(info, "test");
+ mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND);
}
}, 3000);
}
@@ -203,9 +197,7 @@
public void run() {
mHandler.postDelayed(new Runnable() {
public void run() {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- info.setNotificationIconsDisabled(true);
- mStatusBarManager.requestDisabledComponent(info, "test");
+ mStatusBarManager.disable(StatusBarManager.DISABLE_NOTIFICATION_ICONS);
}
}, 3000);
}
@@ -214,73 +206,56 @@
public void run() {
mHandler.postDelayed(new Runnable() {
public void run() {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- info.setStatusBarExpansionDisabled(true);
- info.setNotificationIconsDisabled(true);
- mStatusBarManager.requestDisabledComponent(info, "test");
+ mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND
+ | StatusBarManager.DISABLE_NOTIFICATION_ICONS);
}
}, 3000);
}
},
new Test("Disable Home (StatusBarManager)") {
public void run() {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- info.setNavigationHomeDisabled(true);
- mStatusBarManager.requestDisabledComponent(info, "test");
+ mStatusBarManager.disable(StatusBarManager.DISABLE_HOME);
}
},
new Test("Disable Back (StatusBarManager)") {
public void run() {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- info.setBackDisabled(true);
- mStatusBarManager.requestDisabledComponent(info, "test");
+ mStatusBarManager.disable(StatusBarManager.DISABLE_BACK);
}
},
new Test("Disable Recent (StatusBarManager)") {
public void run() {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- info.setRecentsDisabled(true);
- mStatusBarManager.requestDisabledComponent(info, "test");
+ mStatusBarManager.disable(StatusBarManager.DISABLE_RECENT);
}
},
new Test("Disable Clock") {
public void run() {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- info.setClockDisabled(true);
- mStatusBarManager.requestDisabledComponent(info, "test");
+ mStatusBarManager.disable(StatusBarManager.DISABLE_CLOCK);
}
},
new Test("Disable System Info") {
public void run() {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- info.setSystemIconsDisabled(true);
- mStatusBarManager.requestDisabledComponent(info, "test");
+ mStatusBarManager.disable(StatusBarManager.DISABLE_SYSTEM_INFO);
}
},
new Test("Disable everything in 3 sec") {
public void run() {
mHandler.postDelayed(new Runnable() {
public void run() {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- info.setDisableAll();
- mStatusBarManager.requestDisabledComponent(info, "test");
+ mStatusBarManager.disable(~StatusBarManager.DISABLE_NONE);
}
}, 3000);
}
},
new Test("Enable everything") {
public void run() {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- mStatusBarManager.requestDisabledComponent(info, "test");
+ mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);
}
},
new Test("Enable everything in 3 sec.") {
public void run() {
mHandler.postDelayed(new Runnable() {
public void run() {
- StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
- info.setEnableAll();
- mStatusBarManager.requestDisabledComponent(info, "test");
+ mStatusBarManager.disable(0);
}
}, 3000);
}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
index e3f84c1..f126000 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
@@ -24,6 +24,7 @@
import android.app.Activity;
import android.os.Bundle;
+import android.os.Process;
import android.util.Log;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
@@ -47,8 +48,9 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Log.v(TAG, "Create MainActivity as user " + getUserId() + " on display "
- + getDisplayId());
+ Log.v(TAG, "Create MainActivity as user "
+ + Process.myUserHandle().getIdentifier() + " on display "
+ + getDisplay().getDisplayId());
setContentView(R.layout.main_activity);
mImm = getSystemService(InputMethodManager.class);
mEditor = requireViewById(R.id.edit_text);
diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING
index f6885e1..856e6ee 100644
--- a/tools/hoststubgen/TEST_MAPPING
+++ b/tools/hoststubgen/TEST_MAPPING
@@ -1,63 +1,7 @@
-// Keep the following two TEST_MAPPINGs in sync:
-// frameworks/base/ravenwood/TEST_MAPPING
-// frameworks/base/tools/hoststubgen/TEST_MAPPING
{
- "presubmit": [
- { "name": "tiny-framework-dump-test" },
- { "name": "hoststubgentest" },
- { "name": "hoststubgen-invoke-test" },
+ "imports": [
{
- "name": "RavenwoodMockitoTest_device"
- },
- {
- "name": "RavenwoodBivalentTest_device"
- },
- // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
- {
- "name": "SystemUIGoogleTests",
- "options": [
- {
- "exclude-annotation": "org.junit.Ignore"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- }
- ]
- }
- ],
- "presubmit-large": [
- {
- "name": "SystemUITests",
- "options": [
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "org.junit.Ignore"
- }
- ]
- }
- ],
- "ravenwood-presubmit": [
- {
- "name": "RavenwoodMinimumTest",
- "host": true
- },
- {
- "name": "RavenwoodMockitoTest",
- "host": true
- },
- {
- "name": "CtsUtilTestCasesRavenwood",
- "host": true
- },
- {
- "name": "RavenwoodCoreTest",
- "host": true
- },
- {
- "name": "RavenwoodBivalentTest",
- "host": true
+ "path": "frameworks/base/ravenwood"
}
]
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 3d5a0f7..395f744 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -21,6 +21,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.net.wifi.flags.Flags;
import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
import android.os.Bundle;
import android.os.Parcel;
@@ -170,7 +171,7 @@
* @return Returns the Builder object.
*/
@NonNull
- @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
+ @FlaggedApi(Flags.FLAG_NETWORK_PROVIDER_BATTERY_CHARGING_STATUS)
public Builder setBatteryCharging(boolean isBatteryCharging) {
mIsBatteryCharging = isBatteryCharging;
return this;
@@ -285,7 +286,7 @@
*
* @return Returns true if the battery of the remote device is charging.
*/
- @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
+ @FlaggedApi(Flags.FLAG_NETWORK_PROVIDER_BATTERY_CHARGING_STATUS)
public boolean isBatteryCharging() {
return mIsBatteryCharging;
}