Merge "Move up caller's display and window validation" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 0ee7ace..e6e835b 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -19,6 +19,7 @@
":android.app.flags-aconfig-java{.generated_srcjars}",
":android.app.smartspace.flags-aconfig-java{.generated_srcjars}",
":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+ ":android.app.wearable.flags-aconfig-java{.generated_srcjars}",
":android.appwidget.flags-aconfig-java{.generated_srcjars}",
":android.chre.flags-aconfig-java{.generated_srcjars}",
":android.companion.flags-aconfig-java{.generated_srcjars}",
@@ -1123,3 +1124,16 @@
],
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// Wearable Sensing
+aconfig_declarations {
+ name: "android.app.wearable.flags-aconfig",
+ package: "android.app.wearable",
+ srcs: ["core/java/android/app/wearable/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.app.wearable.flags-aconfig-java",
+ aconfig_declarations: "android.app.wearable.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index cf5a261..b17e3343 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13,6 +13,7 @@
field public static final String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
field public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
field public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
+ field @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") public static final String ACCESS_HIDDEN_PROFILES = "android.permission.ACCESS_HIDDEN_PROFILES";
field public static final String ACCESS_LOCATION_EXTRA_COMMANDS = "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS";
field public static final String ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION";
field public static final String ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE";
@@ -89,6 +90,7 @@
field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
field public static final String DELIVER_COMPANION_MESSAGES = "android.permission.DELIVER_COMPANION_MESSAGES";
field public static final String DETECT_SCREEN_CAPTURE = "android.permission.DETECT_SCREEN_CAPTURE";
+ field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final String DETECT_SCREEN_RECORDING = "android.permission.DETECT_SCREEN_RECORDING";
field public static final String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
field public static final String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
field public static final String DUMP = "android.permission.DUMP";
@@ -44544,6 +44546,7 @@
method @Nullable public android.telephony.CellIdentity getCellIdentity();
method public int getDomain();
method @Nullable public String getRegisteredPlmn();
+ method @FlaggedApi("com.android.internal.telephony.flags.network_registration_info_reject_cause") public int getRejectCause();
method public int getTransportType();
method public boolean isNetworkRegistered();
method public boolean isNetworkRoaming();
@@ -53782,6 +53785,7 @@
method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void addProposedRotationListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+ method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default int addScreenRecordingCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @NonNull public default android.view.WindowMetrics getCurrentWindowMetrics();
method @Deprecated public android.view.Display getDefaultDisplay();
method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
@@ -53791,6 +53795,7 @@
method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver);
method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
+ method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default void removeScreenRecordingCallback(@NonNull java.util.function.Consumer<java.lang.Integer>);
method public void removeViewImmediate(android.view.View);
method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.view.SurfaceControl);
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
@@ -53810,6 +53815,8 @@
field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
field @FlaggedApi("com.android.window.flags.supports_multi_instance_system_ui") public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI = "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
+ field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final int SCREEN_RECORDING_STATE_NOT_VISIBLE = 0; // 0x0
+ field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final int SCREEN_RECORDING_STATE_VISIBLE = 1; // 0x1
}
public static class WindowManager.BadTokenException extends java.lang.RuntimeException {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 86f2b628..e6040f8 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -414,6 +414,7 @@
field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
field public static final String WRITE_SECURITY_LOG = "android.permission.WRITE_SECURITY_LOG";
field public static final String WRITE_SMS = "android.permission.WRITE_SMS";
+ field @FlaggedApi("android.provider.user_keys") public static final String WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS = "android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS";
}
public static final class Manifest.permission_group {
@@ -3157,6 +3158,7 @@
field public static final int STATUS_SUCCESS = 1; // 0x1
field public static final int STATUS_UNKNOWN = 0; // 0x0
field public static final int STATUS_UNSUPPORTED = 2; // 0x2
+ field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6
field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4
}
@@ -11303,6 +11305,12 @@
field public static final int ERROR_UNKNOWN = 0; // 0x0
}
+ @FlaggedApi("android.provider.user_keys") public class ContactKeysManager {
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyLocalVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateSelfKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
+ }
+
@Deprecated public static final class ContactsContract.MetadataSync implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncColumns {
field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata";
field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata";
@@ -14128,7 +14136,6 @@
method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
method public int getNetworkRegistrationState();
method @Deprecated public int getRegistrationState();
- method public int getRejectCause();
method public int getRoamingType();
method public boolean isEmergencyEnabled();
method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5d2a26e..4c54b03 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -303,7 +303,7 @@
public static final boolean DEBUG_MEMORY_TRIM = false;
private static final boolean DEBUG_PROVIDER = false;
public static final boolean DEBUG_ORDER = false;
- private static final boolean DEBUG_APP_INFO = true;
+ private static final boolean DEBUG_APP_INFO = false;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
/**
* The delay to release the provider when it has no more references. It reduces the number of
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 7907059..0dbce97 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1550,7 +1550,7 @@
AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
/**
- * Allows an app whose primary use case is to backup or sync content to run longer jobs.
+ * Allows an app with a major use case of backing-up or syncing content to run longer jobs.
*
* @hide
*/
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index d540748..e2e2f1d 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -225,7 +225,7 @@
boolean focused, boolean newSessionId);
boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
in IBinder activityToken, int flags);
- boolean isAssistDataAllowedOnCurrentActivity();
+ boolean isAssistDataAllowed();
boolean requestAssistDataForTask(in IAssistDataReceiver receiver, int taskId,
in String callingPackageName, String callingAttributionTag);
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index a271328..a045eae 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.app.Flags.enableNightModeCache;
+
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
@@ -31,6 +33,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.os.Binder;
+import android.os.IpcDataCache;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -874,6 +877,51 @@
}
}
+ private Integer getNightModeFromServer() {
+ try {
+ if (sGlobals != null) {
+ return sGlobals.mService.getNightMode();
+ }
+ return -1;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Retrieve the night mode for the user.
+ */
+ private final IpcDataCache.QueryHandler<Void, Integer> mNightModeQuery =
+ new IpcDataCache.QueryHandler<>() {
+
+ @Override
+ @NonNull
+ public Integer apply(Void query) {
+ return getNightModeFromServer();
+ }
+ };
+
+ private static final String NIGHT_MODE_API = "getNightMode";
+
+ /**
+ * Cache the night mode for a user.
+ */
+ private final IpcDataCache<Void, Integer> mNightModeCache =
+ new IpcDataCache<>(1, IpcDataCache.MODULE_SYSTEM,
+ NIGHT_MODE_API, /* cacheName= */ "NightModeCache", mNightModeQuery);
+
+ /**
+ * Invalidate the night mode cache.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NIGHT_MODE_CACHE)
+ public static void invalidateNightModeCache() {
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+ NIGHT_MODE_API);
+ }
+
/**
* Returns the currently configured night mode.
* <p>
@@ -890,14 +938,11 @@
* @see #setNightMode(int)
*/
public @NightMode int getNightMode() {
- if (sGlobals != null) {
- try {
- return sGlobals.mService.getNightMode();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ if (enableNightModeCache()) {
+ return mNightModeCache.query(null);
+ } else {
+ return getNightModeFromServer();
}
- return -1;
}
/**
diff --git a/core/java/android/app/ui_mode_manager.aconfig b/core/java/android/app/ui_mode_manager.aconfig
new file mode 100644
index 0000000..1ae5264
--- /dev/null
+++ b/core/java/android/app/ui_mode_manager.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+ namespace: "system_performance"
+ name: "enable_night_mode_cache"
+ description: "Enables the use of binder caching for system night mode."
+ bug: "255999432"
+}
\ No newline at end of file
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index f1ca086..eca0039 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -99,6 +100,13 @@
*/
public static final int STATUS_ACCESS_DENIED = 5;
+ /**
+ * The value of the status code that indicates the method called is not supported by the
+ * implementation of {@link WearableSensingService}.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE)
+ public static final int STATUS_UNSUPPORTED_OPERATION = 6;
+
/** @hide */
@IntDef(prefix = { "STATUS_" }, value = {
STATUS_UNKNOWN,
@@ -106,7 +114,8 @@
STATUS_UNSUPPORTED,
STATUS_SERVICE_UNAVAILABLE,
STATUS_WEARABLE_UNAVAILABLE,
- STATUS_ACCESS_DENIED
+ STATUS_ACCESS_DENIED,
+ STATUS_UNSUPPORTED_OPERATION
})
@Retention(RetentionPolicy.SOURCE)
public @interface StatusCode {}
diff --git a/core/java/android/app/wearable/flags.aconfig b/core/java/android/app/wearable/flags.aconfig
new file mode 100644
index 0000000..074ce9b
--- /dev/null
+++ b/core/java/android/app/wearable/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.app.wearable"
+
+flag {
+ name: "enable_unsupported_operation_status_code"
+ namespace: "machine_learning"
+ description: "This flag enables the WearableSensingManager#STATUS_UNSUPPORTED_OPERATION status code API."
+ bug: "301427767"
+}
\ No newline at end of file
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 33b1134..af13011 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -411,8 +411,8 @@
"token = " + mAttributionSourceState.token + ", " +
"deviceId = " + mAttributionSourceState.deviceId + ", " +
"next = " + (mAttributionSourceState.next != null
- && mAttributionSourceState.next.length > 0
- ? mAttributionSourceState.next[0] : null) +
+ && mAttributionSourceState.next.length > 0
+ ? new AttributionSource(mAttributionSourceState.next[0]).toString() : null) +
" }";
}
return super.toString();
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index caff457..19bce0b 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -183,3 +183,19 @@
description: "Feature flag to enable permission EMERGENCY_INSTALL_PACKAGES"
bug: "321080601"
}
+
+flag {
+ name: "asl_in_apk_app_metadata_source"
+ namespace: "package_manager_service"
+ description: "Feature flag to allow to know if the Android Safety Label (ASL) of an app is provided by the app's APK itself, or provided by an installer."
+ bug: "287487923"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "force_multi_arch_native_libs_match"
+ namespace: "package_manager_service"
+ description: "Feature flag to force an multiArch app's native libraries to match with the natively supported ABIs of the device"
+ bug: "282783453"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index efb8607..d7e64b6 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -108,3 +108,17 @@
bug: "316362775"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_permission_to_access_hidden_profiles"
+ namespace: "profile_experiences"
+ description: "Add permission to access API hidden users data via system APIs"
+ bug: "321988638"
+}
+
+flag {
+ name: "handle_interleaved_settings_for_private_space"
+ namespace: "profile_experiences"
+ description: "Handle listing of private space apps in settings pages with interleaved content"
+ bug: "323212460"
+}
diff --git a/core/java/android/content/pm/overlay/OverlayPaths.java b/core/java/android/content/pm/overlay/OverlayPaths.java
index bd74b0b..a4db733 100644
--- a/core/java/android/content/pm/overlay/OverlayPaths.java
+++ b/core/java/android/content/pm/overlay/OverlayPaths.java
@@ -49,13 +49,6 @@
public static class Builder {
final OverlayPaths mPaths = new OverlayPaths();
- public Builder() {}
-
- public Builder(@NonNull OverlayPaths base) {
- mPaths.mResourceDirs.addAll(base.getResourceDirs());
- mPaths.mOverlayPaths.addAll(base.getOverlayPaths());
- }
-
/**
* Adds a non-APK path to the contents of {@link OverlayPaths#getOverlayPaths()}.
*/
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index c7790bd..5e442b8 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -273,14 +273,27 @@
throw new NotFoundException("String resource name " + name);
}
+ private static boolean isIntLike(@NonNull String s) {
+ if (s.isEmpty() || s.length() > 10) return false;
+ for (int i = 0, size = s.length(); i < size; i++) {
+ final char c = s.charAt(i);
+ if (c < '0' || c > '9') {
+ return false;
+ }
+ }
+ return true;
+ }
+
int getIdentifier(String name, String defType, String defPackage) {
if (name == null) {
throw new NullPointerException("name is null");
}
- try {
- return Integer.parseInt(name);
- } catch (Exception e) {
- // Ignore
+ if (isIntLike(name)) {
+ try {
+ return Integer.parseInt(name);
+ } catch (Exception e) {
+ // Ignore
+ }
}
return mAssets.getResourceIdentifier(name, defType, defPackage);
}
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 90cd471..1ca11e6 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -47,4 +47,11 @@
name: "configurable_selector_ui_enabled"
description: "Enables OEM configurable Credential Selector UI"
bug: "319448437"
-}
\ No newline at end of file
+}
+
+flag {
+ namespace: "credential_manager"
+ name: "credman_biometric_api_enabled"
+ description: "Enables Credential Manager to work with the Biometric Authenticate API"
+ bug: "323211850"
+}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 64a62a9..f18a0b7 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -202,8 +202,11 @@
/**
* Called by the window manager to perform traversals while holding a
* surface flinger transaction.
+ * @param t The default transaction.
+ * @param displayTransactions The transactions mapped by display id.
*/
- public abstract void performTraversal(Transaction t);
+ public abstract void performTraversal(Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions);
/**
* Tells the display manager about properties of the display that depend on the windows on it.
diff --git a/core/java/android/provider/ContactKeysManager.java b/core/java/android/provider/ContactKeysManager.java
index ecde699..bef6456 100644
--- a/core/java/android/provider/ContactKeysManager.java
+++ b/core/java/android/provider/ContactKeysManager.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
@@ -247,6 +248,44 @@
}
/**
+ * Updates a contact key entry's local verification state that belongs to the app identified
+ * by ownerPackageName.
+ *
+ * @param lookupKey the value that references the contact
+ * @param deviceId an app-specified identifier for the device
+ * @param accountId an app-specified identifier for the account
+ * @param ownerPackageName the package name of the app that owns the key
+ * @param localVerificationState the new local verification state
+ *
+ * @return true if the entry was updated, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+ android.Manifest.permission.WRITE_CONTACTS})
+ public boolean updateContactKeyLocalVerificationState(@NonNull String lookupKey,
+ @NonNull String deviceId,
+ @NonNull String accountId,
+ @NonNull String ownerPackageName,
+ @VerificationState int localVerificationState) {
+ validateVerificationState(localVerificationState);
+
+ final Bundle extras = new Bundle();
+ extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+ extras.putInt(ContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState);
+
+ final Bundle response = nullSafeCall(mContentResolver,
+ ContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras);
+
+ return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ }
+
+ /**
* Updates a contact key entry's remote verification state that belongs to the caller app.
*
* @param lookupKey the value that references the contact
@@ -275,6 +314,45 @@
return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
}
+ /**
+ * Updates a contact key entry's remote verification state that belongs to the app identified
+ * by ownerPackageName.
+ *
+ * @param lookupKey the value that references the contact
+ * @param deviceId an app-specified identifier for the device
+ * @param accountId an app-specified identifier for the account
+ * @param ownerPackageName the package name of the app that owns the key
+ * @param remoteVerificationState the new remote verification state
+ *
+ * @return true if the entry was updated, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+ android.Manifest.permission.WRITE_CONTACTS})
+ public boolean updateContactKeyRemoteVerificationState(@NonNull String lookupKey,
+ @NonNull String deviceId,
+ @NonNull String accountId,
+ @NonNull String ownerPackageName,
+ @VerificationState int remoteVerificationState) {
+ validateVerificationState(remoteVerificationState);
+
+ final Bundle extras = new Bundle();
+ extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+ extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+
+ final Bundle response = nullSafeCall(mContentResolver,
+ ContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+
+ return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ }
+
+
private static void validateVerificationState(int verificationState) {
if (verificationState != UNVERIFIED
&& verificationState != VERIFICATION_FAILED
@@ -297,12 +375,12 @@
public boolean removeContactKey(@NonNull String lookupKey,
@NonNull String deviceId,
@NonNull String accountId) {
- Bundle extras = new Bundle();
+ final Bundle extras = new Bundle();
extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
- Bundle response = nullSafeCall(mContentResolver,
+ final Bundle response = nullSafeCall(mContentResolver,
ContactKeys.REMOVE_CONTACT_KEY_METHOD, extras);
return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
@@ -369,6 +447,41 @@
}
/**
+ * Updates a self key entry's remote verification state that belongs to the app identified
+ * by ownerPackageName.
+ *
+ * @param deviceId an app-specified identifier for the device
+ * @param accountId an app-specified identifier for the account
+ * @param ownerPackageName the package name of the app that owns the key
+ * @param remoteVerificationState the new remote verification state
+ *
+ * @return true if the entry was updated, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+ android.Manifest.permission.WRITE_CONTACTS})
+ public boolean updateSelfKeyRemoteVerificationState(@NonNull String deviceId,
+ @NonNull String accountId,
+ @NonNull String ownerPackageName,
+ @VerificationState int remoteVerificationState) {
+ validateVerificationState(remoteVerificationState);
+
+ Bundle extras = new Bundle();
+ extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+ extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+
+ Bundle response = nullSafeCall(mContentResolver,
+ ContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+
+ return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ }
+
+ /**
* Maximum size of a contact key.
*/
public static int getMaxKeySizeBytes() {
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 064bc69..35b137a 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -314,13 +314,20 @@
if (count < 2) {
return null;
}
+ View next = null;
+ final boolean[] looped = new boolean[1];
switch (direction) {
case View.FOCUS_FORWARD:
- return getNextFocusable(focused, focusables, count);
+ next = getNextFocusable(focused, focusables, count, looped);
+ break;
case View.FOCUS_BACKWARD:
- return getPreviousFocusable(focused, focusables, count);
+ next = getPreviousFocusable(focused, focusables, count, looped);
+ break;
}
- return focusables.get(count - 1);
+ if (root != null && root.mAttachInfo != null && root == root.getRootView()) {
+ root.mAttachInfo.mNextFocusLooped = looped[0];
+ }
+ return next != null ? next : focusables.get(count - 1);
}
private void setFocusBottomRight(ViewGroup root, Rect focusedRect) {
@@ -375,7 +382,8 @@
return closest;
}
- private static View getNextFocusable(View focused, ArrayList<View> focusables, int count) {
+ private static View getNextFocusable(View focused, ArrayList<View> focusables, int count,
+ boolean[] outLooped) {
if (count < 2) {
return null;
}
@@ -385,10 +393,12 @@
return focusables.get(position + 1);
}
}
+ outLooped[0] = true;
return focusables.get(0);
}
- private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) {
+ private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count,
+ boolean[] outLooped) {
if (count < 2) {
return null;
}
@@ -398,6 +408,7 @@
return focusables.get(position - 1);
}
}
+ outLooped[0] = true;
return focusables.get(count - 1);
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7903050..99863d0 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -1085,7 +1085,9 @@
void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
+ @EnforcePermission("DETECT_SCREEN_RECORDING")
boolean registerScreenRecordingCallback(IScreenRecordingCallback callback);
+ @EnforcePermission("DETECT_SCREEN_RECORDING")
void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
}
diff --git a/core/java/android/view/ScreenRecordingCallbacks.java b/core/java/android/view/ScreenRecordingCallbacks.java
new file mode 100644
index 0000000..ee55737
--- /dev/null
+++ b/core/java/android/view/ScreenRecordingCallbacks.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 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 android.view;
+
+import static android.Manifest.permission.DETECT_SCREEN_RECORDING;
+import static android.view.WindowManager.SCREEN_RECORDING_STATE_NOT_VISIBLE;
+import static android.view.WindowManager.SCREEN_RECORDING_STATE_VISIBLE;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.view.WindowManager.ScreenRecordingState;
+import android.window.IScreenRecordingCallback;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * This class is responsible for calling app-registered screen recording callbacks. This class
+ * registers a single screen recording callback with WindowManagerService and calls the
+ * app-registered callbacks whenever that WindowManagerService callback is called.
+ *
+ * @hide
+ */
+public final class ScreenRecordingCallbacks {
+
+ private static ScreenRecordingCallbacks sInstance;
+ private static final Object sLock = new Object();
+
+ private final ArrayMap<Consumer<@ScreenRecordingState Integer>, Executor> mCallbacks =
+ new ArrayMap<>();
+
+ private IScreenRecordingCallback mCallbackNotifier;
+ private @ScreenRecordingState int mState = SCREEN_RECORDING_STATE_NOT_VISIBLE;
+
+ private ScreenRecordingCallbacks() {}
+
+ private static @NonNull IWindowManager getWindowManagerService() {
+ return Objects.requireNonNull(WindowManagerGlobal.getWindowManagerService());
+ }
+
+ static ScreenRecordingCallbacks getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new ScreenRecordingCallbacks();
+ }
+ return sInstance;
+ }
+ }
+
+ @RequiresPermission(DETECT_SCREEN_RECORDING)
+ @ScreenRecordingState
+ int addCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ synchronized (sLock) {
+ if (mCallbackNotifier == null) {
+ mCallbackNotifier =
+ new IScreenRecordingCallback.Stub() {
+ @Override
+ public void onScreenRecordingStateChanged(
+ boolean visibleInScreenRecording) {
+ int state =
+ visibleInScreenRecording
+ ? SCREEN_RECORDING_STATE_VISIBLE
+ : SCREEN_RECORDING_STATE_NOT_VISIBLE;
+ notifyCallbacks(state);
+ }
+ };
+ try {
+ boolean visibleInScreenRecording =
+ getWindowManagerService()
+ .registerScreenRecordingCallback(mCallbackNotifier);
+ mState =
+ visibleInScreenRecording
+ ? SCREEN_RECORDING_STATE_VISIBLE
+ : SCREEN_RECORDING_STATE_NOT_VISIBLE;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ mCallbacks.put(callback, executor);
+ return mState;
+ }
+ }
+
+ @RequiresPermission(DETECT_SCREEN_RECORDING)
+ void removeCallback(@NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ synchronized (sLock) {
+ mCallbacks.remove(callback);
+ if (mCallbacks.isEmpty()) {
+ try {
+ getWindowManagerService().unregisterScreenRecordingCallback(mCallbackNotifier);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ mCallbackNotifier = null;
+ }
+ }
+ }
+
+ private void notifyCallbacks(@ScreenRecordingState int state) {
+ List<Runnable> callbacks;
+ synchronized (sLock) {
+ mState = state;
+ if (mCallbacks.isEmpty()) {
+ return;
+ }
+
+ callbacks = new ArrayList<>();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ Consumer<Integer> callback = mCallbacks.keyAt(i);
+ Executor executor = mCallbacks.valueAt(i);
+ callbacks.add(() -> executor.execute(() -> callback.accept(state)));
+ }
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ for (int i = 0; i < callbacks.size(); i++) {
+ callbacks.get(i).run();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1b22fda..2366ff7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5537,10 +5537,20 @@
*/
private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
+
+ private static final long INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
+ private static final int INFREQUENT_UPDATE_COUNTS = 2;
+
// The preferred frame rate of the view that is mainly used for
// touch boosting, view velocity handling, and TextureView.
private float mPreferredFrameRate = REQUESTED_FRAME_RATE_CATEGORY_DEFAULT;
+ private int mInfrequentUpdateCount = 0;
+ private long mLastUpdateTimeMillis = 0;
+ private long mMinusOneFrameIntervalMillis = 0;
+ private long mMinusTwoFrameIntervalMillis = 0;
+ private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = 0;
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
@@ -20253,7 +20263,10 @@
}
// For VRR to vote the preferred frame rate
- votePreferredFrameRate();
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ updateInfrequentCount();
+ votePreferredFrameRate();
+ }
// Reset content capture caches
mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
@@ -20358,7 +20371,10 @@
protected void damageInParent() {
if (mParent != null && mAttachInfo != null) {
// For VRR to vote the preferred frame rate
- votePreferredFrameRate();
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ updateInfrequentCount();
+ votePreferredFrameRate();
+ }
mParent.onDescendantInvalidated(this, this);
}
}
@@ -31336,6 +31352,13 @@
final ArrayList<View> mTempArrayList = new ArrayList<View>(24);
/**
+ * Indicates if the next focus will be looped back to the first focusable view of the entire
+ * hierarchy when finding in the direction of {@link #FOCUS_FORWARD} or to the last
+ * focusable view when finding in the direction of {@link #FOCUS_BACKWARD}.
+ */
+ boolean mNextFocusLooped = false;
+
+ /**
* The id of the window for accessibility purposes.
*/
int mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
@@ -33124,11 +33147,20 @@
}
private int calculateFrameRateCategory(float sizePercentage) {
- if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
- return FRAME_RATE_CATEGORY_LOW;
- } else {
+ if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis
+ < INFREQUENT_UPDATE_INTERVAL_MILLIS) {
+ if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
+ return FRAME_RATE_CATEGORY_NORMAL;
+ } else {
+ return FRAME_RATE_CATEGORY_HIGH;
+ }
+ }
+
+ if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) {
return FRAME_RATE_CATEGORY_NORMAL;
}
+
+ return mLastFrameRateCategory;
}
private void votePreferredFrameRate() {
@@ -33137,22 +33169,22 @@
float sizePercentage = getSizePercentage();
int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
if (viewRootImpl != null && sizePercentage > 0) {
- if (sToolkitSetFrameRateReadOnlyFlagValue) {
- if (mPreferredFrameRate < 0) {
- if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
- frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
- frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
- }
- } else {
- viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
+ if (mPreferredFrameRate < 0) {
+ if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
}
- viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+ } else {
+ viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
}
+ viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+ mLastFrameRateCategory = frameRateCateogry;
+
if (sToolkitMetricsForFrameRateDecisionFlagValue) {
viewRootImpl.recordViewPercentage(sizePercentage);
}
@@ -33231,4 +33263,27 @@
}
return 0;
}
+
+ /**
+ * This function is mainly used for migrating infrequent layer lagic
+ * from SurfaceFlinger to Toolkit.
+ * The infrequent layter logic includes:
+ * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
+ * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
+ * - otherwise, use the previous category value.
+ */
+ private void updateInfrequentCount() {
+ long currentTimeMillis = AnimationUtils.currentAnimationTimeMillis();
+ long timeIntervalMillis = currentTimeMillis - mLastUpdateTimeMillis;
+ mMinusTwoFrameIntervalMillis = mMinusOneFrameIntervalMillis;
+ mMinusOneFrameIntervalMillis = timeIntervalMillis;
+
+ mLastUpdateTimeMillis = currentTimeMillis;
+ if (timeIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) {
+ mInfrequentUpdateCount = mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS
+ ? mInfrequentUpdateCount : mInfrequentUpdateCount + 1;
+ } else {
+ mInfrequentUpdateCount = 0;
+ }
+ }
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c18aeee..c27b2b1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1025,6 +1025,13 @@
// time for revaluating the idle status before lowering the frame rate.
private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500;
+ /*
+ * the variables below are used to determine whther a dVRR feature should be enabled
+ */
+
+ // Used to determine whether to suppress boost on typing
+ private boolean mShouldSuppressBoostOnTyping = false;
+
/**
* A temporary object used so relayoutWindow can return the latest SyncSeqId
* system. The SyncSeqId system was designed to work without synchronous relayout
@@ -7283,8 +7290,18 @@
if (direction != 0) {
View focused = mView.findFocus();
if (focused != null) {
+ mAttachInfo.mNextFocusLooped = false;
View v = focused.focusSearch(direction);
if (v != null && v != focused) {
+ if (mAttachInfo.mNextFocusLooped) {
+ // The next focus is looped. Let's try to move the focus to the adjacent
+ // window. Note: we still need to move the focus in this window
+ // regardless of what moveFocusToAdjacentWindow returns, so the focus
+ // can be looped back from the focus in the adjacent window to next
+ // focus of this window.
+ moveFocusToAdjacentWindow(direction);
+ }
+
// do the math the get the interesting rect
// of previous focused into the coord system of
// newly focused view
@@ -12275,7 +12292,7 @@
boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
|| motionEventAction == MotionEvent.ACTION_MOVE
|| motionEventAction == MotionEvent.ACTION_UP;
- boolean undesiredType = windowType == TYPE_INPUT_METHOD;
+ boolean undesiredType = windowType == TYPE_INPUT_METHOD && mShouldSuppressBoostOnTyping;
// use toolkitSetFrameRate flag to gate the change
return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue
&& getFrameRateBoostOnTouchEnabled();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c788261..38cf490 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -128,8 +128,10 @@
import com.android.window.flags.Flags;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -6117,4 +6119,65 @@
throw new UnsupportedOperationException(
"getDefaultToken is not implemented");
}
+
+ /** @hide */
+ @Target(ElementType.TYPE_USE)
+ @IntDef(
+ prefix = {"SCREEN_RECORDING_STATE"},
+ value = {SCREEN_RECORDING_STATE_NOT_VISIBLE, SCREEN_RECORDING_STATE_VISIBLE})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ScreenRecordingState {}
+
+ /** Indicates the app that registered the callback is not visible in screen recording. */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ int SCREEN_RECORDING_STATE_NOT_VISIBLE = 0;
+
+ /** Indicates the app that registered the callback is visible in screen recording. */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ int SCREEN_RECORDING_STATE_VISIBLE = 1;
+
+ /**
+ * Adds a screen recording callback. The callback will be invoked whenever the app becomes
+ * visible in screen recording or was visible in screen recording and becomes invisible in
+ * screen recording.
+ *
+ * <p>An app is considered visible in screen recording if any activities owned by the
+ * registering process's UID are being recorded.
+ *
+ * <p>Example:
+ *
+ * <pre>
+ * windowManager.addScreenRecordingCallback(state -> {
+ * // handle change in screen recording state
+ * });
+ * </pre>
+ *
+ * @param executor The executor on which callback method will be invoked.
+ * @param callback The callback that will be invoked when screen recording visibility changes.
+ * @return the current screen recording state.
+ * @see #SCREEN_RECORDING_STATE_NOT_VISIBLE
+ * @see #SCREEN_RECORDING_STATE_VISIBLE
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @RequiresPermission(permission.DETECT_SCREEN_RECORDING)
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ default @ScreenRecordingState int addScreenRecordingCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Removes a screen recording callback.
+ *
+ * @param callback The callback to remove.
+ * @see #addScreenRecordingCallback(Executor, Consumer)
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @RequiresPermission(permission.DETECT_SCREEN_RECORDING)
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ default void removeScreenRecordingCallback(
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 5072ad7..eaf45c4 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -20,6 +20,8 @@
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.window.WindowProviderService.isWindowProviderService;
+import static com.android.window.flags.Flags.screenRecordingCallbacks;
+
import android.annotation.CallbackExecutor;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -551,4 +553,25 @@
public IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) {
return mGlobal.getSurfaceControlInputClientToken(surfaceControl);
}
+
+ @Override
+ public @ScreenRecordingState int addScreenRecordingCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ if (screenRecordingCallbacks()) {
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(callback, "callback must not be null");
+ return ScreenRecordingCallbacks.getInstance().addCallback(executor, callback);
+ }
+ return SCREEN_RECORDING_STATE_NOT_VISIBLE;
+ }
+
+ @Override
+ public void removeScreenRecordingCallback(
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ if (screenRecordingCallbacks()) {
+ Objects.requireNonNull(callback, "callback must not be null");
+ ScreenRecordingCallbacks.getInstance().removeCallback(callback);
+ }
+ }
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index ef1bf5a..146b576 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -2424,7 +2424,7 @@
}
}
try {
- service.attachAccessibilityOverlayToDisplay_enforcePermission(
+ service.attachAccessibilityOverlayToDisplay(
displayId, surfaceControl);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 1c5d29e..eca1586 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -139,5 +139,5 @@
WindowTransformationSpec getWindowTransformationSpec(int windowId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
- void attachAccessibilityOverlayToDisplay_enforcePermission(int displayId, in SurfaceControl surfaceControl);
+ void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl surfaceControl);
}
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 0aa516e..9d613bc 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -50,4 +50,28 @@
description: "Feature flag for toolkit metrics collecting for frame rate decision"
bug: "301343249"
is_fixed_read_only: true
+}
+
+flag {
+ name: "toolkit_frame_rate_default_normal_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for setting frame rate category as NORMAL for default"
+ bug: "239979904"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "toolkit_frame_rate_by_size_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for setting frame rate category based on size"
+ bug: "239979904"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "toolkit_frame_rate_velocity_mapping_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for setting frame rate based on velocity"
+ bug: "239979904"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index c6271d2..2f765ae 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -16,7 +16,6 @@
package android.webkit;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.Compatibility;
@@ -47,8 +46,7 @@
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
- @FlaggedApi(android.os.Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM)
- public static final long PARSE_CONTENT_DISPOSITION_USING_RFC_6266 = 319400769L;
+ static final long PARSE_CONTENT_DISPOSITION_USING_RFC_6266 = 319400769L;
private static final String LOGTAG = "webkit";
private static final boolean TRACE = false;
diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig
new file mode 100644
index 0000000..9f0b7c3
--- /dev/null
+++ b/core/java/android/widget/flags/notification_widget_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.widget.flags"
+
+flag {
+ name: "notif_linearlayout_optimized"
+ namespace: "systemui"
+ description: "Enables notification specific LinearLayout optimization"
+ bug: "316110233"
+}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0171f58..6be1be4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -881,6 +881,19 @@
android:description="@string/permdesc_writeContacts"
android:protectionLevel="dangerous" />
+ <!-- Allows an app to update the verification status of E2EE contact keys owned by other apps.
+ <p>This permission is only granted to system apps.
+ <p>Protection level: signature|privileged
+ @SystemApi
+ @hide
+ @FlaggedApi("android.provider.user_keys")
+ -->
+ <permission android:name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_writeVerificationStateE2eeContactKeys"
+ android:description="@string/permdesc_writeVerificationStateE2eeContactKeys"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows an application to set default account for new contacts.
<p> This permission is only granted to system applications fulfilling the Contacts app role.
<p>Protection level: internal|role
@@ -2632,6 +2645,13 @@
android:description="@string/permdesc_detectScreenCapture"
android:protectionLevel="normal" />
+ <!-- Allows an application to get notified when it is being recorded.
+ <p>Protection level: normal
+ @FlaggedApi("com.android.window.flags.screen_recording_callbacks")
+ -->
+ <permission android:name="android.permission.DETECT_SCREEN_RECORDING"
+ android:protectionLevel="normal" />
+
<!-- ======================================== -->
<!-- Permissions for factory reset protection -->
<!-- ======================================== -->
@@ -3188,6 +3208,14 @@
<permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
android:protectionLevel="signature|appop" />
+ <!-- Allows applications to access profiles with ACCESS_HIDDEN_PROFILES user property
+ <p>Protection level: normal
+ @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") -->
+ <permission android:name="android.permission.ACCESS_HIDDEN_PROFILES"
+ android:label="@string/permlab_accessHiddenProfile"
+ android:description="@string/permdesc_accessHiddenProfile"
+ android:protectionLevel="normal" />
+
<!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
<permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
android:protectionLevel="signature|role" />
@@ -7828,7 +7856,7 @@
android:protectionLevel="normal"/>
<!-- @FlaggedApi("android.app.job.backup_jobs_exemption")
- Gives applications whose <b>primary use case</b> is to backup or sync content increased
+ Gives applications with a <b>major use case</b> of backing-up or syncing content increased
job execution allowance in order to complete the related work. The jobs must have a valid
content URI trigger and network constraint set.
<p>This is a special access permission that can be revoked by the system or the user.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index dd6e79e..be96cc2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -396,6 +396,27 @@
<!-- Displayed when the call forwarding query was set but forwarding is not enabled. -->
<string name="cfTemplateRegisteredTime"><xliff:g id="bearer_service_code">{0}</xliff:g>: Not forwarded</string>
+ <!-- Title of the cellular network security safety center source's status. -->
+ <string name="scCellularNetworkSecurityTitle">Cellular network security</string>
+ <!-- Summary of the cellular network security safety center source's status. -->
+ <string name="scCellularNetworkSecuritySummary">Review settings</string>
+ <!-- Title of the safety center issue and notification when the phone's identifier is shared over the network. -->
+ <string name="scIdentifierDisclosureIssueTitle">Device identifier accessed</string>
+ <!-- Summary of the safety center issue and notification when the phone's identifier is shared over the network. -->
+ <string name="scIdentifierDisclosureIssueSummary">A network on the <xliff:g id="disclosure_network">%4$s</xliff:g> connection recorded your device\'s unique identifier (IMSI) <xliff:g id="disclosure_count">%1$d</xliff:g> times in the period between <xliff:g id="disclosure_window_start_time">%2$tr</xliff:g> and <xliff:g id="disclosure_window_end_time">%3$tr</xliff:g>.</string>
+ <!-- Title of the safety center issue and notification when the phone restores an encrypted connection to the network. -->
+ <string name="scNullCipherIssueEncryptedTitle">Encrypted connection to <xliff:g id="network_name">%1$s</xliff:g></string>
+ <!-- Summary of the safety center issue and notification when the phone restores an encrypted connection to the network. -->
+ <string name="scNullCipherIssueEncryptedSummary">You\'re now connected to a more secure cellular network.</string>
+ <!-- Title of the safety center issue and notification when a connected network is not using encryption. -->
+ <string name="scNullCipherIssueNonEncryptedTitle">Non-encrypted connection to <xliff:g id="network_name">%1$s</xliff:g></string>
+ <!-- Summary of the safety center issue and notification when a connected network is not using encryption. -->
+ <string name="scNullCipherIssueNonEncryptedSummary">You\'re connected to a non-encrypted cellular network. Your calls, messages, and data are vulnerable to interception.</string>
+ <!-- Label for the button that links to the cellular network security settings. -->
+ <string name="scNullCipherIssueActionSettings">Cellular security settings</string>
+ <!-- Label for the button that link to education resourcess about cellular network security settings. -->
+ <string name="scNullCipherIssueActionLearnMore">Learn more</string>
+
<!-- android.net.http Error strings --> <skip />
<!-- Displayed when a feature code (non-phone number) is dialed and completes successfully. -->
<string name="fcComplete">Feature code complete.</string>
@@ -1572,6 +1593,11 @@
<string name="permdesc_setWallpaper">Allows the app to set the system wallpaper.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_accessHiddenProfile">Access hidden profiles</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_accessHiddenProfile">Allows the app to access hidden profiles.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_setWallpaperHints">adjust your wallpaper size</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_setWallpaperHints">Allows the app to set the system wallpaper size hints.</string>
@@ -2222,6 +2248,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
<string name="permdesc_updatePackagesWithoutUserAction">Allows the holder to update the app it previously installed without user action</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+ <string name="permlab_writeVerificationStateE2eeContactKeys">update the verification states of E2EE contact keys owned by other apps</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
+ <string name="permdesc_writeVerificationStateE2eeContactKeys">Allows the app to update the verification states of E2EE contact keys owned by other apps</string>
+
<!-- Policy administration -->
<!-- Title of policy access to limiting the user's password choices -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 699c8ac..603b902 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -954,6 +954,16 @@
<java-symbol type="string" name="roamingText8" />
<java-symbol type="string" name="roamingText9" />
<java-symbol type="string" name="roamingTextSearching" />
+ <java-symbol type="string" name="scCellularNetworkSecuritySummary" />
+ <java-symbol type="string" name="scCellularNetworkSecurityTitle" />
+ <java-symbol type="string" name="scIdentifierDisclosureIssueSummary" />
+ <java-symbol type="string" name="scIdentifierDisclosureIssueTitle" />
+ <java-symbol type="string" name="scNullCipherIssueActionLearnMore" />
+ <java-symbol type="string" name="scNullCipherIssueActionSettings" />
+ <java-symbol type="string" name="scNullCipherIssueEncryptedSummary" />
+ <java-symbol type="string" name="scNullCipherIssueEncryptedTitle" />
+ <java-symbol type="string" name="scNullCipherIssueNonEncryptedSummary" />
+ <java-symbol type="string" name="scNullCipherIssueNonEncryptedTitle" />
<java-symbol type="string" name="selected" />
<java-symbol type="string" name="sendText" />
<java-symbol type="string" name="sending" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index f476799..513e022 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -203,7 +203,6 @@
"androidx.test.uiautomator_uiautomator",
"compatibility-device-util-axt",
"flag-junit",
- "mockito_ravenwood",
"platform-test-annotations",
"flag-junit",
"testng",
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index cf3eb12..60769c7 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -18,6 +18,7 @@
import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
+import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
@@ -475,8 +476,9 @@
* Also, mIsFrameRateBoosting should be true when the visibility becomes visible
*/
@Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public void votePreferredFrameRate_voteFrameRateCategory_visibility() {
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+ public void votePreferredFrameRate_voteFrameRateCategory_visibility_bySize() {
View view = new View(sContext);
attachViewToWindow(view);
ViewRootImpl viewRootImpl = view.getViewRootImpl();
@@ -507,8 +509,9 @@
* <7%: FRAME_RATE_CATEGORY_LOW
*/
@Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public void votePreferredFrameRate_voteFrameRateCategory_smallSize() {
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+ public void votePreferredFrameRate_voteFrameRateCategory_smallSize_bySize() {
View view = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -534,8 +537,9 @@
* >=7% : FRAME_RATE_CATEGORY_NORMAL
*/
@Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public void votePreferredFrameRate_voteFrameRateCategory_normalSize() {
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+ public void votePreferredFrameRate_voteFrameRateCategory_normalSize_bySize() {
View view = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -559,6 +563,96 @@
}
/**
+ * Test the value of the frame rate cateogry based on the visibility of a view
+ * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE
+ * Visible: FRAME_RATE_CATEGORY_HIGH
+ * Also, mIsFrameRateBoosting should be true when the visibility becomes visible
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_voteFrameRateCategory_visibility_defaultHigh() {
+ View view = new View(sContext);
+ attachViewToWindow(view);
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.setVisibility(View.INVISIBLE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ sInstrumentation.runOnMainSync(() -> {
+ view.setVisibility(View.VISIBLE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_HIGH);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ sInstrumentation.runOnMainSync(() -> {
+ assertEquals(viewRootImpl.getIsFrameRateBoosting(), true);
+ });
+ }
+
+ /**
+ * Test the value of the frame rate cateogry based on the size of a view.
+ * The current threshold value is 7% of the screen size
+ * <7%: FRAME_RATE_CATEGORY_NORMAL
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_voteFrameRateCategory_smallSize_defaultHigh() {
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+ wmlp.width = 1;
+ wmlp.height = 1;
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ });
+ }
+
+ /**
+ * Test the value of the frame rate cateogry based on the size of a view.
+ * The current threshold value is 7% of the screen size
+ * >=7% : FRAME_RATE_CATEGORY_HIGH
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_voteFrameRateCategory_normalSize_defaultHigh() {
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ Display display = wm.getDefaultDisplay();
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getMetrics(metrics);
+ wmlp.width = (int) (metrics.widthPixels * 0.9);
+ wmlp.height = (int) (metrics.heightPixels * 0.9);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ });
+ }
+
+ /**
* Test how values of the frame rate cateogry are aggregated.
* It should take the max value among all of the voted categories per frame.
*/
@@ -701,6 +795,61 @@
});
}
+ /**
+ * Test the logic of infrequent layer:
+ * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
+ * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
+ * - otherwise, use the previous category value.
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_infrequentLayer_defaultHigh() throws InterruptedException {
+ final long delay = 200L;
+
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ Display display = wm.getDefaultDisplay();
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getMetrics(metrics);
+ wmlp.width = (int) (metrics.widthPixels * 0.9);
+ wmlp.height = (int) (metrics.heightPixels * 0.9);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+
+ // Frequent update
+ sInstrumentation.runOnMainSync(() -> {
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ });
+
+ // In transistion from frequent update to infrequent update
+ Thread.sleep(delay);
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ });
+
+ // Infrequent update
+ Thread.sleep(delay);
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ });
+ }
+
@Test
public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp
index 2ccee71..f5563a7 100644
--- a/core/tests/utiltests/Android.bp
+++ b/core/tests/utiltests/Android.bp
@@ -60,7 +60,6 @@
static_libs: [
"androidx.annotation_annotation",
"androidx.test.rules",
- "mockito_ravenwood",
"frameworks-base-testutils",
"servicestests-utils",
],
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2873428..91e620c 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -185,6 +185,7 @@
<permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
<permission name="android.permission.UWB_PRIVILEGED"/>
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+ <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
</privapp-permissions>
<privapp-permissions package="com.android.providers.calendar">
@@ -571,6 +572,8 @@
<!-- Permission required for BinaryTransparencyService shell API and host test -->
<permission name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+ <!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
+ <permission name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 2b95f30..9a66c0f 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -57,3 +57,10 @@
description: "Enables new UMO experience for PiP menu"
bug: "307998712"
}
+
+flag {
+ name: "enable_bubble_bar"
+ namespace: "multitasking"
+ description: "Enables the new bubble bar UI for tablets"
+ bug: "286246694"
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index f32f030..50a58da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -465,7 +465,7 @@
/**
* Call when all the views should be removed/cleaned up.
*/
- void cleanupViews() {
+ public void cleanupViews() {
cleanupViews(true);
}
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 aea3ca1..d0db708 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
@@ -730,7 +730,7 @@
// window to show this in, but we use a separate code path.
// TODO(b/273312602): consider foldables where we do need a stack view when folded
if (mLayerView == null) {
- mLayerView = new BubbleBarLayerView(mContext, this);
+ mLayerView = new BubbleBarLayerView(mContext, this, mBubbleData);
mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
}
} else {
@@ -1714,8 +1714,7 @@
@Override
public void removeBubble(Bubble removedBubble) {
if (mLayerView != null) {
- // TODO: need to check if there's something that needs to happen here, e.g. if
- // the currently selected & expanded bubble is removed?
+ mLayerView.removeBubble(removedBubble);
}
}
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 127c7e8..dbfa260 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
@@ -15,7 +15,6 @@
*/
package com.android.wm.shell.bubbles;
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
@@ -41,6 +40,7 @@
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubbles.DismissReason;
+import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.common.bubbles.RemovedBubble;
@@ -427,7 +427,7 @@
/**
* When this method is called it is expected that all info in the bubble has completed loading.
* @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleController, BubbleStackView,
- * BubbleIconFactory, boolean)
+ * BubbleBarLayerView, BubbleIconFactory, boolean)
*/
void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
if (DEBUG_BUBBLE_DATA) {
@@ -1069,7 +1069,6 @@
/**
* The set of bubbles in row.
*/
- @VisibleForTesting(visibility = PACKAGE)
public List<Bubble> getBubbles() {
return Collections.unmodifiableList(mBubbles);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 5fc67d7..dc27133 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -31,6 +31,7 @@
import android.graphics.Rect;
import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
import androidx.annotation.Nullable;
@@ -186,6 +187,7 @@
}
if (mTaskView != null) {
mTaskView.release();
+ ((ViewGroup) mParentView).removeView(mTaskView);
mTaskView = null;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 00d683e..73a9cf4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -266,13 +266,8 @@
mListener.onBackPressed();
}
- /** Cleans up task view, should be called when the bubble is no longer active. */
+ /** Cleans up the expanded view, should be called when the bubble is no longer active. */
public void cleanUpExpandedState() {
- if (mBubbleTaskViewHelper != null) {
- if (mTaskView != null) {
- removeView(mTaskView);
- }
- }
mMenuViewController.hideMenu(false /* animated */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index bd8ce80..b95d258 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -34,7 +34,9 @@
import android.widget.FrameLayout;
import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
@@ -61,6 +63,7 @@
private static final float SCRIM_ALPHA = 0.2f;
private final BubbleController mBubbleController;
+ private final BubbleData mBubbleData;
private final BubblePositioner mPositioner;
private final BubbleBarAnimationHelper mAnimationHelper;
private final BubbleEducationViewController mEducationViewController;
@@ -85,9 +88,10 @@
private TouchDelegate mHandleTouchDelegate;
private final Rect mHandleTouchBounds = new Rect();
- public BubbleBarLayerView(Context context, BubbleController controller) {
+ public BubbleBarLayerView(Context context, BubbleController controller, BubbleData bubbleData) {
super(context);
mBubbleController = controller;
+ mBubbleData = bubbleData;
mPositioner = mBubbleController.getPositioner();
mAnimationHelper = new BubbleBarAnimationHelper(context,
@@ -236,15 +240,44 @@
showScrim(true);
}
+ /** Removes the given {@code bubble}. */
+ public void removeBubble(Bubble bubble) {
+ if (mBubbleData.getBubbles().isEmpty()) {
+ // we're removing the last bubble. collapse the expanded view and cleanup bubble views
+ // at the end.
+ collapse(bubble::cleanupViews);
+ } else {
+ bubble.cleanupViews();
+ }
+ }
+
/** Collapses any showing expanded view */
public void collapse() {
+ collapse(/* endAction= */ null);
+ }
+
+ /**
+ * Collapses any showing expanded view.
+ *
+ * @param endAction an action to run and the end of the collapse animation.
+ */
+ public void collapse(@Nullable Runnable endAction) {
+ if (!mIsExpanded) {
+ return;
+ }
mIsExpanded = false;
final BubbleBarExpandedView viewToRemove = mExpandedView;
mEducationViewController.hideEducation(/* animated = */ true);
+ Runnable runnable = () -> {
+ removeView(viewToRemove);
+ if (endAction != null) {
+ endAction.run();
+ }
+ };
if (mDragController != null && mDragController.isStuckToDismiss()) {
- mAnimationHelper.animateDismiss(() -> removeView(viewToRemove));
+ mAnimationHelper.animateDismiss(runnable);
} else {
- mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
+ mAnimationHelper.animateCollapse(runnable);
}
mBubbleController.getSysuiProxy().onStackExpandChanged(false);
mExpandedView = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
new file mode 100644
index 0000000..fd91ac0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.sysui.ShellCommandHandler
+import java.io.PrintWriter
+
+/**
+ * Handles the shell commands for the DesktopTasksController.
+ */
+class DesktopModeShellCommandHandler(private val controller: DesktopTasksController) :
+ ShellCommandHandler.ShellCommandActionHandler {
+
+ override fun onShellCommand(args: Array<String>, pw: PrintWriter): Boolean {
+ return when (args[0]) {
+ "moveToDesktop" -> {
+ if (!runMoveToDesktop(args, pw)) {
+ pw.println("Task not found. Please enter a valid taskId.")
+ false
+ } else {
+ true
+ }
+ }
+
+ else -> {
+ pw.println("Invalid command: ${args[0]}")
+ false
+ }
+ }
+ }
+
+ private fun runMoveToDesktop(args: Array<String>, pw: PrintWriter): Boolean {
+ if (args.size < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments")
+ return false
+ }
+
+ val taskId = try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: task id should be an integer")
+ return false
+ }
+
+ return controller.moveToDesktopWithoutDecor(taskId, WindowContainerTransaction())
+ }
+
+ override fun printShellCommandHelp(pw: PrintWriter, prefix: String) {
+ pw.println("$prefix moveToDesktop <taskId> ")
+ pw.println("$prefix Move a task with given id to desktop mode.")
+ }
+}
\ No newline at end of file
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 a089e81..e872849 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
@@ -100,6 +100,9 @@
private val desktopMode: DesktopModeImpl
private var visualIndicator: DesktopModeVisualIndicator? = null
+ private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
+ DesktopModeShellCommandHandler(this)
+
private val mOnAnimationFinishedCallback = Consumer<SurfaceControl.Transaction> {
t: SurfaceControl.Transaction ->
visualIndicator?.releaseVisualIndicator(t)
@@ -148,6 +151,8 @@
private fun onInit() {
KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
shellCommandHandler.addDumpCallback(this::dump, this)
+ shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler,
+ this)
shellController.addExternalInterface(
ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
{ createExternalInterface() },
@@ -240,6 +245,40 @@
}
}
+ /** Move a task with given `taskId` to desktop without decor */
+ fun moveToDesktopWithoutDecor(
+ taskId: Int,
+ wct: WindowContainerTransaction
+ ): Boolean {
+ val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return false
+ moveToDesktopWithoutDecor(task, wct)
+ return true
+ }
+
+ /**
+ * Move a task to desktop without decor
+ */
+ private fun moveToDesktopWithoutDecor(
+ task: RunningTaskInfo,
+ wct: WindowContainerTransaction
+ ) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: moveToDesktopWithoutDecor taskId=%d",
+ task.taskId
+ )
+ exitSplitIfApplicable(wct, task)
+ // Bring other apps to front first
+ bringDesktopAppsToFront(task.displayId, wct)
+ addMoveToDesktopChanges(wct, task)
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
/**
* Move a task to desktop
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
index 73a7348..3fad28a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
@@ -15,4 +15,4 @@
Todo
- Per-feature docs
- Feature flagging
-- Best practices
\ No newline at end of file
+- Best practices & patterns
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index fbf326e..9aa5f4f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -102,5 +102,5 @@
Launcher uses.
If the new code doesn't fall into those categories, they can be added explicitly in the Shell's
-[Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp) file under the
+[Android.bp](/libs/WindowManager/Shell/Android.bp) file under the
`wm_shell_util-sources` filegroup.
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
index 6c01d96..7070dea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
@@ -21,16 +21,16 @@
(especially as products override components).
The module dependency tree looks a bit like:
-- [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
+- [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
(provides threading-related components)
- - [WMShellBaseModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
+ - [WMShellBaseModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
(provides components that are likely common to all products, ie. DisplayController,
Transactions, etc.)
- - [WMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
+ - [WMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
(phone/tablet specific components only)
- - [TvPipModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
+ - [TvPipModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
(PIP specific components for TV)
- - [TvWMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
+ - [TvWMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
(TV specific components only)
- etc.
@@ -43,7 +43,7 @@
product it runs on. If there are hooks that can be added to the component, that is the
preferable approach.
-The alternative is to use the [@DynamicOverride](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
+The alternative is to use the [@DynamicOverride](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
annotation to allow the product module to provide an implementation that the base module can
reference. This is most useful if the existence of the entire component is controlled by the
product and the override implementation is optional (there is a default implementation). More
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index f9ea1d4..438aa76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -29,30 +29,78 @@
### Kotlin
Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)).
-For logging in Kotlin, use the [KtProtoLog](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
+For logging in Kotlin, use the [KtProtoLog](/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
class which has a similar API to the Java ProtoLog class.
### Enabling ProtoLog command line logging
-Run these commands to enable protologs for both WM Core and WM Shell to print to logcat.
+Run these commands to enable protologs (in logcat) for WM Core ([list of all core tags](/core/java/com/android/internal/protolog/ProtoLogGroup.java)):
```shell
-adb shell wm logging enable-text NEW_FEATURE
-adb shell wm logging disable-text NEW_FEATURE
+adb shell wm logging enable-text TAG
+adb shell wm logging disable-text TAG
+```
+
+And these commands to enable protologs (in logcat) for WM Shell ([list of all shell tags](/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java)):
+```shell
+adb shell dumpsys activity service SystemUIService WMShell protolog enable-text TAG
+adb shell dumpsys activity service SystemUIService WMShell protolog enable-text TAG
```
## Winscope Tracing
The Winscope tool is extremely useful in determining what is happening on-screen in both
WindowManager and SurfaceFlinger. Follow [go/winscope](http://go/winscope-help) to learn how to
-use the tool.
+use the tool. This trace will contain all the information about the windows/activities/surfaces on
+screen.
-In addition, there is limited preliminary support for Winscope tracing componetns in the Shell,
-which involves adding trace fields to [wm_shell_trace.proto](frameworks/base/libs/WindowManager/Shell/proto/wm_shell_trace.proto)
-file and ensure it is updated as a part of `WMShell#writeToProto`.
+## WindowManager/SurfaceFlinger hierarchy dump
-Tracing can be started via the shell command (to be added to the Winscope tool as needed):
+A quick way to view the WindowManager hierarchy without a winscope trace is via the wm dumps:
```shell
-adb shell cmd statusbar tracing start
-adb shell cmd statusbar tracing stop
+adb shell dumpsys activity containers
+```
+
+Likewise, the SurfaceFlinger hierarchy can be dumped for inspection by running:
+```shell
+adb shell dumpsys SurfaceFlinger
+# Search output for "Layer Hierarchy"
+```
+
+## Tracing global SurfaceControl transaction updates
+
+While Winscope traces are very useful, it sometimes doesn't give you enough information about which
+part of the code is initiating the transaction updates. In such cases, it can be helpful to get
+stack traces when specific surface transaction calls are made, which is possible by enabling the
+following system properties for example:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha # matches the name of the SurfaceControlTransaction method
+adb shell setprop persist.wm.debug.sc.tx.log_match_name com.android.systemui # matches the name of the surface
+adb reboot
+adb logcat -s "SurfaceControlRegistry"
+
+# Disabling logging
+adb shell setprop persist.wm.debug.sc.tx.log_match_call \"\"
+adb shell setprop persist.wm.debug.sc.tx.log_match_name \"\"
+adb reboot
+```
+
+It is not necessary to set both `log_match_call` and `log_match_name`, but note logs can be quite
+noisy if unfiltered.
+
+## Tracing activity starts in the app process
+
+It's sometimes useful to know when to see a stack trace of when an activity starts in the app code
+(ie. if you are repro'ing a bug related to activity starts). You can enable this system property to
+get this trace:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.start_activity true
+adb reboot
+adb logcat -s "Instrumentation"
+
+# Disabling
+adb shell setprop persist.wm.debug.start_activity \"\"
+adb reboot
```
## Dumps
@@ -69,6 +117,21 @@
- Update `WMShell` if you are dumping SysUI state
- Inject `ShellCommandHandler` into your Shell class, and add a dump callback
+## Shell commands
+
+It can be useful to add additional shell commands to drive and test specific interactions.
+
+To add a new command for your feature, inject a `ShellCommandHandler` into your class and add a
+shell command handler in your controller.
+
+```shell
+# List all available commands
+adb shell dumpsys activity service SystemUIService WMShell help
+
+# Run a specific command
+adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
+```
+
## Debugging in Android Studio
If you are using the [go/sysui-studio](http://go/sysui-studio) project, then you can debug Shell
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
index a88ef6a..b489fe8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
@@ -19,25 +19,24 @@
## Where does the code live
-The core WMShell library code is currently located in the [frameworks/base/libs/WindowManager/Shell](frameworks/base/libs/WindowManager/Shell)
+The core WMShell library code is currently located in the [frameworks/base/libs/WindowManager/Shell](/libs/WindowManager/Shell)
directory and is included as a part dependency of the host SystemUI apk.
## How do I build the Shell library
-The library can be built directly by running (using [go/makepush](http://go/makepush)):
+The library can be built directly by running:
```shell
-mp :WindowManager-Shell
+m WindowManager-Shell
```
But this is mainly useful for inspecting the contents of the library or verifying it builds. The
-various targets can be found in the Shell library's [Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp)
+various targets can be found in the Shell library's [Android.bp](/libs/WindowManager/Shell/Android.bp)
file.
Normally, you would build it as a part of the host SystemUI, for example via commandline:
```shell
# Phone SystemUI variant
-mp sysuig
-# Building Shell & SysUI changes along w/ framework changes
-mp core services sysuig
+m SystemUI
+adevice update
```
Or preferably, if you are making WMShell/SysUI only changes (no other framework changes), then
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md
new file mode 100644
index 0000000..0e20a8e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md
@@ -0,0 +1,30 @@
+# Pattern (one line description)
+
+### What this pattern solves
+
+Give detailed information about the problem that this pattern addresses, include when it
+should/should not be used.
+
+### How it works
+
+Give a high level overview of how this pattern works technically with sufficient links for the
+relevant dependencies for folks to read more.
+
+### Code examples
+
+Explain how this pattern is used in code.
+
+File.kt:
+```kotlin
+fun someFunction() {
+ // Use this code
+}
+```
+
+### Relevant links
+
+Add relevant links to other files that implement this pattern, design docs, or other important
+info.
+
+Link 1: [More info](some_example_link) \
+Link 2: [More info](some_example_link)
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
index d6302e6..30ff669 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
@@ -80,4 +80,6 @@
# Run a specific command
adb shell dumpsys activity service SystemUIService WMShell help
adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
-```
\ No newline at end of file
+```
+
+More detail can be found in [Debugging in the Shell](debugging.md) section.
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
index 8a80333..98af930 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
@@ -5,7 +5,7 @@
## Unit tests
New WM Shell unit tests can be added to the
-[Shell/tests/unittest](frameworks/base/libs/WindowManager/Shell/tests/unittest) directory, and can
+[Shell/tests/unittest](/libs/WindowManager/Shell/tests/unittest) directory, and can
be run via command line using `atest`:
```shell
atest WMShellUnitTests
@@ -25,10 +25,24 @@
and SurfaceFlinger traces captured during the run.
New WM Shell Flicker tests can be added to the
-[Shell/tests/flicker](frameworks/base/libs/WindowManager/Shell/tests/flicker) directory, and can
-be run via command line using `atest`:
+[Shell/tests/flicker](/libs/WindowManager/Shell/tests/flicker) directory, and can be run via command line using `atest`:
```shell
-atest WMShellFlickerTests
+# Bubbles
+atest WMShellFlickerTestsBubbles
+
+# PIP
+atest WMShellFlickerTestsPip1
+atest WMShellFlickerTestsPip2
+atest WMShellFlickerTestsPip3
+atest WMShellFlickerTestsPipApps
+atest WMShellFlickerTestsPipAppsCSuite
+
+# Splitscreen
+atest WMShellFlickerTestsSplitScreenGroup1
+atest WMShellFlickerTestsSplitScreenGroup2
+
+# Other
+atest WMShellFlickerTestsOther
```
**Note**: Currently Flicker tests can only be run from the commandline and not via SysUI Studio
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
index eac74889..9d01535 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
@@ -43,7 +43,7 @@
## Dagger setup
-The threading-related components are provided by the [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java),
+The threading-related components are provided by the [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java),
for example, the Executors and Handlers for the various threads that are used. You can request
an executor of the necessary type by using the appropriate annotation for each of the threads (ie.
`@ShellMainThread Executor`) when injecting into your Shell component.
@@ -76,7 +76,7 @@
want to dedupe multiple messages
- In such cases inject `@ShellMainThread Handler` or use view.getHandler() which should be OK
assuming that the view root was initialized on the main Shell thread
-- **Never use Looper.getMainLooper()**
+- <u>**Never</u> use Looper.getMainLooper()**
- It's likely going to be wrong, you can inject `@Main ShellExecutor` to get the SysUI main thread
### Testing
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
index 99df6a3..0c25f27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -18,6 +18,7 @@
import android.internal.perfetto.protos.PerfettoTrace;
import android.os.SystemClock;
+import android.os.Trace;
import android.tracing.perfetto.DataSourceInstance;
import android.tracing.perfetto.DataSourceParams;
import android.tracing.perfetto.InitArguments;
@@ -58,6 +59,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logDispatched");
+ try {
+ doLogDispatched(transitionId, handler);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogDispatched(int transitionId, Transitions.TransitionHandler handler) {
mDataSource.trace(ctx -> {
final int handlerId = getHandlerId(handler, ctx);
@@ -97,6 +107,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMergeRequested");
+ try {
+ doLogMergeRequested(mergeRequestedTransitionId, playingTransitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
@@ -120,10 +139,19 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMerged");
+ try {
+ doLogMerged(mergedTransitionId, playingTransitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogMerged(int mergeRequestedTransitionId, int playingTransitionId) {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
- os.write(PerfettoTrace.ShellTransition.ID, mergedTransitionId);
+ os.write(PerfettoTrace.ShellTransition.ID, mergeRequestedTransitionId);
os.write(PerfettoTrace.ShellTransition.MERGE_TIME_NS,
SystemClock.elapsedRealtimeNanos());
os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
@@ -142,6 +170,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logAborted");
+ try {
+ doLogAborted(transitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogAborted(int transitionId) {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
@@ -157,6 +194,15 @@
}
private void onFlush() {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "onFlush");
+ try {
+ doOnFlush();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doOnFlush() {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
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 a8b39c41..891eea0 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
@@ -888,7 +888,10 @@
private DesktopModeWindowDecoration getFocusedDecor() {
final int size = mWindowDecorByTaskId.size();
DesktopModeWindowDecoration focusedDecor = null;
- for (int i = 0; i < size; i++) {
+ // TODO(b/323251951): We need to iterate this in reverse to avoid potentially getting
+ // a decor for a closed task. This is a short term fix while the core issue is addressed,
+ // which involves refactoring the window decor lifecycle to be visibility based.
+ for (int i = size - 1; i >= 0; i--) {
final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
if (decor != null && decor.isFocused()) {
focusedDecor = decor;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index 75965d6..1668e37 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -143,7 +143,7 @@
bubbleController,
mainExecutor
)
- bubbleBarLayerView = BubbleBarLayerView(context, bubbleController)
+ bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData)
}
@Test
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
index 461dafb..ec1edb8 100644
--- a/location/java/android/location/altitude/AltitudeConverter.java
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -219,6 +219,27 @@
* called on the main thread as data will not be loaded from raw assets. Returns true if a Mean
* Sea Level altitude is added to the {@code location}; otherwise, returns false and leaves the
* {@code location} unchanged.
+ *
+ * <p>Prior calls to {@link #addMslAltitudeToLocation(Context, Location)} off the main thread
+ * are necessary to load data from raw assets. Example code on the main thread is as follows:
+ *
+ * <pre>{@code
+ * if (!mAltitudeConverter.addMslAltitudeToLocation(location)) {
+ * // Queue up only one call off the main thread.
+ * if (mIsAltitudeConverterIdle) {
+ * mIsAltitudeConverterIdle = false;
+ * executeOffMainThread(() -> {
+ * try {
+ * // Load raw assets for next call attempt on main thread.
+ * mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
+ * } catch (IOException e) {
+ * Log.e(TAG, "Not loading raw assets: " + e);
+ * }
+ * mIsAltitudeConverterIdle = true;
+ * });
+ * }
+ * }
+ * }</pre>
*/
@FlaggedApi(Flags.FLAG_GEOID_HEIGHTS_VIA_ALTITUDE_HAL)
public boolean addMslAltitudeToLocation(@NonNull Location location) {
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 018eaf6..f110705 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -12,4 +12,11 @@
namespace: "media_tv"
description: "Enable the TV client-side AD framework."
bug: "303506816"
+}
+
+flag {
+ name: "tiaf_v_apis"
+ namespace: "media_tv"
+ description: "TIAF V3.0 APIs for Android V"
+ bug: "303323657"
}
\ No newline at end of file
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index f4641b9..f425f52 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -272,7 +272,7 @@
<!-- The title of a dialog which asks the user to restore (i.e. re-install, re-download) an app
after parts of the app have been previously moved into the cloud for temporary storage.
"installername" is the app that will facilitate the download of the app. [CHAR LIMIT=50] -->
- <string name="unarchive_application_title">Restore <xliff:g id="appname" example="Bird Game">%1$s</xliff:g> from <xliff:g id="installername" example="App Store">%1$s</xliff:g>?</string>
+ <string name="unarchive_application_title">Restore <xliff:g id="appname" example="Bird Game">%1$s</xliff:g> from <xliff:g id="installername" example="App Store">%2$s</xliff:g>?</string>
<!-- After the user confirms the dialog, a download will start. [CHAR LIMIT=none] -->
<string name="unarchive_body_text">This app will begin to download in the background</string>
<!-- The action to restore (i.e. re-install, re-download) an app after parts of the app have been previously moved
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
index 9af799c..b20117d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
@@ -25,10 +25,12 @@
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.IntentSender;
+import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Process;
+import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -97,14 +99,30 @@
String appTitle = pm.getApplicationInfo(mPackageName,
PackageManager.ApplicationInfoFlags.of(
MATCH_ARCHIVED_PACKAGES)).loadLabel(pm).toString();
- // TODO(ag/25387215) Get the real installer title here after fixing getInstallSource for
- // archived apps.
- showDialogFragment(appTitle, "installerTitle");
+ String installerTitle = getResponsibleInstallerTitle(pm,
+ pm.getInstallSourceInfo(mPackageName));
+ showDialogFragment(appTitle, installerTitle);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Invalid packageName: " + e.getMessage());
}
}
+ private String getResponsibleInstallerTitle(PackageManager pm,
+ InstallSourceInfo installSource)
+ throws PackageManager.NameNotFoundException {
+ String packageName = TextUtils.isEmpty(installSource.getUpdateOwnerPackageName())
+ ? installSource.getInstallingPackageName()
+ : installSource.getUpdateOwnerPackageName();
+ if (packageName == null) {
+ // Should be unreachable.
+ Log.e(TAG, "Installer not found.");
+ setResult(Activity.RESULT_FIRST_USER);
+ finish();
+ return "";
+ }
+ return pm.getApplicationInfo(packageName, /* flags= */ 0).loadLabel(pm).toString();
+ }
+
@NonNull
private String[] getRequestedPermissions(String callingPackage) {
String[] requestedPermissions = null;
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a4b3af9..2e64212 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1340,11 +1340,11 @@
<string name="notice_header" translatable="false"></string>
<!-- Name of the phone device. [CHAR LIMIT=30] -->
- <string name="media_transfer_this_device_name" product="default">This phone</string>
+ <string name="media_transfer_this_device_name">This phone</string>
<!-- Name of the tablet device. [CHAR LIMIT=30] -->
- <string name="media_transfer_this_device_name" product="tablet">This tablet</string>
+ <string name="media_transfer_this_device_name_tablet">This tablet</string>
<!-- Name of the default media output of the TV. [CHAR LIMIT=30] -->
- <string name="media_transfer_this_device_name" product="tv">@string/tv_media_transfer_default</string>
+ <string name="media_transfer_this_device_name_tv">@string/tv_media_transfer_default</string>
<!-- Name of the dock device. [CHAR LIMIT=30] -->
<string name="media_transfer_dock_speaker_device_name">Dock speaker</string>
<!-- Default name of the external device. [CHAR LIMIT=30] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 15f33d2..ba9180d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -37,6 +37,7 @@
import android.hardware.hdmi.HdmiPortInfo;
import android.media.MediaRoute2Info;
import android.media.RouteListingPreference;
+import android.os.SystemProperties;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -45,6 +46,7 @@
import com.android.settingslib.R;
import com.android.settingslib.media.flags.Flags;
+import java.util.Arrays;
import java.util.List;
/**
@@ -63,6 +65,17 @@
private final DeviceIconUtil mDeviceIconUtil;
+ /** Returns this device name for media transfer. */
+ public static @NonNull String getMediaTransferThisDeviceName(@NonNull Context context) {
+ if (isTv(context)) {
+ return context.getString(R.string.media_transfer_this_device_name_tv);
+ } else if (isTablet()) {
+ return context.getString(R.string.media_transfer_this_device_name_tablet);
+ } else {
+ return context.getString(R.string.media_transfer_this_device_name);
+ }
+ }
+
/** Returns the device name for the given {@code routeInfo}. */
public static String getSystemRouteNameFromType(
@NonNull Context context, @NonNull MediaRoute2Info routeInfo) {
@@ -80,7 +93,7 @@
name = context.getString(R.string.media_transfer_dock_speaker_device_name);
break;
case TYPE_BUILTIN_SPEAKER:
- name = context.getString(R.string.media_transfer_this_device_name);
+ name = getMediaTransferThisDeviceName(context);
break;
case TYPE_HDMI:
name = context.getString(isTv ? R.string.tv_media_transfer_default :
@@ -135,6 +148,11 @@
&& Flags.enableTvMediaOutputDialog();
}
+ static boolean isTablet() {
+ return Arrays.asList(SystemProperties.get("ro.build.characteristics").split(","))
+ .contains("tablet");
+ }
+
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 3355fb3..6761aa7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -16,29 +16,71 @@
package com.android.settingslib.volume.data.repository
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.media.AudioDeviceInfo
import android.media.AudioManager
+import android.media.AudioManager.OnCommunicationDeviceChangedListener
+import androidx.concurrent.futures.DirectExecutor
import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
-/** Provides audio managing functionality and data. */
+/** Provides audio streams state and managing functionality. */
interface AudioRepository {
/** Current [AudioManager.getMode]. */
val mode: StateFlow<Int>
+
+ /**
+ * Ringtone mode.
+ *
+ * @see AudioManager.getRingerModeInternal
+ */
+ val ringerMode: StateFlow<RingerMode>
+
+ /**
+ * Communication device. Emits null when there is no communication device available.
+ *
+ * @see AudioDeviceInfo.getType
+ */
+ val communicationDevice: StateFlow<AudioDeviceInfo?>
+
+ /** State of the [AudioStream]. */
+ suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel>
+
+ /** Current state of the [AudioStream]. */
+ suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel
+
+ suspend fun setVolume(audioStream: AudioStream, volume: Int)
+
+ suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean)
}
class AudioRepositoryImpl(
+ private val context: Context,
private val audioManager: AudioManager,
- backgroundCoroutineContext: CoroutineContext,
- coroutineScope: CoroutineScope,
+ private val backgroundCoroutineContext: CoroutineContext,
+ private val coroutineScope: CoroutineScope,
) : AudioRepository {
override val mode: StateFlow<Int> =
@@ -50,4 +92,117 @@
}
.flowOn(backgroundCoroutineContext)
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
+
+ private val audioManagerIntents: SharedFlow<String> =
+ callbackFlow {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent) {
+ intent.action?.let { action -> launch { send(action) } }
+ }
+ }
+ context.registerReceiver(
+ receiver,
+ IntentFilter().apply {
+ for (action in allActions) {
+ addAction(action)
+ }
+ }
+ )
+
+ awaitClose { context.unregisterReceiver(receiver) }
+ }
+ .shareIn(coroutineScope, SharingStarted.WhileSubscribed())
+
+ override val ringerMode: StateFlow<RingerMode> =
+ audioManagerIntents
+ .filter { ringerActions.contains(it) }
+ .map { RingerMode(audioManager.ringerModeInternal) }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ RingerMode(audioManager.ringerModeInternal),
+ )
+
+ override val communicationDevice: StateFlow<AudioDeviceInfo?>
+ get() =
+ callbackFlow {
+ val listener = OnCommunicationDeviceChangedListener { trySend(Unit) }
+ audioManager.addOnCommunicationDeviceChangedListener(
+ DirectExecutor.INSTANCE,
+ listener
+ )
+
+ awaitClose { audioManager.removeOnCommunicationDeviceChangedListener(listener) }
+ }
+ .filterNotNull()
+ .map { audioManager.communicationDevice }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ audioManager.communicationDevice,
+ )
+
+ override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
+ return audioManagerIntents
+ .filter { modelActions.contains(it) }
+ .map { getCurrentAudioStream(audioStream) }
+ .flowOn(backgroundCoroutineContext)
+ }
+
+ override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel {
+ return withContext(backgroundCoroutineContext) {
+ AudioStreamModel(
+ audioStream = audioStream,
+ minVolume = getMinVolume(audioStream),
+ maxVolume = audioManager.getStreamMaxVolume(audioStream.value),
+ volume = audioManager.getStreamVolume(audioStream.value),
+ isAffectedByRingerMode =
+ audioManager.isStreamAffectedByRingerMode(audioStream.value),
+ isMuted = audioManager.isStreamMute(audioStream.value)
+ )
+ }
+ }
+
+ override suspend fun setVolume(audioStream: AudioStream, volume: Int) =
+ withContext(backgroundCoroutineContext) {
+ audioManager.setStreamVolume(audioStream.value, volume, 0)
+ }
+
+ override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
+ withContext(backgroundCoroutineContext) {
+ if (isMuted) {
+ audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_MUTE)
+ } else {
+ audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_UNMUTE)
+ }
+ }
+
+ private fun getMinVolume(stream: AudioStream): Int =
+ try {
+ audioManager.getStreamMinVolume(stream.value)
+ } catch (e: IllegalArgumentException) {
+ // Fallback to STREAM_VOICE_CALL because
+ // CallVolumePreferenceController.java default
+ // return STREAM_VOICE_CALL in getAudioStream
+ audioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL)
+ }
+
+ private companion object {
+ val modelActions =
+ setOf(
+ AudioManager.STREAM_MUTE_CHANGED_ACTION,
+ AudioManager.MASTER_MUTE_CHANGED_ACTION,
+ AudioManager.VOLUME_CHANGED_ACTION,
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+ AudioManager.STREAM_DEVICES_CHANGED_ACTION,
+ )
+ val ringerActions =
+ setOf(
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+ )
+ val allActions = ringerActions + modelActions
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt
new file mode 100644
index 0000000..2b12936
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.settingslib.volume.data.repository
+
+import android.content.Context
+import android.media.AudioSystem
+
+/** Provides the current state of the audio system. */
+interface AudioSystemRepository {
+
+ val isSingleVolume: Boolean
+}
+
+class AudioSystemRepositoryImpl(private val context: Context) : AudioSystemRepository {
+
+ override val isSingleVolume: Boolean
+ get() = AudioSystem.isSingleVolume(context)
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
new file mode 100644
index 0000000..58f3c2d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.settingslib.volume.shared.model
+
+import android.media.AudioManager
+
+/** Type-safe wrapper for [AudioManager] audio stream. */
+@JvmInline
+value class AudioStream(val value: Int) {
+ init {
+ require(value in supportedStreamTypes) { "Unsupported stream=$value" }
+ }
+
+ private companion object {
+ val supportedStreamTypes =
+ setOf(
+ AudioManager.STREAM_VOICE_CALL,
+ AudioManager.STREAM_SYSTEM,
+ AudioManager.STREAM_RING,
+ AudioManager.STREAM_MUSIC,
+ AudioManager.STREAM_ALARM,
+ AudioManager.STREAM_NOTIFICATION,
+ AudioManager.STREAM_BLUETOOTH_SCO,
+ AudioManager.STREAM_SYSTEM_ENFORCED,
+ AudioManager.STREAM_DTMF,
+ AudioManager.STREAM_TTS,
+ AudioManager.STREAM_ACCESSIBILITY,
+ AudioManager.STREAM_ASSISTANT,
+ )
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt
new file mode 100644
index 0000000..c1be1ee
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.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.settingslib.volume.shared.model
+
+/** Current state of the audio stream. */
+data class AudioStreamModel(
+ val audioStream: AudioStream,
+ val volume: Int,
+ val minVolume: Int,
+ val maxVolume: Int,
+ val isAffectedByRingerMode: Boolean,
+ val isMuted: Boolean,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt
new file mode 100644
index 0000000..9f03927
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.settingslib.volume.shared.model
+
+import android.media.AudioManager
+
+/** Type-safe wrapper for [AudioManager] ringer mode. */
+@JvmInline
+value class RingerMode(val value: Int) {
+
+ init {
+ require(value in supportedRingerModes) { "Unsupported stream=$value" }
+ }
+
+ private companion object {
+ val supportedRingerModes =
+ setOf(
+ AudioManager.RINGER_MODE_SILENT,
+ AudioManager.RINGER_MODE_VIBRATE,
+ AudioManager.RINGER_MODE_NORMAL,
+ AudioManager.RINGER_MODE_MAX,
+ )
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index ce3a7ba..f303ab5 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -40,6 +40,8 @@
"android.test.runner",
"telephony-common",
"android.test.base",
+ "android.test.mock",
+ "truth",
],
platform_apis: true,
@@ -49,16 +51,23 @@
"androidx.test.core",
"androidx.test.rules",
"androidx.test.espresso.core",
+ "androidx.test.ext.junit",
"flag-junit",
- "mockito-target-minus-junit4",
+ "kotlinx_coroutines_test",
+ "mockito-target-extended-minus-junit4",
"platform-test-annotations",
"truth",
"SettingsLibDeviceStateRotationLock",
"SettingsLibSettingsSpinner",
"SettingsLibUsageProgressBarPreference",
"settingslib_media_flags_lib",
- "kotlinx_coroutines_test",
],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libmultiplejvmtiagentsinterferenceagent",
+ "libstaticjvmtiagent",
+ ],
dxflags: ["--multi-dex"],
+ manifest: "AndroidManifest.xml",
}
diff --git a/packages/SettingsLib/tests/integ/AndroidManifest.xml b/packages/SettingsLib/tests/integ/AndroidManifest.xml
index 32048ca..9fb1c1f 100644
--- a/packages/SettingsLib/tests/integ/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidManifest.xml
@@ -25,7 +25,7 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- <application>
+ <application android:debuggable="true" android:testOnly="true">
<uses-library android:name="android.test.runner" />
<activity android:name=".drawer.SettingsDrawerActivityTest$TestActivity"/>
diff --git a/packages/SettingsLib/tests/integ/AndroidTest.xml b/packages/SettingsLib/tests/integ/AndroidTest.xml
index d0aee88..9de6019 100644
--- a/packages/SettingsLib/tests/integ/AndroidTest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidTest.xml
@@ -16,6 +16,7 @@
<configuration description="Runs Tests for SettingsLib.">
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
<option name="test-file-name" value="SettingsLibTests.apk" />
+ <option name="install-arg" value="-t" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
new file mode 100644
index 0000000..7b70c64
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -0,0 +1,281 @@
+/*
+ * 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.settingslib.volume.data.repository
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+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.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@Suppress("UnspecifiedRegisterReceiverFlag")
+@RunWith(AndroidJUnit4::class)
+class AudioRepositoryTest {
+
+ @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+ @Captor
+ private lateinit var modeListenerCaptor: ArgumentCaptor<AudioManager.OnModeChangedListener>
+ @Captor
+ private lateinit var communicationDeviceListenerCaptor:
+ ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener>
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var audioManager: AudioManager
+ @Mock private lateinit var communicationDevice: AudioDeviceInfo
+
+ private val volumeByStream: MutableMap<Int, Int> = mutableMapOf()
+ private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf()
+ private val isMuteByStream: MutableMap<Int, Boolean> = mutableMapOf()
+ private val testScope = TestScope()
+
+ private lateinit var underTest: AudioRepository
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(audioManager.mode).thenReturn(AudioManager.MODE_RINGTONE)
+ `when`(audioManager.communicationDevice).thenReturn(communicationDevice)
+ `when`(audioManager.getStreamMinVolume(anyInt())).thenReturn(MIN_VOLUME)
+ `when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
+ `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+ `when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
+ volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
+ triggerIntent(AudioManager.ACTION_VOLUME_CHANGED)
+ }
+ `when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
+ isMuteByStream[it.arguments[0] as Int] = it.arguments[2] == AudioManager.ADJUST_MUTE
+ triggerIntent(AudioManager.STREAM_MUTE_CHANGED_ACTION)
+ }
+ `when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
+ volumeByStream.getOrDefault(it.arguments[0] as Int, 0)
+ }
+ `when`(audioManager.isStreamAffectedByRingerMode(anyInt())).thenAnswer {
+ isAffectedByRingerModeByStream.getOrDefault(it.arguments[0] as Int, false)
+ }
+ `when`(audioManager.isStreamMute(anyInt())).thenAnswer {
+ isMuteByStream.getOrDefault(it.arguments[0] as Int, false)
+ }
+
+ underTest =
+ AudioRepositoryImpl(
+ context,
+ audioManager,
+ testScope.testScheduler,
+ testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun audioModeChanges_repositoryEmits() {
+ testScope.runTest {
+ val modes = mutableListOf<Int>()
+ underTest.mode.onEach { modes.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ triggerModeChange(AudioManager.MODE_IN_CALL)
+ runCurrent()
+
+ assertThat(modes).containsExactly(AudioManager.MODE_RINGTONE, AudioManager.MODE_IN_CALL)
+ }
+ }
+
+ @Test
+ fun ringerModeChanges_repositoryEmits() {
+ testScope.runTest {
+ val modes = mutableListOf<RingerMode>()
+ underTest.ringerMode.onEach { modes.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
+ triggerIntent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)
+ runCurrent()
+
+ assertThat(modes)
+ .containsExactly(
+ RingerMode(AudioManager.RINGER_MODE_NORMAL),
+ RingerMode(AudioManager.RINGER_MODE_SILENT),
+ )
+ }
+ }
+
+ @Test
+ fun communicationDeviceChanges_repositoryEmits() {
+ testScope.runTest {
+ var device: AudioDeviceInfo? = null
+ underTest.communicationDevice.onEach { device = it }.launchIn(backgroundScope)
+ runCurrent()
+
+ triggerConnectedDeviceChange(communicationDevice)
+ runCurrent()
+
+ assertThat(device).isSameInstanceAs(communicationDevice)
+ }
+ }
+
+ @Test
+ fun adjustingVolume_changesTheStream() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setVolume(audioStream, 50)
+ runCurrent()
+
+ assertThat(streamModel)
+ .isEqualTo(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 50,
+ minVolume = MIN_VOLUME,
+ maxVolume = MAX_VOLUME,
+ isAffectedByRingerMode = false,
+ isMuted = false,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun adjustingVolume_currentModeIsUpToDate() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setVolume(audioStream, 50)
+ runCurrent()
+
+ assertThat(underTest.getCurrentAudioStream(audioStream)).isEqualTo(streamModel)
+ }
+ }
+
+ @Test
+ fun muteStream_mutesTheStream() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setMuted(audioStream, true)
+ runCurrent()
+
+ assertThat(streamModel)
+ .isEqualTo(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 0,
+ minVolume = MIN_VOLUME,
+ maxVolume = MAX_VOLUME,
+ isAffectedByRingerMode = false,
+ isMuted = true,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun unmuteStream_unmutesTheStream() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ isMuteByStream[audioStream.value] = true
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setMuted(audioStream, false)
+ runCurrent()
+
+ assertThat(streamModel)
+ .isEqualTo(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 0,
+ minVolume = MIN_VOLUME,
+ maxVolume = MAX_VOLUME,
+ isAffectedByRingerMode = false,
+ isMuted = false,
+ )
+ )
+ }
+ }
+
+ private fun triggerConnectedDeviceChange(communicationDevice: AudioDeviceInfo?) {
+ verify(audioManager)
+ .addOnCommunicationDeviceChangedListener(
+ any(),
+ communicationDeviceListenerCaptor.capture(),
+ )
+ communicationDeviceListenerCaptor.value.onCommunicationDeviceChanged(communicationDevice)
+ }
+
+ private fun triggerModeChange(mode: Int) {
+ verify(audioManager).addOnModeChangedListener(any(), modeListenerCaptor.capture())
+ modeListenerCaptor.value.onModeChanged(mode)
+ }
+
+ private fun triggerIntent(action: String) {
+ verify(context).registerReceiver(receiverCaptor.capture(), any())
+ receiverCaptor.value.onReceive(context, Intent(action))
+ }
+
+ private companion object {
+ const val MIN_VOLUME = 0
+ const val MAX_VOLUME = 100
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
index 686362f..dddf8e82 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
@@ -16,9 +16,15 @@
package com.android.settingslib.volume.data.repository
+import android.media.AudioDeviceInfo
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
class FakeAudioRepository : AudioRepository {
@@ -26,7 +32,59 @@
override val mode: StateFlow<Int>
get() = mutableMode.asStateFlow()
+ private val mutableRingerMode = MutableStateFlow(RingerMode(0))
+ override val ringerMode: StateFlow<RingerMode>
+ get() = mutableRingerMode.asStateFlow()
+
+ private val mutableCommunicationDevice = MutableStateFlow<AudioDeviceInfo?>(null)
+ override val communicationDevice: StateFlow<AudioDeviceInfo?>
+ get() = mutableCommunicationDevice.asStateFlow()
+
+ private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf()
+
+ private fun getAudioStreamModelState(
+ audioStream: AudioStream
+ ): MutableStateFlow<AudioStreamModel> =
+ models.getOrPut(audioStream) {
+ MutableStateFlow(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 0,
+ minVolume = 0,
+ maxVolume = 0,
+ isAffectedByRingerMode = false,
+ isMuted = false,
+ )
+ )
+ }
+
+ override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> =
+ getAudioStreamModelState(audioStream).asStateFlow()
+
+ override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel =
+ getAudioStreamModelState(audioStream).value
+
+ override suspend fun setVolume(audioStream: AudioStream, volume: Int) {
+ getAudioStreamModelState(audioStream).update { it.copy(volume = volume) }
+ }
+
+ override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
+ getAudioStreamModelState(audioStream).update { it.copy(isMuted = isMuted) }
+ }
+
fun setMode(newMode: Int) {
mutableMode.value = newMode
}
+
+ fun setRingerMode(newRingerMode: RingerMode) {
+ mutableRingerMode.value = newRingerMode
+ }
+
+ fun setCommunicationDevice(device: AudioDeviceInfo?) {
+ mutableCommunicationDevice.value = device
+ }
+
+ fun setAudioStreamModel(model: AudioStreamModel) {
+ getAudioStreamModelState(model.audioStream).update { model }
+ }
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
index 3bc1edc..4dbf865 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
@@ -17,8 +17,9 @@
package com.android.settingslib.volume.domain.interactor
import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import com.android.settingslib.BaseTest
import com.android.settingslib.volume.data.repository.FakeAudioRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -33,7 +34,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
-class AudioModeInteractorTest {
+class AudioModeInteractorTest : BaseTest() {
private val testScope = TestScope()
private val fakeAudioRepository = FakeAudioRepository()
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index ceba9be..e2d58d6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -24,6 +24,7 @@
import static com.android.settingslib.media.PhoneMediaDevice.PHONE_ID;
import static com.android.settingslib.media.PhoneMediaDevice.USB_HEADSET_ID;
import static com.android.settingslib.media.PhoneMediaDevice.WIRED_HEADSET_ID;
+import static com.android.settingslib.media.PhoneMediaDevice.getMediaTransferThisDeviceName;
import static com.google.common.truth.Truth.assertThat;
@@ -114,7 +115,7 @@
when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
assertThat(mPhoneMediaDevice.getName())
- .isEqualTo(mContext.getString(R.string.media_transfer_this_device_name));
+ .isEqualTo(getMediaTransferThisDeviceName(mContext));
}
@EnableFlags(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
diff --git a/packages/SettingsLib/tests/unit/Android.bp b/packages/SettingsLib/tests/unit/Android.bp
index 6d6e2ff..e2eda4f 100644
--- a/packages/SettingsLib/tests/unit/Android.bp
+++ b/packages/SettingsLib/tests/unit/Android.bp
@@ -32,5 +32,7 @@
"androidx.test.ext.junit",
"androidx.test.runner",
"truth",
+ "kotlinx_coroutines_test",
+ "mockito-target-minus-junit4",
],
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 95e0e1b..e99fcc9 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -908,6 +908,12 @@
<!-- Permissions required for CTS test - CtsPermissionUiTestCases -->
<uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
+ <!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
+ <uses-permission android:name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
+
+ <!-- Permission required for Cts test ScreenRecordingCallbackTests -->
+ <uses-permission android:name="android.permission.DETECT_SCREEN_RECORDING" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3db99f28..854f6b9 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -323,13 +323,6 @@
}
flag {
- name: "screenshare_notification_hiding"
- namespace: "systemui"
- description: "Enable hiding of notifications during screenshare"
- bug: "312784809"
-}
-
-flag {
name: "run_fingerprint_detect_on_dismissible_keyguard"
namespace: "systemui"
description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
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 576596f..4e72dfe 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
@@ -160,14 +160,23 @@
gridCoordinates
) {
detectLongPressGesture { offset ->
- isButtonToEditWidgetsShowing = true
-
// Deduct both grid offset relative to its container and content offset.
val adjustedOffset =
gridCoordinates?.let {
offset - it.positionInWindow() - contentOffset
}
val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+ // Display the button only when the gesture initiates from widgets,
+ // the CTA tile, or an empty area on the screen. UMO/smartspace have
+ // their own long-press handlers. To prevent user confusion, we should
+ // not display this button.
+ if (
+ index == null ||
+ communalContent[index].isWidget() ||
+ communalContent[index] is CommunalContentModel.CtaTileInViewMode
+ ) {
+ isButtonToEditWidgetsShowing = true
+ }
val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
viewModel.setSelectedKey(key)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 5258078..d904c8b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -504,6 +504,7 @@
layoutImpl.density = density
layoutImpl.swipeSourceDetector = swipeSourceDetector
+ layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
}
layoutImpl.Content(modifier)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index e8cc0ec..2dc94a4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -38,6 +38,8 @@
import com.android.compose.test.runMonotonicClockTest
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,7 +49,7 @@
@RunWith(AndroidJUnit4::class)
class SceneGestureHandlerTest {
private class TestGestureScope(
- val coroutineScope: MonotonicClockTestScope,
+ private val testScope: MonotonicClockTestScope,
) {
private val layoutState =
MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
@@ -92,17 +94,16 @@
swipeSourceDetector = DefaultEdgeDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenesBuilder,
- coroutineScope = coroutineScope,
+ coroutineScope = testScope,
)
.apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }
val sceneGestureHandler = layoutImpl.gestureHandler(Orientation.Vertical)
val horizontalSceneGestureHandler = layoutImpl.gestureHandler(Orientation.Horizontal)
- val draggable = sceneGestureHandler.draggable
fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) =
SceneNestedScrollHandler(
- layoutImpl,
+ layoutImpl = layoutImpl,
orientation = sceneGestureHandler.orientation,
topOrLeftBehavior = nestedScrollBehavior,
bottomOrRightBehavior = nestedScrollBehavior,
@@ -117,8 +118,12 @@
fun up(fractionOfScreen: Float) =
if (fractionOfScreen < 0f) error("use down()") else -down(fractionOfScreen)
- // Offset y: 10% of the screen
- val offsetY10 = Offset(x = 0f, y = down(0.1f))
+ fun downOffset(fractionOfScreen: Float) =
+ if (fractionOfScreen < 0f) {
+ error("upOffset() is required, not implemented yet")
+ } else {
+ Offset(x = 0f, y = down(fractionOfScreen))
+ }
val transitionState: TransitionState
get() = layoutState.transitionState
@@ -127,15 +132,15 @@
get() = (transitionState as Transition).progress
fun advanceUntilIdle() {
- coroutineScope.testScheduler.advanceUntilIdle()
+ testScope.testScheduler.advanceUntilIdle()
}
fun runCurrent() {
- coroutineScope.testScheduler.runCurrent()
+ testScope.testScheduler.runCurrent()
}
fun assertIdle(currentScene: SceneKey) {
- assertWithMessage("transitionState must be Idle").that(transitionState is Idle).isTrue()
+ assertThat(transitionState).isInstanceOf(Idle::class.java)
assertWithMessage("currentScene does not match")
.that(transitionState.currentScene)
.isEqualTo(currentScene)
@@ -148,11 +153,8 @@
progress: Float? = null,
isUserInputOngoing: Boolean? = null
) {
- val transition = transitionState
- assertWithMessage("transitionState must be Transition")
- .that(transition is Transition)
- .isTrue()
- transition as Transition
+ assertThat(transitionState).isInstanceOf(Transition::class.java)
+ val transition = transitionState as Transition
if (currentScene != null)
assertWithMessage("currentScene does not match")
@@ -180,47 +182,115 @@
.that(transition.isUserInputOngoing)
.isEqualTo(isUserInputOngoing)
}
+
+ fun onDragStarted(
+ startedPosition: Offset = Offset.Zero,
+ overSlop: Float,
+ pointersDown: Int = 1
+ ) {
+ // overSlop should be 0f only if the drag gesture starts with startDragImmediately
+ if (overSlop == 0f) error("Consider using onDragStartedImmediately()")
+ onDragStarted(sceneGestureHandler.draggable, startedPosition, overSlop, pointersDown)
+ }
+
+ fun onDragStartedImmediately(startedPosition: Offset = Offset.Zero, pointersDown: Int = 1) {
+ onDragStarted(
+ sceneGestureHandler.draggable,
+ startedPosition,
+ overSlop = 0f,
+ pointersDown
+ )
+ }
+
+ fun onDragStarted(
+ draggableHandler: DraggableHandler,
+ startedPosition: Offset = Offset.Zero,
+ overSlop: Float = 0f,
+ pointersDown: Int = 1
+ ) {
+ draggableHandler.onDragStarted(
+ startedPosition = startedPosition,
+ overSlop = overSlop,
+ pointersDown = pointersDown,
+ )
+
+ // MultiPointerDraggable will always call onDelta with the initial overSlop right after
+ onDelta(pixels = overSlop)
+ }
+
+ fun onDelta(pixels: Float) {
+ sceneGestureHandler.draggable.onDelta(pixels = pixels)
+ }
+
+ fun onDragStopped(velocity: Float) {
+ sceneGestureHandler.draggable.onDragStopped(velocity = velocity)
+ runCurrent()
+ }
+
+ fun NestedScrollConnection.scroll(
+ available: Offset,
+ consumedByScroll: Offset = Offset.Zero,
+ ) {
+ val consumedByPreScroll =
+ onPreScroll(
+ available = available,
+ source = NestedScrollSource.Drag,
+ )
+ val consumed = consumedByPreScroll + consumedByScroll
+
+ onPostScroll(
+ consumed = consumed,
+ available = available - consumed,
+ source = NestedScrollSource.Drag
+ )
+ }
+
+ fun NestedScrollConnection.preFling(
+ available: Velocity,
+ coroutineScope: CoroutineScope = testScope,
+ ) {
+ // onPreFling is a suspend function that returns the consumed velocity once it finishes
+ // consuming it. In the current scenario, it returns after completing the animation.
+ // To return immediately, we can initiate a job that allows us to check the status
+ // before the animation starts.
+ coroutineScope.launch { onPreFling(available = available) }
+ runCurrent()
+ }
}
private fun runGestureTest(block: suspend TestGestureScope.() -> Unit) {
- runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() }
- }
+ runMonotonicClockTest {
+ val testGestureScope = TestGestureScope(testScope = this)
- private fun DraggableHandler.onDragStarted(
- overSlop: Float = 0f,
- startedPosition: Offset = Offset.Zero,
- ) {
- onDragStarted(startedPosition, overSlop)
- // MultiPointerDraggable will always call onDelta with the initial overSlop right after
- onDelta(overSlop)
+ // run the test
+ testGestureScope.block()
+ }
}
@Test fun testPreconditions() = runGestureTest { assertIdle(currentScene = SceneA) }
@Test
fun onDragStarted_shouldStartATransition() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
}
@Test
fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
- draggable.onDelta(pixels = down(0.1f))
+ onDelta(pixels = down(fractionOfScreen = 0.1f))
assertThat(progress).isEqualTo(0.2f)
}
@Test
fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(
- velocity = velocityThreshold - 0.01f,
- )
+ onDragStopped(velocity = velocityThreshold - 0.01f)
assertTransition(currentScene = SceneA)
// wait for the stop animation
@@ -230,11 +300,10 @@
@Test
fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(velocity = velocityThreshold)
-
+ onDragStopped(velocity = velocityThreshold)
assertTransition(currentScene = SceneC)
// wait for the stop animation
@@ -244,10 +313,10 @@
@Test
fun onDragStoppedAfterStarted_returnToIdle() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(velocity = 0f)
+ onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(currentScene = SceneA)
}
@@ -255,7 +324,7 @@
@Test
fun onDragReversedDirection_changeToScene() = runGestureTest {
// Drag A -> B with progress 0.6
- draggable.onDragStarted(overSlop = -60f)
+ onDragStarted(overSlop = -60f)
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -264,7 +333,7 @@
)
// Reverse direction such that A -> C now with 0.4
- draggable.onDelta(100f)
+ onDelta(pixels = 100f)
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -273,7 +342,7 @@
)
// After the drag stopped scene C should be committed
- draggable.onDragStopped(velocity = velocityThreshold)
+ onDragStopped(velocity = velocityThreshold)
assertTransition(currentScene = SceneC, fromScene = SceneA, toScene = SceneC)
// wait for the stop animation
@@ -283,9 +352,12 @@
@Test
fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
- horizontalSceneGestureHandler.draggable.onDragStarted(up(0.3f))
+ val horizontalDraggableHandler = horizontalSceneGestureHandler.draggable
+
+ onDragStarted(horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f))
assertIdle(currentScene = SceneA)
- horizontalSceneGestureHandler.draggable.onDragStarted(down(0.3f))
+
+ onDragStarted(horizontalDraggableHandler, overSlop = down(fractionOfScreen = 0.3f))
assertIdle(currentScene = SceneA)
}
@@ -294,7 +366,7 @@
navigateToSceneC()
// We are on SceneC which has no action in Down direction
- draggable.onDragStarted(10f)
+ onDragStarted(overSlop = 10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -303,7 +375,7 @@
)
// Reverse drag direction, it will consume the previous drag
- draggable.onDelta(-10f)
+ onDelta(pixels = -10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -312,7 +384,7 @@
)
// Continue reverse drag direction, it should record progress to Scene B
- draggable.onDelta(-10f)
+ onDelta(pixels = -10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -326,7 +398,10 @@
navigateToSceneC()
// Start dragging from the bottom
- draggable.onDragStarted(up(0.1f), Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE))
+ onDragStarted(
+ startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE),
+ overSlop = up(fractionOfScreen = 0.1f)
+ )
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -337,14 +412,14 @@
@Test
fun onDragToExactlyZero_toSceneIsSet() = runGestureTest {
- draggable.onDragStarted(down(0.3f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.3f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
toScene = SceneC,
progress = 0.3f
)
- draggable.onDelta(up(0.3f))
+ onDelta(pixels = up(fractionOfScreen = 0.3f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -355,8 +430,8 @@
private fun TestGestureScope.navigateToSceneC() {
assertIdle(currentScene = SceneA)
- draggable.onDragStarted(down(1f))
- draggable.onDragStopped(0f)
+ onDragStarted(overSlop = down(fractionOfScreen = 1f))
+ onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(currentScene = SceneC)
}
@@ -364,7 +439,7 @@
@Test
fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
// Drag A -> B with progress 0.2
- draggable.onDragStarted(overSlop = up(0.2f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -373,13 +448,13 @@
)
// Start animation A -> B with progress 0.2 -> 1.0
- draggable.onDragStopped(velocity = -velocityThreshold)
+ onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
// While at A -> B do a 100% screen drag (progress 1.2). This should go past B and change
// the transition to B -> C with progress 0.2
- draggable.onDragStarted()
- draggable.onDelta(up(1f))
+ onDragStartedImmediately()
+ onDelta(pixels = up(fractionOfScreen = 1f))
assertTransition(
currentScene = SceneB,
fromScene = SceneB,
@@ -388,7 +463,7 @@
)
// After the drag stopped scene C should be committed
- draggable.onDragStopped(velocity = -velocityThreshold)
+ onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneC, fromScene = SceneB, toScene = SceneC)
// wait for the stop animation
@@ -398,9 +473,9 @@
@Test
fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
- draggable.onDragStarted(overSlop = up(0.2f))
- draggable.onDelta(up(0.2f))
- draggable.onDragStopped(velocity = -velocityThreshold)
+ onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
+ onDelta(pixels = up(fractionOfScreen = 0.2f))
+ onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
mutableUserActionsA.remove(Swipe.Up)
@@ -409,83 +484,79 @@
mutableUserActionsB.remove(Swipe.Down)
// start accelaratedScroll and scroll over to B -> null
- draggable.onDragStarted()
- draggable.onDelta(up(0.5f))
- draggable.onDelta(up(0.5f))
+ onDragStartedImmediately()
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
// here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
// still be called. Make sure that they don't crash or change the scene
- draggable.onDelta(up(0.5f))
- draggable.onDragStopped(0f)
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
+ onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(SceneB)
// These events can still come in after the animation has settled
- draggable.onDelta(up(0.5f))
- draggable.onDragStopped(0f)
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
+ onDragStopped(velocity = 0f)
assertIdle(SceneB)
}
@Test
fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest {
- draggable.onDragStarted(up(0.1f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
mutableUserActionsA[Swipe.Up] = SceneC
- draggable.onDelta(up(0.1f))
+ onDelta(pixels = up(fractionOfScreen = 0.1f))
// target stays B even though UserActions changed
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
- draggable.onDragStopped(down(0.1f))
+ onDragStopped(velocity = down(fractionOfScreen = 0.1f))
advanceUntilIdle()
// now target changed to C for new drag
- draggable.onDragStarted(up(0.1f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.1f)
}
@Test
fun onDragTargetsChanged_targetsChangeWhenStartingNewDrag() = runGestureTest {
- draggable.onDragStarted(up(0.1f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
mutableUserActionsA[Swipe.Up] = SceneC
- draggable.onDelta(up(0.1f))
- draggable.onDragStopped(down(0.1f))
+ onDelta(pixels = up(fractionOfScreen = 0.1f))
+ onDragStopped(velocity = down(fractionOfScreen = 0.1f))
// now target changed to C for new drag that started before previous drag settled to Idle
- draggable.onDragStarted(overSlop = 0f)
- draggable.onDelta(up(0.1f))
+ onDragStartedImmediately()
+ onDelta(pixels = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
}
@Test
fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(
- velocity = velocityThreshold,
- )
+ onDragStopped(velocity = velocityThreshold)
- // The stop animation is not started yet
- assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
-
- runCurrent()
-
- assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
- assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
assertTransition(currentScene = SceneC)
+ assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
+ assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
// Start a new gesture while the offset is animating
- draggable.onDragStarted()
+ onDragStartedImmediately()
assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
}
@Test
fun onInitialPreScroll_EdgeWithOverscroll_doNotChangeState() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+ nestedScroll.onPreScroll(
+ available = downOffset(fractionOfScreen = 0.1f),
+ source = NestedScrollSource.Drag
+ )
assertIdle(currentScene = SceneA)
}
@@ -509,40 +580,29 @@
val consumed =
nestedScroll.onPostScroll(
consumed = Offset.Zero,
- available = offsetY10,
+ available = downOffset(fractionOfScreen = 0.1f),
source = NestedScrollSource.Drag
)
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
- assertThat(consumed).isEqualTo(offsetY10)
- }
-
- private fun NestedScrollConnection.scroll(
- available: Offset,
- consumedByScroll: Offset = Offset.Zero,
- ) {
- val consumedByPreScroll =
- onPreScroll(available = available, source = NestedScrollSource.Drag)
- val consumed = consumedByPreScroll + consumedByScroll
- onPostScroll(
- consumed = consumed,
- available = available - consumed,
- source = NestedScrollSource.Drag
- )
+ assertThat(consumed).isEqualTo(downOffset(fractionOfScreen = 0.1f))
}
@Test
fun afterSceneTransitionIsStarted_interceptPreScrollEvents() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
// start intercept preScroll
val consumed =
- nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+ nestedScroll.onPreScroll(
+ available = downOffset(fractionOfScreen = 0.1f),
+ source = NestedScrollSource.Drag
+ )
assertThat(progress).isEqualTo(0.2f)
// do nothing on postScroll
@@ -553,12 +613,12 @@
)
assertThat(progress).isEqualTo(0.2f)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
assertThat(progress).isEqualTo(0.3f)
assertTransition(currentScene = SceneA)
}
- private suspend fun TestGestureScope.preScrollAfterSceneTransition(
+ private fun TestGestureScope.preScrollAfterSceneTransition(
firstScroll: Float,
secondScroll: Float
) {
@@ -567,10 +627,13 @@
nestedScroll.scroll(available = Offset(0f, firstScroll))
// stop scene transition (start the "stop animation")
- nestedScroll.onPreFling(available = Velocity.Zero)
+ nestedScroll.preFling(available = Velocity.Zero)
// a pre scroll event, that could be intercepted by SceneGestureHandler
- nestedScroll.onPreScroll(Offset(0f, secondScroll), NestedScrollSource.Drag)
+ nestedScroll.onPreScroll(
+ available = Offset(0f, secondScroll),
+ source = NestedScrollSource.Drag
+ )
}
@Test
@@ -610,16 +673,17 @@
preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
+ advanceUntilIdle()
assertIdle(SceneB)
}
@Test
fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- nestedScroll.onPreFling(available = Velocity.Zero)
+ nestedScroll.preFling(available = Velocity.Zero)
assertTransition(currentScene = SceneA)
// wait for the stop animation
@@ -627,15 +691,15 @@
assertIdle(currentScene = SceneA)
}
- private suspend fun TestGestureScope.flingAfterScroll(
+ private fun TestGestureScope.flingAfterScroll(
use: NestedScrollBehavior,
idleAfterScroll: Boolean,
) {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = use)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA)
- nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
}
@Test
@@ -679,19 +743,22 @@
}
/** we started the scroll in the scene, then fling with the velocityThreshold */
- private suspend fun TestGestureScope.flingAfterScrollStartedInScene(
+ private fun TestGestureScope.flingAfterScrollStartedInScene(
use: NestedScrollBehavior,
idleAfterScroll: Boolean,
) {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = use)
// scroll consumed in child
- nestedScroll.scroll(available = offsetY10, consumedByScroll = offsetY10)
+ nestedScroll.scroll(
+ available = downOffset(fractionOfScreen = 0.1f),
+ consumedByScroll = downOffset(fractionOfScreen = 0.1f)
+ )
// scroll offsetY10 is all available for parents
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA)
- nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
}
@Test
@@ -732,20 +799,20 @@
@Test
fun beforeDraggableStart_drag_shouldBeIgnored() = runGestureTest {
- draggable.onDelta(down(0.1f))
+ onDelta(pixels = down(fractionOfScreen = 0.1f))
assertIdle(currentScene = SceneA)
}
@Test
fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest {
- draggable.onDragStopped(velocityThreshold)
+ onDragStopped(velocity = velocityThreshold)
assertIdle(currentScene = SceneA)
}
@Test
fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.onPreFling(Velocity(0f, velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
assertIdle(currentScene = SceneA)
}
@@ -753,8 +820,10 @@
fun startNestedScrollWhileDragging() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
+ val offsetY10 = downOffset(fractionOfScreen = 0.1f)
+
// Start a drag and then stop it, given that
- draggable.onDragStarted(overSlop = up(0.1f))
+ onDragStarted(overSlop = up(0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
@@ -764,7 +833,7 @@
assertThat(progress).isEqualTo(0.2f)
// this should be ignored, we are scrolling now!
- draggable.onDragStopped(-velocityThreshold)
+ onDragStopped(-velocityThreshold)
assertTransition(currentScene = SceneA)
nestedScroll.scroll(available = -offsetY10)
@@ -773,7 +842,7 @@
nestedScroll.scroll(available = -offsetY10)
assertThat(progress).isEqualTo(0.4f)
- nestedScroll.onPreFling(available = Velocity(0f, -velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, -velocityThreshold))
assertTransition(currentScene = SceneB)
// wait for the stop animation
@@ -788,7 +857,7 @@
// Swipe up from the middle to transition to scene B.
val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- draggable.onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ onDragStarted(startedPosition = middle, overSlop = up(0.1f))
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -802,7 +871,7 @@
// should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
// should be 0f.
assertThat(sceneGestureHandler.shouldImmediatelyIntercept(middle)).isTrue()
- draggable.onDragStarted(startedPosition = middle, overSlop = 0f)
+ onDragStartedImmediately(startedPosition = middle)
// We should have intercepted the transition, so the transition should be the same object.
assertTransition(currentScene = SceneC, fromScene = SceneC, toScene = SceneB)
@@ -813,7 +882,7 @@
// instead animate from C to A.
val bottom = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)
assertThat(sceneGestureHandler.shouldImmediatelyIntercept(bottom)).isFalse()
- draggable.onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
+ onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
assertTransition(
currentScene = SceneC,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
index fbcd5b2..0245cf2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
@@ -3,7 +3,9 @@
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.TestMonotonicFrameClock
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
@@ -12,16 +14,38 @@
* function.
*
* The [TestCoroutineScheduler] is passed to provide the functionality to wait for idle.
+ *
+ * Note: Please refer to the documentation for [runTest], as this feature utilizes it. This will
+ * provide a comprehensive understanding of all its behaviors.
*/
-@OptIn(ExperimentalTestApi::class)
+@OptIn(ExperimentalTestApi::class, ExperimentalCoroutinesApi::class)
fun runMonotonicClockTest(block: suspend MonotonicClockTestScope.() -> Unit) = runTest {
- // We need a CoroutineScope (like a TestScope) to create a TestMonotonicFrameClock.
- withContext(TestMonotonicFrameClock(this)) {
- MonotonicClockTestScope(coroutineScope = this, testScheduler = testScheduler).block()
+ val testScope: TestScope = this
+
+ withContext(TestMonotonicFrameClock(coroutineScope = testScope)) {
+ val testScopeWithMonotonicFrameClock: CoroutineScope = this
+
+ val scope =
+ MonotonicClockTestScope(
+ testScope = testScopeWithMonotonicFrameClock,
+ testScheduler = testScope.testScheduler,
+ backgroundScope = backgroundScope,
+ )
+
+ // Run the test
+ scope.block()
}
}
+/**
+ * A coroutine scope that for launching test coroutines for Compose.
+ *
+ * @param testScheduler The delay-skipping scheduler used by the test dispatchers running the code
+ * in this scope (see [TestScope.testScheduler]).
+ * @param backgroundScope A scope for background work (see [TestScope.backgroundScope]).
+ */
class MonotonicClockTestScope(
- coroutineScope: CoroutineScope,
- val testScheduler: TestCoroutineScheduler
-) : CoroutineScope by coroutineScope
+ testScope: CoroutineScope,
+ val testScheduler: TestCoroutineScheduler,
+ val backgroundScope: CoroutineScope,
+) : CoroutineScope by testScope
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 4b21105..e39d7ed 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -20,8 +20,10 @@
import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -32,6 +34,12 @@
private val backgroundDispatcher: CoroutineDispatcher,
private val secureSettingsRepository: SecureSettingsRepository,
) {
+ val isNotificationHistoryEnabled: Flow<Boolean> =
+ secureSettingsRepository
+ .intSetting(name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED)
+ .map { it == 1 }
+ .distinctUntilChanged()
+
/** The current state of the notification setting. */
val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
secureSettingsRepository
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
index 9ec6ec8..04e8090 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
@@ -23,6 +23,8 @@
class NotificationSettingsInteractor(
private val repository: NotificationSettingsRepository,
) {
+ val isNotificationHistoryEnabled = repository.isNotificationHistoryEnabled
+
/** Should notifications be visible on the lockscreen? */
val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
repository.isShowNotificationsOnLockScreenEnabled
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
index b6605ed..fa47a02 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
@@ -59,15 +59,21 @@
}
@Test
+ fun isEnabled_settingNotInitialized_returnsFalseByDefault() =
+ scope.runTest {
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+ runCurrent()
+
+ Truth.assertThat(actualValue).isFalse()
+ }
+
+ @Test
fun isEnabled_initiallyGetsSettingsValue() =
scope.runTest {
val actualValue by collectLastValue(underTest.isEnabled(testUser1))
- settings.putIntForUser(
- SETTING_NAME,
- ENABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
Truth.assertThat(actualValue).isTrue()
@@ -78,25 +84,13 @@
scope.runTest {
val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1))
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
- settings.putIntForUser(
- SETTING_NAME,
- ENABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
Truth.assertThat(flowValues.size).isEqualTo(3)
@@ -109,26 +103,14 @@
val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser1.identifier
- )
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser2.identifier
- )
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier)
runCurrent()
Truth.assertThat(lastValueUser1).isFalse()
Truth.assertThat(lastValueUser2).isFalse()
- settings.putIntForUser(
- SETTING_NAME,
- ENABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
Truth.assertThat(lastValueUser1).isTrue()
@@ -142,11 +124,7 @@
runCurrent()
Truth.assertThat(success).isTrue()
- val actualValue =
- settings.getIntForUser(
- SETTING_NAME,
- testUser1.identifier
- )
+ val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
Truth.assertThat(actualValue).isEqualTo(ENABLED)
}
@@ -157,11 +135,7 @@
runCurrent()
Truth.assertThat(success).isTrue()
- val actualValue =
- settings.getIntForUser(
- SETTING_NAME,
- testUser1.identifier
- )
+ val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
Truth.assertThat(actualValue).isEqualTo(DISABLED)
}
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 30eb782..3d8159e 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,6 +24,7 @@
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
@@ -59,6 +60,16 @@
}
@Test
+ fun isEnabled_settingNotInitialized_returnsFalseByDefault() =
+ scope.runTest {
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+ runCurrent()
+
+ Truth.assertThat(actualValue).isFalse()
+ }
+
+ @Test
fun isEnabled_initiallyGetsSettingsValue() =
scope.runTest {
val actualValue by collectLastValue(underTest.isEnabled(testUser1))
@@ -72,8 +83,7 @@
@Test
fun isEnabled_settingUpdated_valueUpdated() =
scope.runTest {
- val flowValues: List<Boolean> by
- collectValues(underTest.isEnabled(testUser1))
+ val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1))
settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 76b0d4a..1642e52 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -20,6 +20,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.util.mockito.KotlinArgumentCaptor
@@ -44,27 +45,32 @@
class CommunalMediaRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var mediaData: MediaData
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
+ private lateinit var underTest: CommunalMediaRepositoryImpl
private val mediaDataListenerCaptor: KotlinArgumentCaptor<MediaDataManager.Listener> by lazy {
KotlinArgumentCaptor(MediaDataManager.Listener::class.java)
}
- private lateinit var mediaRepository: CommunalMediaRepository
-
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+
+ underTest =
+ CommunalMediaRepositoryImpl(
+ mediaDataManager,
+ tableLogBuffer,
+ )
}
@Test
fun hasAnyMediaOrRecommendation_defaultsToFalse() =
testScope.runTest {
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
- val mediaModel = collectLastValue(mediaRepository.mediaModel)
+ val mediaModel = collectLastValue(underTest.mediaModel)
runCurrent()
assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
}
@@ -72,13 +78,11 @@
@Test
fun mediaModel_updatesWhenMediaDataLoaded() =
testScope.runTest {
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
// Listener is added
verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
// Initial value is false.
- val mediaModel = collectLastValue(mediaRepository.mediaModel)
+ val mediaModel = collectLastValue(underTest.mediaModel)
runCurrent()
assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
@@ -96,8 +100,6 @@
@Test
fun mediaModel_updatesWhenMediaDataRemoved() =
testScope.runTest {
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
// Listener is added
verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
@@ -107,7 +109,7 @@
runCurrent()
// Media active now returns true.
- val mediaModel = collectLastValue(mediaRepository.mediaModel)
+ val mediaModel = collectLastValue(underTest.mediaModel)
assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
// Change to media unavailable and notify the listener.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
index d15e15e..6bff0dc7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -25,6 +25,8 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.settings.UserFileManager
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
@@ -36,11 +38,15 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@android.platform.test.annotations.EnabledOnRavenwood
class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
private lateinit var underTest: CommunalPrefsRepositoryImpl
private val kosmos = testKosmos()
@@ -51,6 +57,8 @@
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
userRepository = kosmos.fakeUserRepository
userRepository.setUserInfos(USER_INFOS)
@@ -67,6 +75,8 @@
kosmos.testDispatcher,
userRepository,
userFileManager,
+ logcatLogBuffer("CommunalPrefsRepositoryImplTest"),
+ tableLogBuffer,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
index 0c66bbb..2911a50 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.settings.FakeSettings
@@ -34,12 +35,15 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@android.platform.test.annotations.EnabledOnRavenwood
class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -64,6 +68,7 @@
userRepository,
secureSettings,
logcatLogBuffer("CommunalTutorialRepositoryImplTest"),
+ tableLogBuffer,
)
}
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 c979ca6..475179d 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
@@ -198,13 +198,22 @@
}
@Test
- fun deleteWidget_removeWidgetId_andDeleteFromDb() =
+ fun deleteWidgetFromDb() =
testScope.runTest {
val id = 1
- underTest.deleteWidget(id)
+ underTest.deleteWidgetFromDb(id)
runCurrent()
verify(communalWidgetDao).deleteWidgetById(id)
+ }
+
+ @Test
+ fun deleteWidgetFromHost() =
+ testScope.runTest {
+ val id = 1
+ underTest.deleteWidgetFromHost(id)
+ runCurrent()
+
verify(appWidgetHost).deleteAppWidgetId(id)
}
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 273d1cd..cf727cf 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
@@ -36,6 +36,7 @@
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
@@ -82,6 +83,7 @@
kosmos.communalInteractor,
mediaHost,
uiEventLogger,
+ logcatLogBuffer("CommunalEditModeViewModelTest"),
)
}
@@ -142,6 +144,49 @@
}
@Test
+ fun deleteWidget() =
+ testScope.runTest {
+ tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+ // Widgets available.
+ val widgets =
+ listOf(
+ CommunalWidgetContentModel(
+ appWidgetId = 0,
+ priority = 30,
+ providerInfo = mock(),
+ ),
+ CommunalWidgetContentModel(
+ appWidgetId = 1,
+ priority = 20,
+ providerInfo = mock(),
+ ),
+ )
+ widgetRepository.setCommunalWidgets(widgets)
+
+ val communalContent by collectLastValue(underTest.communalContent)
+
+ // Widgets and CTA tile are shown.
+ assertThat(communalContent?.size).isEqualTo(3)
+ assertThat(communalContent?.get(0))
+ .isInstanceOf(CommunalContentModel.Widget::class.java)
+ assertThat(communalContent?.get(1))
+ .isInstanceOf(CommunalContentModel.Widget::class.java)
+ assertThat(communalContent?.get(2))
+ .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
+
+ underTest.onDeleteWidget(widgets.get(0).appWidgetId)
+
+ // Only one widget and CTA tile remain.
+ assertThat(communalContent?.size).isEqualTo(2)
+ val item = communalContent?.get(0)
+ val appWidgetId = if (item is CommunalContentModel.Widget) item.appWidgetId else null
+ assertThat(appWidgetId).isEqualTo(widgets.get(1).appWidgetId)
+ assertThat(communalContent?.get(1))
+ .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
+ }
+
+ @Test
fun reorderWidget_uiEventLogging_start() {
underTest.onReorderWidgetStart()
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
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 0723e83..73d3091 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
@@ -40,6 +40,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
@@ -99,6 +100,7 @@
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
mediaHost,
+ logcatLogBuffer("CommunalViewModelTest"),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
index efccf7a..e4ce6cb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
@@ -17,7 +17,7 @@
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
-import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
import com.android.systemui.kosmos.Kosmos
@@ -38,4 +38,3 @@
val Kosmos.controlsComponent by Kosmos.Fixture<ControlsComponent> { mock() }
val Kosmos.controlsListingController by Kosmos.Fixture<ControlsListingController> { mock() }
-val Kosmos.authorizedPanelsRepository by Kosmos.Fixture<AuthorizedPanelsRepository> { mock() }
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 ce74a90..6ad32cc 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
@@ -27,13 +27,14 @@
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
+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.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -41,6 +42,9 @@
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.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -48,6 +52,7 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class HomeControlsComponentInteractorTest : SysuiTestCase() {
@@ -59,20 +64,20 @@
private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
private lateinit var underTest: HomeControlsComponentInteractor
private lateinit var userRepository: FakeUserRepository
- private lateinit var selectedComponentRepository: FakeSelectedComponentRepository
+ private lateinit var selectedComponentRepository: SelectedComponentRepository
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- userRepository = kosmos.fakeUserRepository
- userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
controlsComponent = kosmos.controlsComponent
authorizedPanelsRepository = kosmos.authorizedPanelsRepository
controlsListingController = kosmos.controlsListingController
selectedComponentRepository = kosmos.selectedComponentRepository
- selectedComponentRepository.setCurrentUserHandle(PRIMARY_USER.userHandle)
+ userRepository = kosmos.fakeUserRepository
+ userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
+
whenever(controlsComponent.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
@@ -90,14 +95,13 @@
fun testPanelComponentReturnsComponentNameForSelectedItemByUser() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
- userRepository.setSelectedUserInfo(PRIMARY_USER)
+ setActiveUser(PRIMARY_USER)
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
val actualValue by collectLastValue(underTest.panelComponent)
assertThat(actualValue).isNull()
runServicesUpdate()
- assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+ assertThat(actualValue).isEqualTo(TEST_COMPONENT)
}
}
@@ -105,16 +109,15 @@
fun testPanelComponentReturnsComponentNameAsInitialValueWithoutServiceUpdate() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
- userRepository.setSelectedUserInfo(PRIMARY_USER)
+ setActiveUser(PRIMARY_USER)
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
whenever(controlsListingController.getCurrentServices())
.thenReturn(
- listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
)
val actualValue by collectLastValue(underTest.panelComponent)
- assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+ assertThat(actualValue).isEqualTo(TEST_COMPONENT)
}
}
@@ -122,9 +125,8 @@
fun testPanelComponentReturnsNullForHomeControlsThatDoesNotSupportPanel() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
- userRepository.setSelectedUserInfo(PRIMARY_USER)
+ setActiveUser(PRIMARY_USER)
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
val actualValue by collectLastValue(underTest.panelComponent)
assertThat(actualValue).isNull()
@@ -137,8 +139,8 @@
fun testPanelComponentReturnsNullWhenPanelIsUnauthorized() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf())
- userRepository.setSelectedUserInfo(PRIMARY_USER)
+ setActiveUser(PRIMARY_USER)
+ authorizedPanelsRepository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
val actualValue by collectLastValue(underTest.panelComponent)
assertThat(actualValue).isNull()
@@ -151,17 +153,24 @@
fun testPanelComponentReturnsComponentNameForDifferentUsers() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
- userRepository.setSelectedUserInfo(ANOTHER_USER)
+ val actualValue by collectLastValue(underTest.panelComponent)
+
+ // Secondary user has non-panel selected.
+ setActiveUser(ANOTHER_USER)
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
- selectedComponentRepository.setCurrentUserHandle(ANOTHER_USER.userHandle)
+
+ // Primary user has panel selected.
+ setActiveUser(PRIMARY_USER)
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
- val actualValue by collectLastValue(underTest.panelComponent)
- assertThat(actualValue).isNull()
runServicesUpdate()
- assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+ assertThat(actualValue).isEqualTo(TEST_COMPONENT)
+
+ // Back to secondary user, should be null.
+ setActiveUser(ANOTHER_USER)
+ runServicesUpdate()
+ assertThat(actualValue).isNull()
}
}
@@ -169,8 +178,7 @@
fun testPanelComponentReturnsNullWhenControlsComponentReturnsNullForListingController() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
whenever(controlsComponent.getControlsListingController())
.thenReturn(Optional.empty())
userRepository.setSelectedUserInfo(PRIMARY_USER)
@@ -182,11 +190,17 @@
private fun runServicesUpdate(hasPanelBoolean: Boolean = true) {
val listings =
- listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = hasPanelBoolean))
+ listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = hasPanelBoolean))
val callback = withArgCaptor { verify(controlsListingController).addCallback(capture()) }
callback.onServicesUpdated(listings)
}
+ private suspend fun TestScope.setActiveUser(user: UserInfo) {
+ userRepository.setSelectedUserInfo(user)
+ kosmos.fakeUserTracker.set(listOf(user), 0)
+ runCurrent()
+ }
+
private fun ControlsServiceInfo(
componentName: ComponentName,
label: CharSequence,
@@ -237,19 +251,9 @@
)
private const val TEST_PACKAGE = "pkg"
private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service")
- private const val TEST_PACKAGE_PANEL = "pkg.panel"
- private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
private val TEST_SELECTED_COMPONENT_PANEL =
- SelectedComponentRepository.SelectedComponent(
- TEST_PACKAGE_PANEL,
- TEST_COMPONENT_PANEL,
- true
- )
+ SelectedComponentRepository.SelectedComponent(TEST_PACKAGE, TEST_COMPONENT, true)
private val TEST_SELECTED_COMPONENT_NON_PANEL =
- SelectedComponentRepository.SelectedComponent(
- TEST_PACKAGE_PANEL,
- TEST_COMPONENT_PANEL,
- false
- )
+ SelectedComponentRepository.SelectedComponent(TEST_PACKAGE, TEST_COMPONENT, false)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
index 6610e70..87b1bbb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -84,8 +85,7 @@
userRepository.setUserInfos(listOf(PRIMARY_USER))
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE_PANEL))
whenever(controlsComponent.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
index c7e5b64..c23a051 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
@@ -61,7 +61,8 @@
.observerFlow(userHandle.identifier, SETTING_NAME)
.onStart { emit(Unit) }
.map {
- secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+ secureSettings.getIntForUser(SETTING_NAME, DISABLED, userHandle.identifier) ==
+ ENABLED
}
.distinctUntilChanged()
.flowOn(bgCoroutineContext)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
index 419eada..8525797 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
@@ -61,7 +61,8 @@
.observerFlow(userHandle.identifier, SETTING_NAME)
.onStart { emit(Unit) }
.map {
- secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+ secureSettings.getIntForUser(SETTING_NAME, DISABLED, userHandle.identifier) ==
+ ENABLED
}
.distinctUntilChanged()
.flowOn(bgCoroutineContext)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 4176083..4ea5f4c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -198,11 +198,13 @@
UdfpsTouchOverlayBinder.bind(
view = this,
viewModel = deviceEntryUdfpsTouchOverlayViewModel.get(),
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
)
else ->
UdfpsTouchOverlayBinder.bind(
view = this,
viewModel = defaultUdfpsTouchOverlayViewModel.get(),
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
index bb6a68b..2e29c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
@@ -16,9 +16,11 @@
package com.android.systemui.biometrics.ui.binder
+import android.util.Log
import androidx.core.view.isInvisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay
import com.android.systemui.biometrics.ui.viewmodel.UdfpsTouchOverlayViewModel
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
@@ -31,19 +33,26 @@
/**
* Updates visibility for the UdfpsTouchOverlay which controls whether the view will receive
- * touches or not.
+ * touches or not. For some devices, this is instead handled by UdfpsOverlayInteractor, so this
+ * viewBinder will send the information to the interactor.
*/
@JvmStatic
fun bind(
view: UdfpsTouchOverlay,
viewModel: UdfpsTouchOverlayViewModel,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) return
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.shouldHandleTouches.collect { shouldHandleTouches ->
+ Log.d(
+ "UdfpsTouchOverlayBinder",
+ "[$view]: update shouldHandleTouches=$shouldHandleTouches"
+ )
view.isInvisible = !shouldHandleTouches
+ udfpsOverlayInteractor.setHandleTouches(shouldHandleTouches)
}
}
}
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 0f1340a..a39aabf 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
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.ui.viewmodel
import android.content.Context
+import android.content.pm.PackageManager
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
@@ -244,7 +245,13 @@
!customBiometricPrompt() || it == null -> null
it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
- else -> context.packageManager.getApplicationIcon(it.opPackageName)
+ else ->
+ try {
+ context.packageManager.getApplicationIcon(it.opPackageName)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Cannot find icon for package " + it.opPackageName, e)
+ null
+ }
}
}
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 779446d..3b727d2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -97,7 +97,7 @@
fun getWidgets(): Flow<Map<CommunalItemRank, CommunalWidgetItem>>
@Query("SELECT * FROM communal_widget_table WHERE widget_id = :id")
- fun getWidgetByIdNow(id: Int): CommunalWidgetItem
+ fun getWidgetByIdNow(id: Int): CommunalWidgetItem?
@Delete fun deleteWidgets(vararg widgets: CommunalWidgetItem)
@@ -120,7 +120,9 @@
fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
widgetIdToPriorityMap.forEach { (id, priority) ->
val widget = getWidgetByIdNow(id)
- updateItemRank(widget.itemId, priority)
+ if (widget != null) {
+ updateItemRank(widget.itemId, priority)
+ }
}
}
@@ -134,9 +136,13 @@
}
@Transaction
- fun deleteWidgetById(widgetId: Int) {
- val widget = getWidgetByIdNow(widgetId)
+ fun deleteWidgetById(widgetId: Int): Boolean {
+ val widget =
+ getWidgetByIdNow(widgetId) ?: // no entry to delete from db
+ return false
+
deleteItemRankById(widget.itemId)
deleteWidgets(widget)
+ return true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
index cf2e33c..c46f0d1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
@@ -16,15 +16,34 @@
package com.android.systemui.communal.data.model
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
/** Data model of media on the communal hub. */
data class CommunalMediaModel(
val hasAnyMediaOrRecommendation: Boolean,
val createdTimestampMillis: Long = 0L,
-) {
+) : Diffable<CommunalMediaModel> {
companion object {
val INACTIVE =
CommunalMediaModel(
hasAnyMediaOrRecommendation = false,
)
}
+
+ override fun logDiffs(prevVal: CommunalMediaModel, row: TableRowLogger) {
+ if (hasAnyMediaOrRecommendation != prevVal.hasAnyMediaOrRecommendation) {
+ row.logChange(
+ columnName = "isMediaActive",
+ value = hasAnyMediaOrRecommendation,
+ )
+ }
+
+ if (createdTimestampMillis != prevVal.createdTimestampMillis) {
+ row.logChange(
+ columnName = "mediaCreationTimestamp",
+ value = createdTimestampMillis.toString(),
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index e8a561b..2b66491 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -18,6 +18,9 @@
import com.android.systemui.communal.data.model.CommunalMediaModel
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.pipeline.MediaDataManager
import javax.inject.Inject
@@ -34,6 +37,7 @@
@Inject
constructor(
private val mediaDataManager: MediaDataManager,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) : CommunalMediaRepository {
private val mediaDataListener =
@@ -61,7 +65,12 @@
private val _mediaModel: MutableStateFlow<CommunalMediaModel> =
MutableStateFlow(CommunalMediaModel.INACTIVE)
- override val mediaModel: Flow<CommunalMediaModel> = _mediaModel
+ override val mediaModel: Flow<CommunalMediaModel> =
+ _mediaModel.logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ initialValue = CommunalMediaModel.INACTIVE,
+ )
private fun updateMediaModel(data: MediaData? = null) {
if (mediaDataManager.hasAnyMediaOrRecommendation()) {
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 c2ea2e9..0e9b32f 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
@@ -21,9 +21,15 @@
import android.content.pm.UserInfo
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.CommunalLog
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.settings.UserFileManager
-import com.android.systemui.settings.UserFileManagerExt.observeSharedPreferences
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -59,11 +65,21 @@
@Background private val bgDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
private val userFileManager: UserFileManager,
+ @CommunalLog logBuffer: LogBuffer,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) : CommunalPrefsRepository {
+ private val logger = Logger(logBuffer, "CommunalPrefsRepositoryImpl")
+
override val isCtaDismissed: Flow<Boolean> =
userRepository.selectedUserInfo
.flatMapLatest(::observeCtaDismissState)
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isCtaDismissed",
+ initialValue = false,
+ )
.stateIn(
scope = backgroundScope,
started = SharingStarted.WhileSubscribed(),
@@ -76,11 +92,13 @@
.edit()
.putBoolean(CTA_DISMISSED_STATE, true)
.apply()
+
+ logger.i("Dismissed CTA tile")
}
private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
- userFileManager
- .observeSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id)
+ getSharedPrefsForUser(user)
+ .observe(CTA_DISMISSED_STATE)
// 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/CommunalTutorialRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
index 046aaaa..4c06e97 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
@@ -25,6 +25,9 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -66,6 +69,7 @@
private val userRepository: UserRepository,
private val secureSettings: SecureSettings,
@CommunalLog logBuffer: LogBuffer,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) : CommunalTutorialRepository {
companion object {
@@ -94,6 +98,12 @@
settingsState
.map { it.hubModeTutorialState }
.filterNotNull()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "tutorialSettingState",
+ initialValue = HUB_MODE_TUTORIAL_NOT_STARTED,
+ )
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
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 f36547b..2ac9d05 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
@@ -54,8 +54,11 @@
configurator: WidgetConfigurator? = null
) {}
- /** Delete a widget by id from app widget service and the database. */
- fun deleteWidget(widgetId: Int) {}
+ /** Delete a widget by id from the database. */
+ fun deleteWidgetFromDb(widgetId: Int) {}
+
+ /** Delete a widget by id from app widget host. */
+ fun deleteWidgetFromHost(widgetId: Int) {}
/**
* Update the order of widgets in the database.
@@ -143,9 +146,18 @@
}
}
- override fun deleteWidget(widgetId: Int) {
+ override fun deleteWidgetFromDb(widgetId: Int) {
bgScope.launch {
- communalWidgetDao.deleteWidgetById(widgetId)
+ if (communalWidgetDao.deleteWidgetById(widgetId)) {
+ logger.i("Deleted widget with id $widgetId from DB .")
+ } else {
+ logger.w("Widget with id $widgetId cannot be deleted from DB.")
+ }
+ }
+ }
+
+ override fun deleteWidgetFromHost(widgetId: Int) {
+ bgScope.launch {
appWidgetHost.deleteAppWidgetId(widgetId)
logger.i("Deleted widget with id $widgetId.")
}
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 192c9001..75a27a2 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
@@ -33,16 +33,25 @@
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.BooleanFlowOperators.and
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.BooleanFlowOperators.or
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
@@ -50,6 +59,8 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
/** Encapsulates business-logic related to communal mode. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -57,6 +68,7 @@
class CommunalInteractor
@Inject
constructor(
+ @Application applicationScope: CoroutineScope,
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
private val communalPrefsRepository: CommunalPrefsRepository,
@@ -65,8 +77,12 @@
userRepository: UserRepository,
keyguardInteractor: KeyguardInteractor,
private val appWidgetHost: CommunalAppWidgetHost,
- private val editWidgetsActivityStarter: EditWidgetsActivityStarter
+ private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
+ @CommunalLog logBuffer: LogBuffer,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
+ private val logger = Logger(logBuffer, "CommunalInteractor")
+
private val _editModeOpen = MutableStateFlow(false)
/** Whether edit mode is currently open. */
@@ -85,6 +101,22 @@
or(keyguardInteractor.isKeyguardVisible, keyguardInteractor.isDreaming)
)
.distinctUntilChanged()
+ .onEach { available ->
+ logger.i({ "Communal is ${if (bool1) "" else "un"}available" }) {
+ bool1 = available
+ }
+ }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isCommunalAvailable",
+ initialValue = false,
+ )
+ .shareIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through
@@ -142,7 +174,23 @@
*/
// TODO(b/323215860): rename to something more appropriate after cleaning up usages
val isCommunalShowing: Flow<Boolean> =
- communalRepository.desiredScene.map { it == CommunalSceneKey.Communal }
+ communalRepository.desiredScene
+ .map { it == CommunalSceneKey.Communal }
+ .distinctUntilChanged()
+ .onEach { showing ->
+ logger.i({ "Communal is ${if (bool1) "showing" else "gone"}" }) { bool1 = showing }
+ }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isCommunalShowing",
+ initialValue = false,
+ )
+ .shareIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
/**
* Flow that emits a boolean if the communal UI is fully visible and not in transition.
@@ -189,9 +237,15 @@
configurator: WidgetConfigurator?,
) = widgetRepository.addWidget(componentName, priority, configurator)
- /** Delete a widget by id. */
- fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
+ /**
+ * Delete a widget by id from the database. [CommunalAppWidgetHostStartable] invokes this
+ * function to manage the deletion from the database for uninstalled or user-deleted widgets,
+ * following the removal of a widget from the host.
+ */
+ fun deleteWidgetFromDb(id: Int) = widgetRepository.deleteWidgetFromDb(id)
+ /** Delete a widget by id from AppWidgetHost. Called when user deletes a widget from the hub */
+ fun deleteWidgetFromHost(id: Int) = widgetRepository.deleteWidgetFromHost(id)
/**
* Reorder the widgets.
*
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
index 309c84e..1404ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -22,6 +22,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,6 +52,7 @@
keyguardInteractor: KeyguardInteractor,
private val communalRepository: CommunalRepository,
communalInteractor: CommunalInteractor,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
/** An observable for whether the tutorial is available. */
val isTutorialAvailable: StateFlow<Boolean> =
@@ -61,6 +65,12 @@
isKeyguardVisible &&
tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
}
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isTutorialAvailable",
+ initialValue = false,
+ )
.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(),
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 ebcfb8b..69d5581 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
@@ -21,6 +21,9 @@
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.media.dagger.MediaModule
import javax.inject.Inject
@@ -29,6 +32,7 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
/** The view model for communal hub in edit mode. */
@SysUISingleton
@@ -38,21 +42,27 @@
private val communalInteractor: CommunalInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
private val uiEventLogger: UiEventLogger,
+ @CommunalLog logBuffer: LogBuffer,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
+
+ private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
+
override val isEditMode = true
// Only widgets are editable. The CTA tile comes last in the list and remains visible.
override val communalContent: Flow<List<CommunalContentModel>> =
- communalInteractor.widgetContent.map { widgets ->
- widgets + listOf(CommunalContentModel.CtaTileInEditMode())
- }
+ communalInteractor.widgetContent
+ .map { widgets -> widgets + listOf(CommunalContentModel.CtaTileInEditMode()) }
+ .onEach { models ->
+ logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
+ }
private val _reorderingWidgets = MutableStateFlow(false)
override val reorderingWidgets: StateFlow<Boolean>
get() = _reorderingWidgets
- override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
+ override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidgetFromHost(id)
override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index d7a3705..0c12841 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -21,6 +21,9 @@
import com.android.systemui.communal.domain.model.CommunalContentModel
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.Logger
+import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.media.controls.ui.MediaHostState
@@ -37,6 +40,7 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
/** The default view model used for showing the communal hub. */
@@ -48,22 +52,30 @@
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
+ @CommunalLog logBuffer: LogBuffer,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
+
+ private val logger = Logger(logBuffer, "CommunalViewModel")
+
@OptIn(ExperimentalCoroutinesApi::class)
override val communalContent: Flow<List<CommunalContentModel>> =
- tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode ->
- if (isTutorialMode) {
- return@flatMapLatest flowOf(communalInteractor.tutorialContent)
+ tutorialInteractor.isTutorialAvailable
+ .flatMapLatest { isTutorialMode ->
+ if (isTutorialMode) {
+ return@flatMapLatest flowOf(communalInteractor.tutorialContent)
+ }
+ combine(
+ communalInteractor.ongoingContent,
+ communalInteractor.widgetContent,
+ communalInteractor.ctaTileContent,
+ ) { ongoing, widgets, ctaTile,
+ ->
+ ongoing + widgets + ctaTile
+ }
}
- combine(
- communalInteractor.ongoingContent,
- communalInteractor.widgetContent,
- communalInteractor.ctaTileContent,
- ) { ongoing, widgets, ctaTile,
- ->
- ongoing + widgets + ctaTile
+ .onEach { models ->
+ logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
}
- }
private val _isPopupOnDismissCtaShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isPopupOnDismissCtaShowing: Flow<Boolean> =
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 fb9abeb..6fd0fbe 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -50,7 +50,7 @@
.launchIn(bgScope)
appWidgetHost.appWidgetIdToRemove
- .onEach { appWidgetId -> communalInteractor.deleteWidget(appWidgetId) }
+ .onEach { appWidgetId -> communalInteractor.deleteWidgetFromDb(appWidgetId) }
.launchIn(bgScope)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index eb6dc43..92e8153 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -32,6 +32,9 @@
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.res.R
import javax.inject.Inject
@@ -42,7 +45,8 @@
private val communalViewModel: CommunalEditModeViewModel,
private var windowManagerService: IWindowManager? = null,
private val uiEventLogger: UiEventLogger,
- private val widgetConfiguratorFactory: WidgetConfigurationController.Factory
+ private val widgetConfiguratorFactory: WidgetConfigurationController.Factory,
+ @CommunalLog logBuffer: LogBuffer,
) : ComponentActivity() {
companion object {
private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
@@ -54,6 +58,8 @@
const val EXTRA_PRESELECTED_KEY = "preselected_key"
}
+ private val logger = Logger(logBuffer, "EditWidgetsActivity")
+
private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) }
private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
@@ -157,11 +163,15 @@
override fun onStart() {
super.onStart()
+
+ logger.i("Starting the communal widget editor activity")
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN)
}
override fun onStop() {
super.onStop()
+
+ logger.i("Stopping the communal widget editor activity")
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_GONE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
index ae9c37a..b35bec4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
@@ -17,11 +17,16 @@
package com.android.systemui.controls.panels
+import android.os.UserHandle
+import kotlinx.coroutines.flow.Flow
+
/**
* Repository for keeping track of which packages the panel has authorized to show control panels
* (embedded activity).
*/
interface AuthorizedPanelsRepository {
+ /** Exposes the authorized panels as a [Flow] for subscribing to updates */
+ fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>>
/** A set of package names that the user has previously authorized to show panels. */
fun getAuthorizedPanels(): Set<String>
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 4e935df..7c2dae3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -19,11 +19,16 @@
import android.content.Context
import android.content.SharedPreferences
+import android.os.UserHandle
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
class AuthorizedPanelsRepositoryImpl
@Inject
@@ -33,19 +38,24 @@
private val userTracker: UserTracker,
) : AuthorizedPanelsRepository {
+ override fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>> {
+ val prefs = instantiateSharedPrefs(user)
+ return prefs.observe(KEY).onStart { emit(Unit) }.map { getAuthorizedPanelsInternal(prefs) }
+ }
+
override fun getAuthorizedPanels(): Set<String> {
- return getAuthorizedPanelsInternal(instantiateSharedPrefs())
+ return getAuthorizedPanelsInternal(instantiateSharedPrefs(userTracker.userHandle))
}
override fun getPreferredPackages(): Set<String> =
context.resources.getStringArray(R.array.config_controlsPreferredPackages).toSet()
override fun addAuthorizedPanels(packageNames: Set<String>) {
- addAuthorizedPanelsInternal(instantiateSharedPrefs(), packageNames)
+ addAuthorizedPanelsInternal(instantiateSharedPrefs(userTracker.userHandle), packageNames)
}
override fun removeAuthorizedPanels(packageNames: Set<String>) {
- with(instantiateSharedPrefs()) {
+ with(instantiateSharedPrefs(userTracker.userHandle)) {
val currentSet = getAuthorizedPanelsInternal(this)
edit().putStringSet(KEY, currentSet - packageNames).apply()
}
@@ -63,12 +73,12 @@
sharedPreferences.edit().putStringSet(KEY, currentSet + packageNames).apply()
}
- private fun instantiateSharedPrefs(): SharedPreferences {
+ private fun instantiateSharedPrefs(user: UserHandle): SharedPreferences {
val sharedPref =
userFileManager.getSharedPreferences(
DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
Context.MODE_PRIVATE,
- userTracker.userId,
+ user.identifier,
)
// We should add default packages when we've never run this
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 0baa81a..9be04940 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -20,21 +20,18 @@
import android.content.Context
import android.content.SharedPreferences
import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
@SysUISingleton
@@ -43,9 +40,7 @@
constructor(
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
- private val featureFlags: FeatureFlags,
- @Background private val bgDispatcher: CoroutineDispatcher,
- @Application private val applicationScope: CoroutineScope
+ @Background private val bgDispatcher: CoroutineDispatcher
) : SelectedComponentRepository {
private companion object {
@@ -66,22 +61,11 @@
override fun selectedComponentFlow(
userHandle: UserHandle
): Flow<SelectedComponentRepository.SelectedComponent?> {
- return conflatedCallbackFlow {
- val sharedPreferencesByUserId = getSharedPreferencesForUser(userHandle.identifier)
- val listener =
- SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
- applicationScope.launch(bgDispatcher) {
- if (key == PREF_COMPONENT) {
- trySend(getSelectedComponent(userHandle))
- }
- }
- }
- sharedPreferencesByUserId.registerOnSharedPreferenceChangeListener(listener)
- send(getSelectedComponent(userHandle))
- awaitClose {
- sharedPreferencesByUserId.unregisterOnSharedPreferenceChangeListener(listener)
- }
- }
+ val prefs = getSharedPreferencesForUser(userHandle.identifier)
+ return prefs
+ .observe(PREF_COMPONENT)
+ .onStart { emit(Unit) }
+ .map { getSelectedComponent(userHandle) }
.flowOn(bgDispatcher)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index e9d1e94..dd186d6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -42,7 +42,7 @@
import com.android.systemui.rotationlock.RotationLockModule;
import com.android.systemui.scene.SceneContainerFrameworkModule;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
-import com.android.systemui.settings.dagger.MultiUserUtilsModule;
+import com.android.systemui.settings.MultiUserUtilsModule;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeModule;
import com.android.systemui.statusbar.CommandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 5ee2045..a3d6ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -47,7 +47,7 @@
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
-import com.android.systemui.settings.dagger.MultiUserUtilsModule
+import com.android.systemui.settings.MultiUserUtilsModule
import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.ImmersiveModeConfirmation
import com.android.systemui.statusbar.gesture.GesturePointerEventListener
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 91e0547..0cab10db 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
@@ -47,24 +47,30 @@
@Inject
constructor(
private val selectedComponentRepository: SelectedComponentRepository,
- private val controlsComponent: ControlsComponent,
- private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+ controlsComponent: ControlsComponent,
+ authorizedPanelsRepository: AuthorizedPanelsRepository,
userRepository: UserRepository,
@Background private val bgScope: CoroutineScope
) {
- private val controlsListingController =
+ private val controlsListingController: ControlsListingController? =
controlsComponent.getControlsListingController().getOrNull()
/** Gets the current user's selected panel, or null if there isn't one */
- private val selectedItem: Flow<SelectedComponentRepository.SelectedComponent?> =
+ private val selectedPanel: Flow<SelectedComponentRepository.SelectedComponent?> =
userRepository.selectedUserInfo
.flatMapLatest { user ->
selectedComponentRepository.selectedComponentFlow(user.userHandle)
}
.map { if (it?.isPanel == true) it else null }
- /** Gets all the available panels which are authorized by the user */
- private fun allPanelItem(): Flow<List<PanelComponent>> {
+ /** Gets the current user's authorized panels */
+ private val allAuthorizedPanels: Flow<Set<String>> =
+ userRepository.selectedUserInfo.flatMapLatest { user ->
+ authorizedPanelsRepository.observeAuthorizedPanels(user.userHandle)
+ }
+
+ /** Gets all the available services from [ControlsListingController] */
+ private fun allAvailableServices(): Flow<List<ControlsServiceInfo>> {
if (controlsListingController == null) {
return emptyFlow()
}
@@ -79,26 +85,38 @@
awaitClose { controlsListingController.removeCallback(listener) }
}
.onStart { emit(controlsListingController.getCurrentServices()) }
- .map { serviceInfos ->
- val authorizedPanels = authorizedPanelsRepository.getAuthorizedPanels()
- serviceInfos.mapNotNull {
- val panelActivity = it.panelActivity
- if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
- PanelComponent(it.componentName, panelActivity)
- } else {
- null
- }
+ }
+
+ /** Gets all panels which are available and authorized by the user */
+ private val allAvailableAndAuthorizedPanels: Flow<List<PanelComponent>> =
+ combine(
+ allAvailableServices(),
+ allAuthorizedPanels,
+ ) { serviceInfos, authorizedPanels ->
+ serviceInfos.mapNotNull {
+ val panelActivity = it.panelActivity
+ if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
+ PanelComponent(it.componentName, panelActivity)
+ } else {
+ null
}
}
- }
+ }
+
val panelComponent: StateFlow<ComponentName?> =
- combine(allPanelItem(), selectedItem) { items, selected ->
+ combine(
+ allAvailableAndAuthorizedPanels,
+ selectedPanel,
+ ) { panels, selected ->
val item =
- items.firstOrNull { it.componentName == selected?.componentName }
- ?: items.firstOrNull()
+ panels.firstOrNull { it.componentName == selected?.componentName }
+ ?: panels.firstOrNull()
item?.panelActivity
}
.stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
- data class PanelComponent(val componentName: ComponentName, val panelActivity: ComponentName)
+ private data class PanelComponent(
+ val componentName: ComponentName,
+ val panelActivity: ComponentName,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c69c9ef..6eff792 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -585,10 +585,6 @@
@JvmField
val SHARE_WIFI_QS_BUTTON = releasedFlag("share_wifi_qs_button")
- // TODO(b/287205379): Tracking bug
- @JvmField
- val QS_CONTAINER_GRAPH_OPTIMIZER = releasedFlag( "qs_container_graph_optimizer")
-
/** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */
@JvmField
val BLUETOOTH_QS_TILE_DIALOG = releasedFlag("bluetooth_qs_tile_dialog")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index b5f9c69..4cabd70 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
@@ -61,7 +60,6 @@
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
@@ -108,20 +106,7 @@
private final ScreenOnCoordinator mScreenOnCoordinator;
private final ShellTransitions mShellTransitions;
private final DisplayTracker mDisplayTracker;
- private PowerInteractor mPowerInteractor;
-
- private static int newModeToLegacyMode(int newMode) {
- switch (newMode) {
- case WindowManager.TRANSIT_OPEN:
- case WindowManager.TRANSIT_TO_FRONT:
- return MODE_OPENING;
- case WindowManager.TRANSIT_CLOSE:
- case WindowManager.TRANSIT_TO_BACK:
- return MODE_CLOSING;
- default:
- return 2; // MODE_CHANGING
- }
- }
+ private final PowerInteractor mPowerInteractor;
private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers,
SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap,
@@ -253,8 +238,7 @@
public void mergeAnimation(IBinder candidateTransition, TransitionInfo candidateInfo,
SurfaceControl.Transaction candidateT, IBinder currentTransition,
- IRemoteTransitionFinishedCallback candidateFinishCallback)
- throws RemoteException {
+ IRemoteTransitionFinishedCallback candidateFinishCallback) {
if ((candidateInfo.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) {
keyguardViewMediator.setPendingLock(true);
keyguardViewMediator.cancelKeyguardExitAnimation();
@@ -265,13 +249,13 @@
runner.onAnimationCancelled();
finish(currentTransition);
} catch (RemoteException e) {
- // nothing, we'll just let it finish on its own I guess.
+ // Ignore.
}
}
@Override
- public void onTransitionConsumed(IBinder transition, boolean aborted)
- throws RemoteException {
+ public void onTransitionConsumed(IBinder transition, boolean aborted) {
+ // No-op.
}
private static void initAlphaForAnimationTargets(@NonNull SurfaceControl.Transaction t,
@@ -283,7 +267,7 @@
}
private void finish(IBinder transition) throws RemoteException {
- IRemoteTransitionFinishedCallback finishCallback = null;
+ final IRemoteTransitionFinishedCallback finishCallback;
SurfaceControl.Transaction finishTransaction = null;
synchronized (mLeashMap) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 794befa..f085e88 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -45,12 +45,12 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.BroadcastOptions;
import android.app.IActivityTaskManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
-import android.app.WallpaperManager;
import android.app.WindowConfiguration;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
@@ -174,6 +174,8 @@
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -183,7 +185,6 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
-import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -326,7 +327,6 @@
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
private StatusBarManager mStatusBarManager;
- private WallpaperManager mWallpaperManager;
private final IStatusBarService mStatusBarService;
private final IBinder mStatusBarDisableToken = new Binder();
private final UserTracker mUserTracker;
@@ -356,13 +356,13 @@
private final SecureSettings mSecureSettings;
private final SystemSettings mSystemSettings;
private final SystemClock mSystemClock;
- private SystemPropertiesHelper mSystemPropertiesHelper;
+ private final SystemPropertiesHelper mSystemPropertiesHelper;
/**
* Used to keep the device awake while to ensure the keyguard finishes opening before
* we sleep.
*/
- private PowerManager.WakeLock mShowKeyguardWakeLock;
+ private final PowerManager.WakeLock mShowKeyguardWakeLock;
private final Lazy<KeyguardViewController> mKeyguardViewControllerLazy;
@@ -405,13 +405,13 @@
private boolean mWakeAndUnlocking = false;
/**
- * Helps remember whether the screen has turned on since the last time
- * it turned off due to timeout. see {@link #onScreenTurnedOff(int)}
+ * Helps remember whether the screen has turned on since the last time it turned off due to
+ * timeout. See {@link #onScreenTurnedOff}
*/
private int mDelayedShowingSequence;
/**
- * Similar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
+ * Similar to {@link #mDelayedShowingSequence}, but it is for profile case.
*/
private int mDelayedProfileShowingSequence;
@@ -439,7 +439,7 @@
private boolean mGoingToSleep;
// last known state of the cellular connection
- private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
+ private final String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
/**
* Whether a hide is pending and we are just waiting for #startKeyguardExitAnimation to be
@@ -1088,8 +1088,7 @@
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
- if (!handleOnAnimationStart(
- transit, apps, wallpapers, nonApps, finishedCallback)) {
+ if (!handleOnAnimationStart(apps, finishedCallback)) {
// Usually we rely on animation completion to synchronize occluded status,
// but there was no animation to play, so just update it now.
setOccluded(true /* isOccluded */, false /* animate */);
@@ -1097,9 +1096,8 @@
}
}
- private boolean handleOnAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+ private boolean handleOnAnimationStart(RemoteAnimationTarget[] apps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
if (apps == null || apps.length == 0 || apps[0] == null) {
Log.d(TAG, "No apps provided to the OccludeByDream runner; "
+ "skipping occluding animation.");
@@ -1107,8 +1105,8 @@
}
final RemoteAnimationTarget primary = apps[0];
- final boolean isDream = (apps[0].taskInfo != null
- && apps[0].taskInfo.topActivityType
+ final boolean isDream = (primary.taskInfo != null
+ && primary.taskInfo.topActivityType
== WindowConfiguration.ACTIVITY_TYPE_DREAM);
if (!isDream) {
Log.w(TAG, "The occluding app isn't Dream; "
@@ -1322,9 +1320,9 @@
}
private DeviceConfigProxy mDeviceConfig;
- private DozeParameters mDozeParameters;
- private SelectedUserInteractor mSelectedUserInteractor;
- private KeyguardInteractor mKeyguardInteractor;
+ private final DozeParameters mDozeParameters;
+ private final SelectedUserInteractor mSelectedUserInteractor;
+ private final KeyguardInteractor mKeyguardInteractor;
@VisibleForTesting
protected FoldGracePeriodProvider mFoldGracePeriodProvider =
new FoldGracePeriodProvider();
@@ -1346,14 +1344,12 @@
private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
private final InteractionJankMonitor mInteractionJankMonitor;
private boolean mWallpaperSupportsAmbientMode;
- private ScreenOnCoordinator mScreenOnCoordinator;
private final KeyguardTransitions mKeyguardTransitions;
- private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
- private Lazy<ScrimController> mScrimControllerLazy;
- private IActivityTaskManager mActivityTaskManagerService;
+ private final Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
+ private final Lazy<ScrimController> mScrimControllerLazy;
+ private final IActivityTaskManager mActivityTaskManagerService;
- private FeatureFlags mFeatureFlags;
private final UiEventLogger mUiEventLogger;
private final SessionTracker mSessionTracker;
private final CoroutineDispatcher mMainDispatcher;
@@ -1361,7 +1357,7 @@
mDreamingToLockscreenTransitionViewModel;
private RemoteAnimationTarget mRemoteAnimationTarget;
- private Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+ private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
/**
* Injected constructor. See {@link KeyguardModule}.
@@ -1433,7 +1429,6 @@
mShadeController = shadeControllerLazy;
dumpManager.registerDumpable(this);
mDeviceConfig = deviceConfig;
- mScreenOnCoordinator = screenOnCoordinator;
mKeyguardTransitions = keyguardTransitions;
mNotificationShadeWindowControllerLazy = notificationShadeWindowControllerLazy;
mShowHomeOverLockscreen = mDeviceConfig.getBoolean(
@@ -1445,9 +1440,8 @@
mHandler::post,
mOnPropertiesChangedListener);
mInGestureNavigationMode =
- QuickStepContract.isGesturalMode(navigationModeController.addListener(mode -> {
- mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
- }));
+ QuickStepContract.isGesturalMode(navigationModeController.addListener(mode ->
+ mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode)));
mDozeParameters = dozeParameters;
mSelectedUserInteractor = selectedUserInteractor;
mKeyguardInteractor = keyguardInteractor;
@@ -1474,7 +1468,6 @@
mDreamOpenAnimationDuration = (int) DREAMING_ANIMATION_DURATION_MS;
- mFeatureFlags = featureFlags;
mUiEventLogger = uiEventLogger;
mSessionTracker = sessionTracker;
@@ -1578,14 +1571,6 @@
this::setWallpaperSupportsAmbientMode);
}
- // TODO(b/273443374) remove, temporary util to get a feature flag
- private WallpaperManager getWallpaperManager() {
- if (mWallpaperManager == null) {
- mWallpaperManager = mContext.getSystemService(WallpaperManager.class);
- }
- return mWallpaperManager;
- }
-
@Override
public void start() {
synchronized (this) {
@@ -1611,11 +1596,11 @@
ViewRootImpl viewRootImpl = mKeyguardViewControllerLazy.get().getViewRootImpl();
if (viewRootImpl != null) {
- collectFlow(viewRootImpl.getView(),
- mDreamingToLockscreenTransitionViewModel.get().getDreamOverlayAlpha(),
+ DreamingToLockscreenTransitionViewModel viewModel =
+ mDreamingToLockscreenTransitionViewModel.get();
+ collectFlow(viewRootImpl.getView(), viewModel.getDreamOverlayAlpha(),
getRemoteSurfaceAlphaApplier(), mMainDispatcher);
- collectFlow(viewRootImpl.getView(),
- mDreamingToLockscreenTransitionViewModel.get().getTransitionEnded(),
+ collectFlow(viewRootImpl.getView(), viewModel.getTransitionEnded(),
getFinishedCallbackConsumer(), mMainDispatcher);
}
}
@@ -2304,6 +2289,7 @@
showKeyguard(options);
}
+ @SuppressLint("MissingPermission")
private void lockProfile(int userId) {
mTrustManager.setDeviceLockedForUser(userId, true);
}
@@ -2497,13 +2483,12 @@
};
/**
- * This handler will be associated with the policy thread, which will also
- * be the UI thread of the keyguard. Since the apis of the policy, and therefore
- * this class, can be called by other threads, any action that directly
- * interacts with the keyguard ui should be posted to this handler, rather
- * than called directly.
+ * This handler will be associated with the policy thread, which will also be the UI thread of
+ * the keyguard. Since the apis of the policy, and therefore this class, can be called by other
+ * threads, any action that directly interacts with the keyguard ui should be posted to this
+ * handler, rather than called directly.
*/
- private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
+ private final Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
@Override
public void handleMessage(Message msg) {
String message = "";
@@ -2766,7 +2751,7 @@
try {
mActivityTaskManagerService.setLockScreenShown(showing, aodShowing);
- } catch (RemoteException e) {
+ } catch (RemoteException ignored) {
}
});
}
@@ -2790,9 +2775,8 @@
if (!mSystemReady) {
if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready.");
return;
- } else {
- if (DEBUG) Log.d(TAG, "handleShow");
}
+ if (DEBUG) Log.d(TAG, "handleShow");
mKeyguardExitAnimationRunner = null;
mWakeAndUnlocking = false;
@@ -2851,6 +2835,7 @@
}
private final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
+ @SuppressLint("MissingPermission")
@Override
public void run() {
Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
@@ -2925,24 +2910,13 @@
return;
}
- final String reasonDescription;
-
- switch(reason) {
- case WakeAndUnlockUpdateReason.FULFILL:
- reasonDescription = "fulfilling existing request";
- break;
- case WakeAndUnlockUpdateReason.HIDE:
- reasonDescription = "hiding keyguard";
- break;
- case WakeAndUnlockUpdateReason.SHOW:
- reasonDescription = "showing keyguard";
- break;
- case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK:
- reasonDescription = "waking to unlock";
- break;
- default:
- throw new IllegalStateException("Unexpected value: " + reason);
- }
+ final String reasonDescription = switch (reason) {
+ case WakeAndUnlockUpdateReason.FULFILL -> "fulfilling existing request";
+ case WakeAndUnlockUpdateReason.HIDE -> "hiding keyguard";
+ case WakeAndUnlockUpdateReason.SHOW -> "showing keyguard";
+ case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK -> "waking to unlock";
+ default -> throw new IllegalStateException("Unexpected value: " + reason);
+ };
final boolean unsetUnfulfilled = !updatedValue
&& reason != WakeAndUnlockUpdateReason.FULFILL;
@@ -3057,7 +3031,7 @@
IRemoteAnimationFinishedCallback callback =
new IRemoteAnimationFinishedCallback() {
@Override
- public void onAnimationFinished() throws RemoteException {
+ public void onAnimationFinished() {
if (!KeyguardWmStateRefactor.isEnabled()) {
try {
finishedCallback.onAnimationFinished();
@@ -3542,11 +3516,6 @@
/**
* Registers the CentralSurfaces to which the Keyguard View is mounted.
*
- * @param centralSurfaces
- * @param panelView
- * @param biometricUnlockController
- * @param notificationContainer
- * @param bypassController
* @return the View Controller for the Keyguard View this class is mediating.
*/
public KeyguardViewController registerCentralSurfaces(CentralSurfaces centralSurfaces,
@@ -3773,9 +3742,7 @@
}
});
updateInputRestrictedLocked();
- mUiBgExecutor.execute(() -> {
- mTrustManager.reportKeyguardShowingChanged();
- });
+ mUiBgExecutor.execute(mTrustManager::reportKeyguardShowingChanged);
}
private void notifyTrustedChangedLocked(boolean trusted) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index e16f8dc..70da3e7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -90,10 +90,12 @@
import java.util.concurrent.Executor;
import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
/**
* Dagger Module providing keyguard.
*/
+@ExperimentalCoroutinesApi
@Module(subcomponents = {
KeyguardQsUserSwitchComponent.class,
KeyguardStatusBarViewComponent.class,
@@ -115,7 +117,7 @@
*/
@Provides
@SysUISingleton
- public static KeyguardViewMediator newKeyguardViewMediator(
+ static KeyguardViewMediator newKeyguardViewMediator(
Context context,
UiEventLogger uiEventLogger,
SessionTracker sessionTracker,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 48092c6..789d30f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -191,6 +191,7 @@
.collect { y ->
childViews[burnInLayerId]?.translationY = y
childViews[largeClockId]?.translationY = y
+ childViews[aodNotificationIconContainerId]?.translationY = y
}
}
@@ -200,6 +201,7 @@
.collect { x ->
childViews[burnInLayerId]?.translationX = x
childViews[largeClockId]?.translationX = x
+ childViews[aodNotificationIconContainerId]?.translationX = x
}
}
@@ -219,6 +221,10 @@
// transition with other parts in burnInLayer
childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
+ childViews[aodNotificationIconContainerId]?.scaleX =
+ scaleViewModel.scale
+ childViews[aodNotificationIconContainerId]?.scaleY =
+ scaleViewModel.scale
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 3d36eb0..9a1fcc1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -41,11 +41,13 @@
return
}
- val nic = constraintLayout.requireViewById<View>(R.id.aod_notification_icon_container)
+ // The burn-in layer requires at least 1 view at all times
+ val emptyView = View(context, null).apply { id = View.generateViewId() }
+ constraintLayout.addView(emptyView)
burnInLayer =
AodBurnInLayer(context).apply {
id = R.id.burn_in_layer
- addView(nic)
+ addView(emptyView)
if (!migrateClocksToBlueprint()) {
val statusView =
constraintLayout.requireViewById<View>(R.id.keyguard_status_view)
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt
new file mode 100644
index 0000000..fe7dc4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.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.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [TableLogBuffer] for communal-related logging. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CommunalTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 3e00940..ac579d6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -579,6 +579,16 @@
return factory.create("CommunalLog", 250);
}
+ /**
+ * Provides a {@link TableLogBuffer} for communal-related logs.
+ */
+ @Provides
+ @SysUISingleton
+ @CommunalTableLog
+ public static TableLogBuffer provideCommunalTableLogBuffer(TableLogBufferFactory factory) {
+ return factory.create("CommunalTableLog", 250);
+ }
+
/** Provides a {@link LogBuffer} for display metrics related logs. */
@Provides
@SysUISingleton
@@ -618,4 +628,13 @@
public static LogBuffer providePackageChangeRepoLogBuffer(LogBufferFactory factory) {
return factory.create("PackageChangeRepo", 50);
}
+
+ /** Provides a {@link LogBuffer} for NavBarButtonClicks. */
+ @Provides
+ @SysUISingleton
+ @NavBarButtonClickLog
+ public static LogBuffer provideNavBarButtonClickLogBuffer(LogBufferFactory factory) {
+ return factory.create("NavBarButtonClick", 50);
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
new file mode 100644
index 0000000..939dab2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
@@ -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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for {@link com.android.systemui.navigationbar.NavigationBar}. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NavBarButtonClickLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt
new file mode 100644
index 0000000..408acf3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.navigationbar
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NavBarButtonClickLog
+import javax.inject.Inject
+
+class NavBarButtonClickLogger
+@Inject
+constructor(@NavBarButtonClickLog private val buffer: LogBuffer) {
+ fun logHomeButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Home Button Triggered" })
+ }
+
+ fun logBackButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Back Button Triggered" })
+ }
+
+ fun logRecentsButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Recents Button Triggered" })
+ }
+
+ fun logImeSwitcherClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Ime Switcher Triggered" })
+ }
+
+ fun logAccessibilityButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Accessibility Button Triggered" })
+ }
+}
+
+private const val TAG = "NavBarButtonClick"
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 068e5fd..95b75ac 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -84,11 +84,7 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.ViewRootImpl.SurfaceChangedCallback;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
@@ -285,6 +281,7 @@
private boolean mImeVisible;
private final Rect mSamplingBounds = new Rect();
private final Binder mInsetsSourceOwner = new Binder();
+ private final NavBarButtonClickLogger mNavBarButtonClickLogger;
/**
* When quickswitching between apps of different orientations, we draw a secondary home handle
@@ -559,7 +556,8 @@
UserContextProvider userContextProvider,
WakefulnessLifecycle wakefulnessLifecycle,
TaskStackChangeListeners taskStackChangeListeners,
- DisplayTracker displayTracker) {
+ DisplayTracker displayTracker,
+ NavBarButtonClickLogger navBarButtonClickLogger) {
super(navigationBarView);
mFrame = navigationBarFrame;
mContext = context;
@@ -601,6 +599,7 @@
mTaskStackChangeListeners = taskStackChangeListeners;
mDisplayTracker = displayTracker;
mEdgeBackGestureHandler = navBarHelper.getEdgeBackGestureHandler();
+ mNavBarButtonClickLogger = navBarButtonClickLogger;
mNavColorSampleMargin = getResources()
.getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
@@ -1276,6 +1275,10 @@
ButtonDispatcher homeButton = mView.getHomeButton();
homeButton.setOnTouchListener(this::onHomeTouch);
+ homeButton.setNavBarButtonClickLogger(mNavBarButtonClickLogger);
+
+ ButtonDispatcher backButton = mView.getBackButton();
+ backButton.setNavBarButtonClickLogger(mNavBarButtonClickLogger);
reconfigureHomeLongClick();
@@ -1388,6 +1391,8 @@
}
private void onRecentsClick(View v) {
+ mNavBarButtonClickLogger.logRecentsButtonClick();
+
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionStart(
LatencyTracker.ACTION_TOGGLE_RECENTS);
@@ -1397,6 +1402,7 @@
}
private void onImeSwitcherClick(View v) {
+ mNavBarButtonClickLogger.logImeSwitcherClick();
mInputMethodManager.showInputMethodPickerFromSystem(
true /* showAuxiliarySubtypes */, mDisplayId);
mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
@@ -1486,6 +1492,7 @@
}
private void onAccessibilityClick(View v) {
+ mNavBarButtonClickLogger.logAccessibilityButtonClick();
final Display display = v.getDisplay();
mAccessibilityManager.notifyAccessibilityButtonClicked(
display != null ? display.getDisplayId() : mDisplayTracker.getDefaultDisplayId());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
index 5739abc..fc37b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
@@ -23,6 +23,9 @@
import android.animation.ValueAnimator;
import android.view.View;
import android.view.View.AccessibilityDelegate;
+import android.view.ViewGroup;
+
+import com.android.systemui.navigationbar.NavBarButtonClickLogger;
import java.util.ArrayList;
@@ -52,6 +55,7 @@
private boolean mVertical;
private ValueAnimator mFadeAnimator;
private AccessibilityDelegate mAccessibilityDelegate;
+ private NavBarButtonClickLogger mNavBarButtonClickLogger;
private final ValueAnimator.AnimatorUpdateListener mAlphaListener = animation ->
setAlpha(
@@ -341,4 +345,36 @@
*/
public void onDestroy() {
}
+
+ /**
+ * Sets the NavBarButtonClickLogger for all the KeyButtonViews respectively.
+ */
+ public void setNavBarButtonClickLogger(NavBarButtonClickLogger navBarButtonClickLogger) {
+ if (navBarButtonClickLogger != null) {
+ mNavBarButtonClickLogger = navBarButtonClickLogger;
+ final int size = mViews.size();
+ for (int i = 0; i < size; i++) {
+ final View v = mViews.get(i);
+ setNavBarButtonClickLoggerForViewChildren(v);
+ }
+ }
+ }
+
+ /**
+ * Recursively explores view hierarchy until the children of provided view are of type
+ * KeyButtonView, so the NavBarButtonClickLogger can be set on them.
+ */
+ private void setNavBarButtonClickLoggerForViewChildren(View v) {
+ if (v instanceof KeyButtonView) {
+ ((KeyButtonView) v).setNavBarButtonClickLogger(mNavBarButtonClickLogger);
+ return;
+ }
+
+ if (v instanceof ViewGroup viewGroup) {
+ final int childrenCount = viewGroup.getChildCount();
+ for (int i = 0; i < childrenCount; i++) {
+ setNavBarButtonClickLoggerForViewChildren(viewGroup.getChildAt(i));
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index df6843d..dbe87ea 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -59,6 +59,7 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.NavBarButtonClickLogger;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.res.R;
import com.android.systemui.shared.navigationbar.KeyButtonRipple;
@@ -86,6 +87,7 @@
private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private float mDarkIntensity;
private boolean mHasOvalBg = false;
+ private NavBarButtonClickLogger mNavBarButtonClickLogger;
@VisibleForTesting
public enum NavBarButtonEvent implements UiEventLogger.UiEventEnum {
@@ -197,6 +199,10 @@
mOnClickListener = onClickListener;
}
+ public void setNavBarButtonClickLogger(NavBarButtonClickLogger navBarButtonClickLogger) {
+ mNavBarButtonClickLogger = navBarButtonClickLogger;
+ }
+
public void loadAsync(Icon icon) {
new AsyncTask<Icon, Void, Drawable>() {
@Override
@@ -389,11 +395,19 @@
uiEvent = longPressSet
? NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS
: NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
+
+ if (mNavBarButtonClickLogger != null) {
+ mNavBarButtonClickLogger.logBackButtonClick();
+ }
break;
case KeyEvent.KEYCODE_HOME:
uiEvent = longPressSet
? NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS
: NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
+
+ if (mNavBarButtonClickLogger != null) {
+ mNavBarButtonClickLogger.logHomeButtonClick();
+ }
break;
case KeyEvent.KEYCODE_APP_SWITCH:
uiEvent = longPressSet
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
rename to packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
index a0dd924..fd807db 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.settings.dagger;
+package com.android.systemui.settings;
import android.app.ActivityManager;
import android.app.IActivityManager;
@@ -29,14 +29,6 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.settings.DisplayTrackerImpl;
-import com.android.systemui.settings.UserContentResolverProvider;
-import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.settings.UserFileManager;
-import com.android.systemui.settings.UserFileManagerImpl;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.settings.UserTrackerImpl;
import dagger.Binds;
import dagger.Module;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
new file mode 100644
index 0000000..76d1d3d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings
+
+import android.content.ContentResolver
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepositoryImpl
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+
+@Module
+object SecureSettingsRepositoryModule {
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun provideSecureSettingsRepository(
+ contentResolver: ContentResolver,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ ): SecureSettingsRepository =
+ SecureSettingsRepositoryImpl(contentResolver, backgroundDispatcher)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt
deleted file mode 100644
index b09bfe2..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt
+++ /dev/null
@@ -1,43 +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.settings
-
-import android.annotation.UserIdInt
-import android.content.Context
-import android.content.SharedPreferences
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-
-/** Extension functions for [UserFileManager]. */
-object UserFileManagerExt {
-
- /** Returns a flow of [Unit] that is invoked each time the shared preference is updated. */
- fun UserFileManager.observeSharedPreferences(
- fileName: String,
- @Context.PreferencesMode mode: Int,
- @UserIdInt userId: Int
- ): Flow<Unit> = conflatedCallbackFlow {
- val sharedPrefs = getSharedPreferences(fileName, mode, userId)
-
- val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) }
-
- sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
- awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 84cad1d..c0afa32 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -30,8 +30,6 @@
import com.android.systemui.Flags.centralizedStatusBarDimensRefactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentService
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -59,18 +57,17 @@
@SysUISingleton
class NotificationsQSContainerController @Inject constructor(
- view: NotificationsQuickSettingsContainer,
- private val navigationModeController: NavigationModeController,
- private val overviewProxyService: OverviewProxyService,
- private val shadeHeaderController: ShadeHeaderController,
- private val shadeInteractor: ShadeInteractor,
- private val fragmentService: FragmentService,
- @Main private val delayableExecutor: DelayableExecutor,
- private val featureFlags: FeatureFlags,
- private val
- notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
- private val splitShadeStateController: SplitShadeStateController,
- private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
+ view: NotificationsQuickSettingsContainer,
+ private val navigationModeController: NavigationModeController,
+ private val overviewProxyService: OverviewProxyService,
+ private val shadeHeaderController: ShadeHeaderController,
+ private val shadeInteractor: ShadeInteractor,
+ private val fragmentService: FragmentService,
+ @Main private val delayableExecutor: DelayableExecutor,
+ private val
+ notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
+ private val splitShadeStateController: SplitShadeStateController,
+ private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
private var splitShadeEnabled = false
@@ -133,9 +130,6 @@
isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
mView.setStackScroller(notificationStackScrollLayoutController.getView())
- if (featureFlags.isEnabled(Flags.QS_CONTAINER_GRAPH_OPTIMIZER)){
- mView.enableGraphOptimization()
- }
}
public override fun onViewAttached() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index de3d16a..25e558e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -32,10 +32,10 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
-import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
-import com.android.systemui.res.R;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import java.util.ArrayList;
@@ -73,6 +73,7 @@
public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
super(context, attrs);
+ setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH);
}
@Override
@@ -180,10 +181,6 @@
super.dispatchDraw(canvas);
}
- void enableGraphOptimization() {
- setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH);
- }
-
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev,
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 32cd56c..b64e0b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -56,7 +56,6 @@
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarIconList;
-import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -73,7 +72,7 @@
* their own version of CentralSurfaces can include just dependencies, without injecting
* CentralSurfaces itself.
*/
-@Module(includes = {StatusBarNotificationPresenterModule.class})
+@Module
public interface CentralSurfacesDependenciesModule {
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
index 99d4b2e..27536bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
@@ -18,12 +18,14 @@
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
import dagger.Module;
-/** */
-@Module(includes = {StatusBarPhoneModule.class, CentralSurfacesDependenciesModule.class,
+/** */
+@Module(includes = {CentralSurfacesDependenciesModule.class,
+ StatusBarNotificationPresenterModule.class, StatusBarPhoneModule.class,
NotificationsModule.class, NotificationRowModule.class})
public interface CentralSurfacesModule {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index ae4ba27..29627e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -18,7 +18,7 @@
import android.os.UserHandle
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.screenshareNotificationHiding
+import com.android.server.notification.Flags.screenshareNotificationHiding
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index cd816ae..954e805 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.collection.inflation;
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 6bba72b..92b0c04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -51,6 +51,7 @@
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.data.NotificationDataLayerModule;
+import com.android.systemui.statusbar.notification.domain.NotificationDomainLayerModule;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule;
import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
@@ -78,14 +79,14 @@
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import javax.inject.Provider;
-
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
+import javax.inject.Provider;
+
/**
* Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
*/
@@ -94,6 +95,7 @@
FooterViewModelModule.class,
KeyguardNotificationVisibilityProviderModule.class,
NotificationDataLayerModule.class,
+ NotificationDomainLayerModule.class,
NotifPipelineChoreographerModule.class,
NotificationSectionHeadersModule.class,
ActivatableNotificationViewModelModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
index 2cac000..b187cf1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
@@ -17,4 +17,10 @@
import dagger.Module
-@Module(includes = []) interface NotificationDataLayerModule
+@Module(
+ includes =
+ [
+ NotificationSettingsRepositoryModule::class,
+ ]
+)
+interface NotificationDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
new file mode 100644
index 0000000..a7970c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.data
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.SecureSettingsRepositoryModule
+import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+
+@Module(includes = [SecureSettingsRepositoryModule::class])
+object NotificationSettingsRepositoryModule {
+ @Provides
+ @SysUISingleton
+ fun provideNotificationSettingsRepository(
+ @Background backgroundScope: CoroutineScope,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ secureSettingsRepository: SecureSettingsRepository,
+ ): NotificationSettingsRepository =
+ NotificationSettingsRepository(
+ backgroundScope,
+ backgroundDispatcher,
+ secureSettingsRepository
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt
new file mode 100644
index 0000000..5c49b28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain
+
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationSettingsInteractorModule
+import dagger.Module
+
+@Module(includes = [NotificationSettingsInteractorModule::class])
+object NotificationDomainLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
new file mode 100644
index 0000000..0a9e12a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
+import dagger.Module
+import dagger.Provides
+
+@Module
+object NotificationSettingsInteractorModule {
+ @Provides
+ @SysUISingleton
+ fun provideNotificationSettingsInteractor(repository: NotificationSettingsRepository) =
+ NotificationSettingsInteractor(repository)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 3616fd6d..16f18a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -54,7 +54,7 @@
private static final String TAG = "FooterView";
private FooterViewButton mClearAllButton;
- private FooterViewButton mManageButton;
+ private FooterViewButton mManageOrHistoryButton;
private boolean mShowHistory;
// String cache, for performance reasons.
// Reading them from a Resources object can be quite slow sometimes.
@@ -68,6 +68,8 @@
private @StringRes int mClearAllButtonTextId;
private @StringRes int mClearAllButtonDescriptionId;
+ private @StringRes int mManageOrHistoryButtonTextId;
+ private @StringRes int mManageOrHistoryButtonDescriptionId;
private @StringRes int mMessageStringId;
private @DrawableRes int mMessageIconId;
@@ -155,6 +157,43 @@
mClearAllButton.setContentDescription(getContext().getString(mClearAllButtonDescriptionId));
}
+ /** Set the text label for the "Manage"/"History" button. */
+ public void setManageOrHistoryButtonText(@StringRes int textId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+ if (mManageOrHistoryButtonTextId == textId) {
+ return; // nothing changed
+ }
+ mManageOrHistoryButtonTextId = textId;
+ updateManageOrHistoryButtonText();
+ }
+
+ private void updateManageOrHistoryButtonText() {
+ if (mManageOrHistoryButtonTextId == 0) {
+ return; // not initialized yet
+ }
+ mManageOrHistoryButton.setText(getContext().getString(mManageOrHistoryButtonTextId));
+ }
+
+ /** Set the accessibility content description for the "Clear all" button. */
+ public void setManageOrHistoryButtonDescription(@StringRes int contentDescriptionId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+ if (mManageOrHistoryButtonDescriptionId == contentDescriptionId) {
+ return; // nothing changed
+ }
+ mManageOrHistoryButtonDescriptionId = contentDescriptionId;
+ updateManageOrHistoryButtonDescription();
+ }
+
+ private void updateManageOrHistoryButtonDescription() {
+ if (mManageOrHistoryButtonDescriptionId == 0) {
+ return; // not initialized yet
+ }
+ mManageOrHistoryButton.setContentDescription(
+ getContext().getString(mManageOrHistoryButtonDescriptionId));
+ }
+
/** Set the string for a message to be shown instead of the buttons. */
public void setMessageString(@StringRes int messageId) {
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
@@ -173,7 +212,6 @@
mSeenNotifsFooterTextView.setText(messageString);
}
-
/** Set the icon to be shown before the message (see {@link #setMessageString(int)}). */
public void setMessageIcon(@DrawableRes int iconId) {
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
@@ -203,9 +241,11 @@
protected void onFinishInflate() {
super.onFinishInflate();
mClearAllButton = (FooterViewButton) findSecondaryView();
- mManageButton = findViewById(R.id.manage_text);
+ mManageOrHistoryButton = findViewById(R.id.manage_text);
mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
- updateResources();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateResources();
+ }
updateContent();
updateColors();
}
@@ -213,11 +253,11 @@
/** Show a message instead of the footer buttons. */
public void setFooterLabelVisible(boolean isVisible) {
if (isVisible) {
- mManageButton.setVisibility(View.GONE);
+ mManageOrHistoryButton.setVisibility(View.GONE);
mClearAllButton.setVisibility(View.GONE);
mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
} else {
- mManageButton.setVisibility(View.VISIBLE);
+ mManageOrHistoryButton.setVisibility(View.VISIBLE);
mClearAllButton.setVisibility(View.VISIBLE);
mSeenNotifsFooterTextView.setVisibility(View.GONE);
}
@@ -225,7 +265,7 @@
/** Set onClickListener for the manage/history button. */
public void setManageButtonClickListener(OnClickListener listener) {
- mManageButton.setOnClickListener(listener);
+ mManageOrHistoryButton.setOnClickListener(listener);
}
/** Set onClickListener for the clear all (end) button. */
@@ -252,6 +292,7 @@
/** Show "History" instead of "Manage" on the start button. */
public void showHistory(boolean showHistory) {
+ FooterViewRefactor.assertInLegacyMode();
if (mShowHistory == showHistory) {
return;
}
@@ -260,17 +301,13 @@
}
private void updateContent() {
- if (mShowHistory) {
- mManageButton.setText(mManageNotificationHistoryText);
- mManageButton.setContentDescription(mManageNotificationHistoryText);
- } else {
- mManageButton.setText(mManageNotificationText);
- mManageButton.setContentDescription(mManageNotificationText);
- }
if (FooterViewRefactor.isEnabled()) {
updateClearAllButtonText();
updateClearAllButtonDescription();
+ updateManageOrHistoryButtonText();
+ updateManageOrHistoryButtonDescription();
+
updateMessageString();
updateMessageIcon();
} else {
@@ -285,6 +322,14 @@
// `updateResources`, which will eventually be removed. There are, however, still
// situations in which we want to update the views even if the resource IDs didn't
// change, such as configuration changes.
+ if (mShowHistory) {
+ mManageOrHistoryButton.setText(mManageNotificationHistoryText);
+ mManageOrHistoryButton.setContentDescription(mManageNotificationHistoryText);
+ } else {
+ mManageOrHistoryButton.setText(mManageNotificationText);
+ mManageOrHistoryButton.setContentDescription(mManageNotificationText);
+ }
+
mClearAllButton.setText(R.string.clear_all_notifications_text);
mClearAllButton.setContentDescription(
mContext.getString(R.string.accessibility_clear_all));
@@ -297,6 +342,7 @@
/** Whether the start button shows "History" (true) or "Manage" (false). */
public boolean isHistoryShown() {
+ FooterViewRefactor.assertInLegacyMode();
return mShowHistory;
}
@@ -304,7 +350,9 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateColors();
- updateResources();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateResources();
+ }
updateContent();
}
@@ -328,23 +376,22 @@
}
mClearAllButton.setBackground(clearAllBg);
mClearAllButton.setTextColor(onSurface);
- mManageButton.setBackground(manageBg);
- mManageButton.setTextColor(onSurface);
+ mManageOrHistoryButton.setBackground(manageBg);
+ mManageOrHistoryButton.setTextColor(onSurface);
mSeenNotifsFooterTextView.setTextColor(onSurface);
mSeenNotifsFooterTextView.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
}
private void updateResources() {
+ FooterViewRefactor.assertInLegacyMode();
mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
mManageNotificationHistoryText = getContext()
.getString(R.string.manage_notifications_history_text);
- if (!FooterViewRefactor.isEnabled()) {
- int unlockIconSize = getResources()
- .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
- mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
- mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
- mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
- }
+ int unlockIconSize = getResources()
+ .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
+ mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
+ mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
+ mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index e0eee96..9fb453a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -25,49 +25,136 @@
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
/** Binds a [FooterView] to its [view model][FooterViewModel]. */
object FooterViewBinder {
- fun bind(
+ fun bindWhileAttached(
footer: FooterView,
viewModel: FooterViewModel,
clearAllNotifications: View.OnClickListener,
+ launchNotificationSettings: View.OnClickListener,
+ launchNotificationHistory: View.OnClickListener,
): DisposableHandle {
+ return footer.repeatWhenAttached {
+ lifecycleScope.launch {
+ bind(
+ footer,
+ viewModel,
+ clearAllNotifications,
+ launchNotificationSettings,
+ launchNotificationHistory
+ )
+ }
+ }
+ }
+
+ suspend fun bind(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ clearAllNotifications: View.OnClickListener,
+ launchNotificationSettings: View.OnClickListener,
+ launchNotificationHistory: View.OnClickListener
+ ) = coroutineScope {
+ launch {
+ bindClearAllButton(
+ footer,
+ viewModel,
+ clearAllNotifications,
+ )
+ }
+ launch {
+ bindManageOrHistoryButton(
+ footer,
+ viewModel,
+ launchNotificationSettings,
+ launchNotificationHistory
+ )
+ }
+ launch { bindMessage(footer, viewModel) }
+ }
+
+ private suspend fun bindClearAllButton(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ clearAllNotifications: View.OnClickListener,
+ ) = coroutineScope {
+ footer.setClearAllButtonClickListener(clearAllNotifications)
+
+ launch {
+ viewModel.clearAllButton.labelId.collect { textId ->
+ footer.setClearAllButtonText(textId)
+ }
+ }
+
+ launch {
+ viewModel.clearAllButton.accessibilityDescriptionId.collect { textId ->
+ footer.setClearAllButtonDescription(textId)
+ }
+ }
+
+ launch {
+ viewModel.clearAllButton.isVisible.collect { isVisible ->
+ if (isVisible.isAnimating) {
+ footer.setClearAllButtonVisible(
+ isVisible.value,
+ /* animate = */ true,
+ ) { _ ->
+ isVisible.stopAnimating()
+ }
+ } else {
+ footer.setClearAllButtonVisible(
+ isVisible.value,
+ /* animate = */ false,
+ )
+ }
+ }
+ }
+ }
+
+ private suspend fun bindManageOrHistoryButton(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ launchNotificationSettings: View.OnClickListener,
+ launchNotificationHistory: View.OnClickListener,
+ ) = coroutineScope {
+ launch {
+ viewModel.manageButtonShouldLaunchHistory.collect { shouldLaunchHistory ->
+ if (shouldLaunchHistory) {
+ footer.setManageButtonClickListener(launchNotificationHistory)
+ } else {
+ footer.setManageButtonClickListener(launchNotificationSettings)
+ }
+ }
+ }
+
+ launch {
+ viewModel.manageOrHistoryButton.labelId.collect { textId ->
+ footer.setManageOrHistoryButtonText(textId)
+ }
+ }
+
+ launch {
+ viewModel.clearAllButton.accessibilityDescriptionId.collect { textId ->
+ footer.setManageOrHistoryButtonDescription(textId)
+ }
+ }
+
+ // NOTE: The manage/history button is always visible as long as the footer is visible, no
+ // need to update the visibility here.
+ }
+
+ private suspend fun bindMessage(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ ) = coroutineScope {
// Bind the resource IDs
footer.setMessageString(viewModel.message.messageId)
footer.setMessageIcon(viewModel.message.iconId)
- footer.setClearAllButtonText(viewModel.clearAllButton.labelId)
- footer.setClearAllButtonDescription(viewModel.clearAllButton.accessibilityDescriptionId)
- // Bind the click listeners
- footer.setClearAllButtonClickListener(clearAllNotifications)
-
- // Listen for visibility changes when the view is attached.
- return footer.repeatWhenAttached {
- lifecycleScope.launch {
- viewModel.clearAllButton.isVisible.collect { isVisible ->
- if (isVisible.isAnimating) {
- footer.setClearAllButtonVisible(
- isVisible.value,
- /* animate = */ true,
- ) { _ ->
- isVisible.stopAnimating()
- }
- } else {
- footer.setClearAllButtonVisible(
- isVisible.value,
- /* animate = */ false,
- )
- }
- }
- }
-
- lifecycleScope.launch {
- viewModel.message.isVisible.collect { visible ->
- footer.setFooterLabelVisible(visible)
- }
- }
+ launch {
+ viewModel.message.isVisible.collect { visible -> footer.setFooterLabelVisible(visible) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
index 244555a..691dc42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
@@ -21,7 +21,7 @@
import kotlinx.coroutines.flow.Flow
data class FooterButtonViewModel(
- @StringRes val labelId: Int,
- @StringRes val accessibilityDescriptionId: Int,
+ @StringRes val labelId: Flow<Int>,
+ @StringRes val accessibilityDescriptionId: Flow<Int>,
val isVisible: Flow<AnimatedValue<Boolean>>,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index e6b0abc..5111c11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -19,30 +19,36 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.ui.AnimatableEvent
+import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.toAnimatedValueFlow
import dagger.Module
import dagger.Provides
import java.util.Optional
import javax.inject.Provider
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
/** ViewModel for [FooterView]. */
class FooterViewModel(
activeNotificationsInteractor: ActiveNotificationsInteractor,
+ notificationSettingsInteractor: NotificationSettingsInteractor,
seenNotificationsInteractor: SeenNotificationsInteractor,
shadeInteractor: ShadeInteractor,
) {
val clearAllButton: FooterButtonViewModel =
FooterButtonViewModel(
- labelId = R.string.clear_all_notifications_text,
- accessibilityDescriptionId = R.string.accessibility_clear_all,
+ labelId = flowOf(R.string.clear_all_notifications_text),
+ accessibilityDescriptionId = flowOf(R.string.accessibility_clear_all),
isVisible =
activeNotificationsInteractor.hasClearableNotifications
.sample(
@@ -59,6 +65,22 @@
.toAnimatedValueFlow(),
)
+ val manageButtonShouldLaunchHistory =
+ notificationSettingsInteractor.isNotificationHistoryEnabled
+
+ private val manageOrHistoryButtonText: Flow<Int> =
+ manageButtonShouldLaunchHistory.map { shouldLaunchHistory ->
+ if (shouldLaunchHistory) R.string.manage_notifications_history_text
+ else R.string.manage_notifications_text
+ }
+
+ val manageOrHistoryButton: FooterButtonViewModel =
+ FooterButtonViewModel(
+ labelId = manageOrHistoryButtonText,
+ accessibilityDescriptionId = manageOrHistoryButtonText,
+ isVisible = flowOf(AnimatedValue.NotAnimating(true)),
+ )
+
val message: FooterMessageViewModel =
FooterMessageViewModel(
messageId = R.string.unlock_to_see_notif_text,
@@ -73,6 +95,7 @@
@SysUISingleton
fun provideOptional(
activeNotificationsInteractor: Provider<ActiveNotificationsInteractor>,
+ notificationSettingsInteractor: Provider<NotificationSettingsInteractor>,
seenNotificationsInteractor: Provider<SeenNotificationsInteractor>,
shadeInteractor: Provider<ShadeInteractor>,
): Optional<FooterViewModel> {
@@ -80,6 +103,7 @@
Optional.of(
FooterViewModel(
activeNotificationsInteractor.get(),
+ notificationSettingsInteractor.get(),
seenNotificationsInteractor.get(),
shadeInteractor.get()
)
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 b9afb14..5e0110b 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
@@ -4698,6 +4698,7 @@
* this will return false.
**/
public boolean isHistoryShown() {
+ FooterViewRefactor.assertInLegacyMode();
return mFooterView != null && mFooterView.isHistoryShown();
}
@@ -4710,10 +4711,10 @@
}
mFooterView = footerView;
addView(mFooterView, index);
- if (mManageButtonClickListener != null) {
- mFooterView.setManageButtonClickListener(mManageButtonClickListener);
- }
if (!FooterViewRefactor.isEnabled()) {
+ if (mManageButtonClickListener != null) {
+ mFooterView.setManageButtonClickListener(mManageButtonClickListener);
+ }
mFooterView.setClearAllButtonClickListener(v -> {
if (mFooterClearAllListener != null) {
mFooterClearAllListener.onClearAll();
@@ -4794,8 +4795,8 @@
}
boolean animate = mIsExpanded && mAnimationsEnabled;
mFooterView.setVisible(visible, animate);
- mFooterView.showHistory(showHistory);
if (!FooterViewRefactor.isEnabled()) {
+ mFooterView.showHistory(showHistory);
mFooterView.setClearAllButtonVisible(showDismissView, animate);
mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
}
@@ -5490,6 +5491,7 @@
* Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked.
*/
public void setManageButtonClickListener(@Nullable OnClickListener listener) {
+ FooterViewRefactor.assertInLegacyMode();
mManageButtonClickListener = listener;
if (mFooterView != null) {
mFooterView.setManageButtonClickListener(mManageButtonClickListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index ed26677..eaff8a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -22,7 +22,7 @@
import static com.android.app.animation.Interpolators.STANDARD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
@@ -845,11 +845,13 @@
mView.setKeyguardBypassEnabled(mKeyguardBypassController.getBypassEnabled());
mKeyguardBypassController
.registerOnBypassStateChangedListener(mView::setKeyguardBypassEnabled);
- mView.setManageButtonClickListener(v -> {
- if (mNotificationActivityStarter != null) {
- mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
- }
- });
+ if (!FooterViewRefactor.isEnabled()) {
+ mView.setManageButtonClickListener(v -> {
+ if (mNotificationActivityStarter != null) {
+ mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
+ }
+ });
+ }
mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 44a7e7e..4d65b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -29,6 +29,7 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
@@ -45,6 +46,7 @@
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -58,9 +60,11 @@
private val configuration: ConfigurationState,
private val falsingManager: FalsingManager,
private val iconAreaController: NotificationIconAreaController,
+ private val loggerOptional: Optional<NotificationStatsLogger>,
private val metricsLogger: MetricsLogger,
private val nicBinder: NotificationIconContainerShelfViewBinder,
- private val loggerOptional: Optional<NotificationStatsLogger>,
+ // Using a provider to avoid a circular dependency.
+ private val notificationActivityStarter: Provider<NotificationActivityStarter>,
private val viewModel: NotificationListViewModel,
) {
@@ -115,7 +119,7 @@
) { footerView: FooterView ->
traceSection("bind FooterView") {
val disposableHandle =
- FooterViewBinder.bind(
+ FooterViewBinder.bindWhileAttached(
footerView,
footerViewModel,
clearAllNotifications = {
@@ -124,6 +128,16 @@
)
parentView.clearAllNotifications()
},
+ launchNotificationSettings = { view ->
+ notificationActivityStarter
+ .get()
+ .startHistoryIntent(view, /* showHistory = */ false)
+ },
+ launchNotificationHistory = { view ->
+ notificationActivityStarter
+ .get()
+ .startHistoryIntent(view, /* showHistory = */ true)
+ },
)
parentView.setFooterView(footerView)
return@reinflateAndBindLatest disposableHandle
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index 3c4ca44..11e374f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -16,12 +16,13 @@
package com.android.systemui.statusbar.policy;
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.os.Trace;
+import android.service.notification.StatusBarNotification;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
@@ -46,8 +47,9 @@
public void onStart(MediaProjectionInfo info) {
Trace.beginSection(
"SNPC.onProjectionStart");
- mProjection = info;
- mListeners.forEach(Runnable::run);
+ // Only enable sensitive content protection if sharing full screen
+ // Launch cookie only set (non-null) if sharing single app/task
+ updateProjectionState((info.getLaunchCookie() == null) ? info : null);
Trace.endSection();
}
@@ -55,10 +57,22 @@
public void onStop(MediaProjectionInfo info) {
Trace.beginSection(
"SNPC.onProjectionStop");
- mProjection = null;
- mListeners.forEach(Runnable::run);
+ updateProjectionState(null);
Trace.endSection();
}
+
+ private void updateProjectionState(MediaProjectionInfo info) {
+ // capture previous state
+ boolean wasSensitive = isSensitiveStateActive();
+
+ // update internal state
+ mProjection = info;
+
+ // if either previous or new state is sensitive, notify listeners.
+ if (wasSensitive || isSensitiveStateActive()) {
+ mListeners.forEach(Runnable::run);
+ }
+ }
};
@Inject
@@ -86,7 +100,6 @@
public boolean isSensitiveStateActive() {
// TODO(b/316955558): Add disabled by developer option
// TODO(b/316955306): Add feature exemption for sysui and bug handlers
- // TODO(b/316955346): Add feature exemption for single app screen sharing
return mProjection != null;
}
@@ -96,9 +109,18 @@
return false;
}
+ MediaProjectionInfo projection = mProjection;
+ if (projection == null) {
+ return false;
+ }
+
// Exempt foreground service notifications from protection in effort to keep screen share
// stop actions easily accessible
- // TODO(b/316955208): Exempt FGS notifications only for app that started projection
- return !entry.getSbn().getNotification().isFgsOrUij();
+ StatusBarNotification sbn = entry.getSbn();
+ if (sbn.getNotification().isFgsOrUij()) {
+ return !sbn.getPackageName().equals(projection.getPackageName());
+ }
+
+ return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
new file mode 100644
index 0000000..ab6a37b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
@@ -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.systemui.util.kotlin
+
+import android.content.SharedPreferences
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.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 }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 8d5e55a..ff1daea 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.dagger
+import android.content.Context
import android.media.AudioManager
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
@@ -35,10 +36,12 @@
@Provides
fun provideAudioRepository(
+ @Application context: Context,
audioManager: AudioManager,
@Background coroutineContext: CoroutineContext,
@Application coroutineScope: CoroutineScope,
- ): AudioRepository = AudioRepositoryImpl(audioManager, coroutineContext, coroutineScope)
+ ): AudioRepository =
+ AudioRepositoryImpl(context, audioManager, coroutineContext, coroutineScope)
@Provides
fun provideAudioModeInteractor(repository: AudioRepository): AudioModeInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 8299acb..375ebe8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -51,6 +51,7 @@
import android.view.accessibility.IRemoteMagnificationAnimationCallback;
import android.view.animation.AccelerateInterpolator;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -79,6 +80,7 @@
@LargeTest
@RunWith(AndroidTestingRunner.class)
+@FlakyTest(bugId = 308501761)
public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Rule
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 6a9c881..2e94d38 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
@@ -81,6 +81,7 @@
private const val CHALLENGE = 2L
private const val DELAY = 1000L
private const val OP_PACKAGE_NAME = "biometric.testapp"
+private const val OP_PACKAGE_NAME_NO_ICON = "biometric.testapp.noicon"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -1246,6 +1247,14 @@
}
@Test
+ fun logoIsNullIfPackageNameNotFound() =
+ runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ val logo by collectLastValue(viewModel.logo)
+ assertThat(logo).isNull()
+ }
+
+ @Test
fun defaultLogoIfNoLogoSet() = runGenericTest {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val logo by collectLastValue(viewModel.logo)
@@ -1291,7 +1300,8 @@
contentView: PromptContentView? = null,
logoRes: Int = -1,
logoBitmap: Bitmap? = null,
- block: suspend TestScope.() -> Unit
+ packageName: String = OP_PACKAGE_NAME,
+ block: suspend TestScope.() -> Unit,
) {
selector.initializePrompt(
requireConfirmation = testCase.confirmationRequested,
@@ -1302,6 +1312,7 @@
contentViewFromApp = contentView,
logoResFromApp = logoRes,
logoBitmapFromApp = logoBitmap,
+ packageName = packageName,
)
// put the view model in the initial authenticating state, unless explicitly skipped
@@ -1481,6 +1492,7 @@
contentViewFromApp: PromptContentView? = null,
logoResFromApp: Int = -1,
logoBitmapFromApp: Bitmap? = null,
+ packageName: String = OP_PACKAGE_NAME,
) {
val info =
PromptInfo().apply {
@@ -1500,7 +1512,7 @@
USER_ID,
CHALLENGE,
BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
- OP_PACKAGE_NAME,
+ packageName,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index 9c5cd71..20dd913 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -77,6 +77,18 @@
}
@Test
+ fun deleteWidget_notInDb_returnsFalse() =
+ testScope.runTest {
+ val (widgetId, provider, priority) = widgetInfo1
+ communalWidgetDao.addWidget(
+ widgetId = widgetId,
+ provider = provider,
+ priority = priority,
+ )
+ assertThat(communalWidgetDao.deleteWidgetById(widgetId = 123)).isFalse()
+ }
+
+ @Test
fun addWidget_emitsActiveWidgetsInDb(): Unit =
testScope.runTest {
val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index c98d537..de455f63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -34,11 +34,12 @@
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dump.DumpManager
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+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
@@ -69,12 +70,13 @@
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import java.io.File
-import java.util.*
+import java.util.Optional
import java.util.function.Consumer
@SmallTest
@RunWith(AndroidTestingRunner::class)
class ControlsControllerImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
@Mock
private lateinit var uiController: ControlsUiController
@@ -109,8 +111,6 @@
private lateinit var listingCallbackCaptor:
ArgumentCaptor<ControlsListingController.ControlsListingCallback>
- private val preferredPanelRepository = FakeSelectedComponentRepository()
-
private lateinit var delayableExecutor: FakeExecutor
private lateinit var controller: ControlsControllerImpl
private lateinit var canceller: DidRunRunnable
@@ -171,7 +171,7 @@
wrapper,
delayableExecutor,
uiController,
- preferredPanelRepository,
+ kosmos.selectedComponentRepository,
bindingController,
listingController,
userFileManager,
@@ -225,7 +225,7 @@
mContext,
delayableExecutor,
uiController,
- preferredPanelRepository,
+ kosmos.selectedComponentRepository,
bindingController,
listingController,
userFileManager,
@@ -245,7 +245,7 @@
mContext,
delayableExecutor,
uiController,
- preferredPanelRepository,
+ kosmos.selectedComponentRepository,
bindingController,
listingController,
userFileManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
index 4828ba3..18ce4a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
@@ -18,36 +18,40 @@
package com.android.systemui.controls.panels
import android.content.SharedPreferences
+import android.content.pm.UserInfo
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserFileManager
-import com.android.systemui.settings.UserTracker
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.io.File
+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
@RunWith(AndroidTestingRunner::class)
@SmallTest
class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
- @Mock private lateinit var userTracker: UserTracker
+ private lateinit var userTracker: FakeUserTracker
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
mContext.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf<String>()
)
- whenever(userTracker.userId).thenReturn(0)
+ userTracker = kosmos.fakeUserTracker.apply { set(listOf(PRIMARY_USER, SECONDARY_USER), 0) }
}
@Test
@@ -91,7 +95,7 @@
val repository = createRepository(fileManager)
assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE)
- whenever(userTracker.userId).thenReturn(1)
+ userTracker.set(listOf(SECONDARY_USER), 0)
assertThat(repository.getAuthorizedPanels()).isEmpty()
}
@@ -127,6 +131,51 @@
assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
}
+ @Test
+ fun observeAuthorizedPanels() =
+ testScope.runTest {
+ val sharedPrefs = FakeSharedPreferences()
+ val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
+ val repository = createRepository(fileManager)
+
+ val authorizedPanels by
+ collectLastValue(repository.observeAuthorizedPanels(PRIMARY_USER.userHandle))
+ assertThat(authorizedPanels).isEmpty()
+
+ repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+ assertThat(authorizedPanels).containsExactly(TEST_PACKAGE)
+
+ repository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
+ assertThat(authorizedPanels).isEmpty()
+ }
+
+ @Test
+ fun observeAuthorizedPanelsForAnotherUser() =
+ testScope.runTest {
+ val fileManager =
+ FakeUserFileManager(
+ mapOf(
+ 0 to FakeSharedPreferences(),
+ 1 to FakeSharedPreferences(),
+ )
+ )
+ val repository = createRepository(fileManager)
+
+ val authorizedPanels by
+ collectLastValue(repository.observeAuthorizedPanels(SECONDARY_USER.userHandle))
+ assertThat(authorizedPanels).isEmpty()
+
+ // Primary user is active, add authorized panels.
+ repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+ assertThat(authorizedPanels).isEmpty()
+
+ // Make secondary user active and add authorized panels again.
+ userTracker.set(listOf(PRIMARY_USER, SECONDARY_USER), 1)
+ assertThat(authorizedPanels).isEmpty()
+ repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+ assertThat(authorizedPanels).containsExactly(TEST_PACKAGE)
+ }
+
private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
}
@@ -153,5 +202,9 @@
private const val FILE_NAME = "controls_prefs"
private const val KEY = "authorized_panels"
private const val TEST_PACKAGE = "package"
+ private val PRIMARY_USER =
+ UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
+ private val SECONDARY_USER =
+ UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
index b463adf..a7e7ba9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
@@ -23,8 +23,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.UserFileManager
@@ -74,7 +72,6 @@
@Mock private lateinit var userTracker: UserTracker
private lateinit var userFileManager: UserFileManager
- private val featureFlags = FakeFeatureFlags()
// under test
private lateinit var repository: SelectedComponentRepository
@@ -95,11 +92,9 @@
)
repository =
SelectedComponentRepositoryImpl(
- userFileManager,
- userTracker,
- featureFlags,
+ userFileManager = userFileManager,
+ userTracker = userTracker,
bgDispatcher = testDispatcher,
- applicationScope = applicationCoroutineScope
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
index bcef67e..94ea799 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -38,8 +38,8 @@
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
@@ -87,7 +87,7 @@
@Mock private lateinit var userManager: UserManager
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
- private lateinit var preferredPanelsRepository: FakeSelectedComponentRepository
+ private lateinit var preferredPanelsRepository: SelectedComponentRepository
private lateinit var fakeExecutor: FakeExecutor
@@ -99,7 +99,7 @@
whenever(userTracker.userHandle).thenReturn(UserHandle.of(1))
fakeExecutor = FakeExecutor(FakeSystemClock())
- preferredPanelsRepository = FakeSelectedComponentRepository()
+ preferredPanelsRepository = kosmos.selectedComponentRepository
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 36ae0c7..8f3813d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -43,8 +43,8 @@
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
@@ -53,6 +53,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSystemUIDialogController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -85,6 +86,8 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class ControlsUiControllerImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@Mock lateinit var controlsController: ControlsController
@Mock lateinit var controlsListingController: ControlsListingController
@Mock lateinit var controlActionCoordinator: ControlActionCoordinator
@@ -100,7 +103,7 @@
@Mock lateinit var packageManager: PackageManager
@Mock lateinit var systemUIDialogFactory: SystemUIDialog.Factory
- private val preferredPanelRepository = FakeSelectedComponentRepository()
+ private val preferredPanelRepository = kosmos.selectedComponentRepository
private lateinit var fakeDialogController: FakeSystemUIDialogController
private val uiExecutor = FakeExecutor(FakeSystemClock())
private val bgExecutor = FakeExecutor(FakeSystemClock())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
index 437a35f..e3c4c28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
@@ -475,10 +475,7 @@
// Then the device name is the PhoneMediaDevice string
val data = captureDeviceData(KEY)
- assertThat(data.name)
- .isEqualTo(
- context.getString(com.android.settingslib.R.string.media_transfer_this_device_name)
- )
+ assertThat(data.name).isEqualTo(PhoneMediaDevice.getMediaTransferThisDeviceName(context))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 239bf65..edba902 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.dream.MediaDreamComplication
@@ -52,8 +53,6 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertNotNull
@@ -106,8 +105,7 @@
private lateinit var dreamOverlayCallback:
ArgumentCaptor<(DreamOverlayStateController.Callback)>
@JvmField @Rule val mockito = MockitoJUnit.rule()
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private val testScope = kosmos.testScope
private lateinit var mediaHierarchyManager: MediaHierarchyManager
private lateinit var isQsBypassingShade: MutableStateFlow<Boolean>
private lateinit var mediaFrame: ViewGroup
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index db5bd9b..0d1e874 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -182,6 +182,8 @@
@Mock
private UiEventLogger mUiEventLogger;
@Mock
+ private NavBarButtonClickLogger mNavBarButtonClickLogger;
+ @Mock
private ViewTreeObserver mViewTreeObserver;
NavBarHelper mNavBarHelper;
@Mock
@@ -596,7 +598,8 @@
mUserContextProvider,
mWakefulnessLifecycle,
mTaskStackChangeListeners,
- new FakeDisplayTracker(mContext)));
+ new FakeDisplayTracker(mContext),
+ mNavBarButtonClickLogger));
}
private void processAllMessages() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index a6e240b..0831971 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -44,6 +44,7 @@
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.BeforeClass
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -51,6 +52,7 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@Ignore("b/323053208")
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 697b05a..c226121 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -28,8 +28,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
@@ -94,7 +92,6 @@
lateinit var underTest: NotificationsQSContainerController
- private lateinit var featureFlags: FakeFeatureFlags
private lateinit var navigationModeCallback: ModeChangedListener
private lateinit var taskbarVisibilityCallback: OverviewProxyListener
private lateinit var windowInsetsCallback: Consumer<WindowInsets>
@@ -106,7 +103,6 @@
MockitoAnnotations.initMocks(this)
fakeSystemClock = FakeSystemClock()
delayableExecutor = FakeExecutor(fakeSystemClock)
- featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, false) }
mContext.ensureTestableResources()
whenever(view.context).thenReturn(mContext)
whenever(view.resources).thenReturn(mContext.resources)
@@ -123,7 +119,6 @@
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
@@ -536,7 +531,6 @@
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index e66251a..c326350 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -28,8 +28,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
@@ -91,7 +89,6 @@
lateinit var underTest: NotificationsQSContainerController
- private lateinit var featureFlags: FakeFeatureFlags
private lateinit var navigationModeCallback: ModeChangedListener
private lateinit var taskbarVisibilityCallback: OverviewProxyListener
private lateinit var windowInsetsCallback: Consumer<WindowInsets>
@@ -104,7 +101,6 @@
fakeSystemClock = FakeSystemClock()
delayableExecutor = FakeExecutor(fakeSystemClock)
mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME)
- featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, true) }
mContext.ensureTestableResources()
whenever(view.context).thenReturn(mContext)
whenever(view.resources).thenReturn(mContext.resources)
@@ -122,7 +118,6 @@
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
@@ -513,7 +508,6 @@
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
index 50349be..0dd988d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -82,4 +82,22 @@
underTest.setShowNotificationsOnLockscreenEnabled(false)
assertThat(showNotifs).isEqualTo(false)
}
+
+ @Test
+ fun testGetIsNotificationHistoryEnabled() =
+ testScope.runTest {
+ val historyEnabled by collectLastValue(underTest.isNotificationHistoryEnabled)
+
+ secureSettingsRepository.setInt(
+ name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+ value = 1,
+ )
+ assertThat(historyEnabled).isEqualTo(true)
+
+ secureSettingsRepository.setInt(
+ name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+ value = 0,
+ )
+ assertThat(historyEnabled).isEqualTo(false)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 350ed2d..7d99d05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -21,7 +21,7 @@
import android.service.notification.StatusBarNotification
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
+import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index 57dac3a..cac4a8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -17,10 +17,13 @@
package com.android.systemui.statusbar.notification.footer.ui.view;
import static com.android.systemui.log.LogAssertKt.assertLogsWtf;
+
import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -95,6 +98,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void setHistoryShown() {
mView.showHistory(true);
assertTrue(mView.isHistoryShown());
@@ -103,6 +107,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void setHistoryNotShown() {
mView.showHistory(false);
assertFalse(mView.isHistoryShown());
@@ -128,6 +133,62 @@
@Test
@EnableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonText_resourceOnlyFetchedOnce() {
+ int resId = R.string.manage_notifications_history_text;
+ mView.setManageOrHistoryButtonText(resId);
+ verify(mSpyContext).getString(eq(resId));
+
+ clearInvocations(mSpyContext);
+
+ assertThat(((TextView) mView.findViewById(R.id.manage_text))
+ .getText().toString()).contains("History");
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setManageOrHistoryButtonText(resId);
+ mView.setManageOrHistoryButtonText(resId);
+
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonText_expectsFlagEnabled() {
+ clearInvocations(mSpyContext);
+ int resId = R.string.manage_notifications_history_text;
+ assertLogsWtf(() -> mView.setManageOrHistoryButtonText(resId));
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @EnableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonDescription_resourceOnlyFetchedOnce() {
+ int resId = R.string.manage_notifications_history_text;
+ mView.setManageOrHistoryButtonDescription(resId);
+ verify(mSpyContext).getString(eq(resId));
+
+ clearInvocations(mSpyContext);
+
+ assertThat(((TextView) mView.findViewById(R.id.manage_text))
+ .getContentDescription().toString()).contains("History");
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setManageOrHistoryButtonDescription(resId);
+ mView.setManageOrHistoryButtonDescription(resId);
+
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonDescription_expectsFlagEnabled() {
+ clearInvocations(mSpyContext);
+ int resId = R.string.accessibility_clear_all;
+ assertLogsWtf(() -> mView.setManageOrHistoryButtonDescription(resId));
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @EnableFlags(FooterViewRefactor.FLAG_NAME)
public void testSetClearAllButtonText_resourceOnlyFetchedOnce() {
int resId = R.string.clear_all_notifications_text;
mView.setClearAllButtonText(resId);
@@ -150,7 +211,7 @@
public void testSetClearAllButtonText_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.clear_all_notifications_text;
- assertLogsWtf(()-> mView.setClearAllButtonText(resId));
+ assertLogsWtf(() -> mView.setClearAllButtonText(resId));
verify(mSpyContext, never()).getString(anyInt());
}
@@ -178,7 +239,7 @@
public void testSetClearAllButtonDescription_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.accessibility_clear_all;
- assertLogsWtf(()-> mView.setClearAllButtonDescription(resId));
+ assertLogsWtf(() -> mView.setClearAllButtonDescription(resId));
verify(mSpyContext, never()).getString(anyInt());
}
@@ -206,7 +267,7 @@
public void testSetMessageString_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.unlock_to_see_notif_text;
- assertLogsWtf(()-> mView.setMessageString(resId));
+ assertLogsWtf(() -> mView.setMessageString(resId));
verify(mSpyContext, never()).getString(anyInt());
}
@@ -231,7 +292,7 @@
public void testSetMessageIcon_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.drawable.ic_friction_lock_closed;
- assertLogsWtf(()-> mView.setMessageIcon(resId));
+ assertLogsWtf(() -> mView.setMessageIcon(resId));
verify(mSpyContext, never()).getDrawable(anyInt());
}
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 8ab13f5..620d972 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
@@ -14,109 +14,61 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.statusbar.notification.footer.ui.viewmodel
import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
import android.testing.AndroidTestingRunner
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.collectLastValue
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.powerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
import com.android.systemui.statusbar.notification.collection.render.NotifStats
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.testKosmos
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
-import java.util.Optional
-import org.junit.Before
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
@EnableFlags(FooterViewRefactor.FLAG_NAME)
class FooterViewModelTest : SysuiTestCase() {
- private lateinit var footerViewModel: FooterViewModel
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- ActivatableNotificationViewModelModule::class,
- FooterViewModelModule::class,
- HeadlessSystemUserModeModule::class,
- ]
- )
- interface TestComponent : SysUITestComponent<Optional<FooterViewModel>> {
- val activeNotificationListRepository: ActiveNotificationListRepository
- val configurationRepository: FakeConfigurationRepository
- val keyguardRepository: FakeKeyguardRepository
- val keyguardTransitionRepository: FakeKeyguardTransitionRepository
- val shadeRepository: FakeShadeRepository
- val powerRepository: FakePowerRepository
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- featureFlags: FakeFeatureFlagsClassicModule,
- mocks: TestMocksModule,
- ): TestComponent
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
- }
+ private val testScope = kosmos.testScope
+ private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+ private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+ private val shadeRepository = kosmos.shadeRepository
+ private val powerRepository = kosmos.powerRepository
+ private val fakeSecureSettingsRepository = kosmos.fakeSecureSettingsRepository
- private val dozeParameters: DozeParameters = mock()
-
- private val testComponent: TestComponent =
- DaggerFooterViewModelTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
- },
- mocks =
- TestMocksModule(
- dozeParameters = dozeParameters,
- )
- )
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- // The underTest in the component is Optional, because that matches the provider we
- // currently have for the footer view model.
- footerViewModel = testComponent.underTest.get()
- }
+ val underTest = kosmos.footerViewModel
@Test
fun testMessageVisible_whenFilteredNotifications() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.message.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.message.isVisible)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
@@ -125,8 +77,8 @@
@Test
fun testMessageVisible_whenNoFilteredNotifications() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.message.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.message.isVisible)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
@@ -135,8 +87,8 @@
@Test
fun testClearAllButtonVisible_whenHasClearableNotifs() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
activeNotificationListRepository.notifStats.value =
NotifStats(
@@ -153,8 +105,8 @@
@Test
fun testClearAllButtonVisible_whenHasNoClearableNotifs() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
activeNotificationListRepository.notifStats.value =
NotifStats(
@@ -171,12 +123,12 @@
@Test
fun testClearAllButtonAnimating_whenShadeExpandedAndTouchable() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
runCurrent()
// WHEN shade is expanded
- keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
shadeRepository.setLegacyShadeExpansion(1f)
// AND QS not expanded
shadeRepository.setQsExpansion(0f)
@@ -205,12 +157,12 @@
@Test
fun testClearAllButtonAnimating_whenShadeNotExpanded() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
runCurrent()
// WHEN shade is collapsed
- keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
shadeRepository.setLegacyShadeExpansion(0f)
// AND QS not expanded
shadeRepository.setQsExpansion(0f)
@@ -236,4 +188,30 @@
// THEN button visibility should not animate
assertThat(visible?.isAnimating).isFalse()
}
+
+ @Test
+ fun testManageButton_whenHistoryDisabled() =
+ testScope.runTest {
+ val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
+ runCurrent()
+
+ // WHEN notification history is disabled
+ fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0)
+
+ // THEN label is "Manage"
+ assertThat(buttonLabel).isEqualTo(R.string.manage_notifications_text)
+ }
+
+ @Test
+ fun testHistoryButton_whenHistoryEnabled() =
+ testScope.runTest {
+ val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
+ runCurrent()
+
+ // WHEN notification history is disabled
+ fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 1)
+
+ // THEN label is "History"
+ assertThat(buttonLabel).isEqualTo(R.string.manage_notifications_history_text)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index dbe63f2..7589a49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.stack;
-import static com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
+import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 4188c5d..88e4f5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -23,36 +23,27 @@
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
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.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.notification.dagger.NotificationStatsLoggerModule
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepository
-import com.android.systemui.unfold.UnfoldTransitionModule
-import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.statusbar.policy.fakeConfigurationController
+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
@@ -62,46 +53,18 @@
@RunWith(AndroidJUnit4::class)
@EnableFlags(FooterViewRefactor.FLAG_NAME)
class NotificationListViewModelTest : SysuiTestCase() {
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- ActivatableNotificationViewModelModule::class,
- FooterViewModelModule::class,
- HeadlessSystemUserModeModule::class,
- UnfoldTransitionModule.Bindings::class,
- NotificationStatsLoggerModule::class,
- ]
- )
- interface TestComponent : SysUITestComponent<NotificationListViewModel> {
- val activeNotificationListRepository: ActiveNotificationListRepository
- val keyguardTransitionRepository: FakeKeyguardTransitionRepository
- val shadeRepository: FakeShadeRepository
- val zenModeRepository: FakeZenModeRepository
- val configurationController: FakeConfigurationController
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- featureFlags: FakeFeatureFlagsClassicModule,
- mocks: TestMocksModule,
- ): TestComponent
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
- }
+ private val testScope = kosmos.testScope
+ private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+ private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val fakeShadeRepository = kosmos.fakeShadeRepository
+ private val zenModeRepository = kosmos.zenModeRepository
+ private val fakeConfigurationController = kosmos.fakeConfigurationController
- private val testComponent: TestComponent =
- DaggerNotificationListViewModelTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
- },
- mocks = TestMocksModule()
- )
+ val underTest = kosmos.notificationListViewModel
@Before
fun setUp() {
@@ -110,11 +73,11 @@
@Test
fun testIsImportantForAccessibility_falseWhenNoNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val important by collectLastValue(underTest.isImportantForAccessibility)
// WHEN on lockscreen
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
testScope,
@@ -129,11 +92,11 @@
@Test
fun testIsImportantForAccessibility_trueWhenNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val important by collectLastValue(underTest.isImportantForAccessibility)
// WHEN on lockscreen
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
testScope,
@@ -148,11 +111,11 @@
@Test
fun testIsImportantForAccessibility_trueWhenNotKeyguard() =
- testComponent.runTest {
+ testScope.runTest {
val important by collectLastValue(underTest.isImportantForAccessibility)
// WHEN not on lockscreen
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
testScope,
@@ -167,7 +130,7 @@
@Test
fun testShouldShowEmptyShadeView_trueWhenNoNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
@@ -180,7 +143,7 @@
@Test
fun testShouldShowEmptyShadeView_falseWhenNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has notifs
@@ -193,13 +156,13 @@
@Test
fun testShouldShowEmptyShadeView_falseWhenQsExpandedDefault() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND quick settings are expanded
- shadeRepository.legacyQsFullscreen.value = true
+ fakeShadeRepository.legacyQsFullscreen.value = true
runCurrent()
// THEN should not show
@@ -208,16 +171,16 @@
@Test
fun testShouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND quick settings are expanded
- shadeRepository.setQsExpansion(1f)
+ fakeShadeRepository.setQsExpansion(1f)
// AND split shade is enabled
overrideResource(R.bool.config_use_split_notification_shade, true)
- configurationController.notifyConfigurationChanged()
+ fakeConfigurationController.notifyConfigurationChanged()
runCurrent()
// THEN should show
@@ -226,13 +189,13 @@
@Test
fun testShouldShowEmptyShadeView_falseWhenTransitioningToAOD() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND transitioning to AOD
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN,
@@ -248,13 +211,13 @@
@Test
fun testShouldShowEmptyShadeView_falseWhenBouncerShowing() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND is on bouncer
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.PRIMARY_BOUNCER,
testScope,
@@ -267,7 +230,7 @@
@Test
fun testAreNotificationsHiddenInShade_true() =
- testComponent.runTest {
+ testScope.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
@@ -279,7 +242,7 @@
@Test
fun testAreNotificationsHiddenInShade_false() =
- testComponent.runTest {
+ testScope.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
@@ -291,7 +254,7 @@
@Test
fun testHasFilteredOutSeenNotifications_true() =
- testComponent.runTest {
+ testScope.runTest {
val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
@@ -302,7 +265,7 @@
@Test
fun testHasFilteredOutSeenNotifications_false() =
- testComponent.runTest {
+ testScope.runTest {
val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index cd5d5ed..9919c6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy
+import android.app.ActivityOptions
import android.app.Notification
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
@@ -23,7 +24,7 @@
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
+import com.android.server.notification.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import org.junit.Assert.assertFalse
@@ -69,6 +70,8 @@
MockitoAnnotations.initMocks(this)
mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ setShareFullScreen()
+
controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
// Obtain useful MediaProjectionCallback
@@ -195,6 +198,14 @@
}
@Test
+ fun isSensitiveStateActive_projectionActive_singleActivity_false() {
+ setShareSingleApp()
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ assertFalse(controller.isSensitiveStateActive)
+ }
+
+ @Test
fun shouldProtectNotification_projectionInactive_false() {
val notificationEntry = mock(NotificationEntry::class.java)
@@ -202,30 +213,74 @@
}
@Test
- fun shouldProtectNotification_projectionActive_fgsNotification_false() {
+ fun shouldProtectNotification_projectionActive_singleActivity_false() {
+ setShareSingleApp()
mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- val notificationEntry = mock(NotificationEntry::class.java)
- val sbn = mock(StatusBarNotification::class.java)
- val notification = mock(Notification::class.java)
- `when`(notificationEntry.sbn).thenReturn(sbn)
- `when`(sbn.notification).thenReturn(notification)
- `when`(notification.isFgsOrUij).thenReturn(true)
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
assertFalse(controller.shouldProtectNotification(notificationEntry))
}
@Test
+ fun shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_false() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupFgsNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
+ fun shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_true() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupFgsNotificationEntry(TEST_PACKAGE_NAME)
+
+ assertTrue(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
+
+ assertTrue(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ private fun setShareFullScreen() {
+ `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+ `when`(mediaProjectionInfo.launchCookie).thenReturn(null)
+ }
+
+ private fun setShareSingleApp() {
+ `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+ `when`(mediaProjectionInfo.launchCookie).thenReturn(ActivityOptions.LaunchCookie())
+ }
+
+ private fun setupNotificationEntry(
+ packageName: String,
+ isFgs: Boolean = false
+ ): NotificationEntry {
val notificationEntry = mock(NotificationEntry::class.java)
val sbn = mock(StatusBarNotification::class.java)
val notification = mock(Notification::class.java)
`when`(notificationEntry.sbn).thenReturn(sbn)
+ `when`(sbn.packageName).thenReturn(packageName)
`when`(sbn.notification).thenReturn(notification)
- `when`(notification.isFgsOrUij).thenReturn(false)
+ `when`(notification.isFgsOrUij).thenReturn(isFgs)
- assertTrue(controller.shouldProtectNotification(notificationEntry))
+ return notificationEntry
+ }
+
+ private fun setupFgsNotificationEntry(packageName: String): NotificationEntry {
+ return setupNotificationEntry(packageName, /* isFgs= */ true)
+ }
+
+ companion object {
+ private const val TEST_PROJECTION_PACKAGE_NAME =
+ "com.android.systemui.statusbar.policy.projectionpackage"
+ private const val TEST_PACKAGE_NAME = "com.android.systemui.statusbar.policy.testpackage"
}
}
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 fab64e3..7301404 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
@@ -36,7 +36,7 @@
}
}
- override fun deleteWidget(widgetId: Int) {
+ override fun deleteWidgetFromDb(widgetId: Int) {
if (_communalWidgets.value.none { it.appWidgetId == widgetId }) {
return
}
@@ -44,6 +44,10 @@
_communalWidgets.value = _communalWidgets.value.filter { it.appWidgetId != widgetId }
}
+ override fun deleteWidgetFromHost(widgetId: Int) {
+ deleteWidgetFromDb(widgetId)
+ }
+
private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
_communalWidgets.value += listOf(CommunalWidgetContentModel(id, providerInfo, priority))
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index c818e9c..c47f020 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -24,12 +24,15 @@
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.smartspace.data.repository.smartspaceRepository
import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.mockito.mock
val Kosmos.communalInteractor by Fixture {
CommunalInteractor(
+ applicationScope = applicationCoroutineScope,
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
mediaRepository = communalMediaRepository,
@@ -39,6 +42,8 @@
appWidgetHost = mock(),
keyguardInteractor = keyguardInteractor,
editWidgetsActivityStarter = editWidgetsActivityStarter,
+ logBuffer = logcatLogBuffer("CommunalInteractor"),
+ tableLogBuffer = mock(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
index adaea7c..9776b43 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.util.mockito.mock
val Kosmos.communalTutorialInteractor by
Kosmos.Fixture {
@@ -30,5 +31,6 @@
keyguardInteractor = keyguardInteractor,
communalRepository = communalRepository,
communalInteractor = communalInteractor,
+ tableLogBuffer = mock(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.kt
new file mode 100644
index 0000000..109e113
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.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.controls.panels
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.fakeUserFileManager
+import com.android.systemui.settings.fakeUserTracker
+
+var Kosmos.authorizedPanelsRepository: AuthorizedPanelsRepository by
+ Kosmos.Fixture {
+ AuthorizedPanelsRepositoryImpl(applicationContext, fakeUserFileManager, fakeUserTracker)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
deleted file mode 100644
index a231212..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.controls.panels
-
-import android.os.UserHandle
-import com.android.systemui.kosmos.Kosmos
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class FakeSelectedComponentRepository : SelectedComponentRepository {
- private var shouldAddDefaultPanel: Boolean = true
- private val _selectedComponentFlows =
- mutableMapOf<UserHandle, MutableStateFlow<SelectedComponentRepository.SelectedComponent?>>()
- private var currentUserHandle: UserHandle = UserHandle.of(0)
-
- override fun selectedComponentFlow(
- userHandle: UserHandle
- ): Flow<SelectedComponentRepository.SelectedComponent?> {
- // Return an existing flow for the user or create a new one
- return _selectedComponentFlows.getOrPut(getUserHandle(userHandle)) {
- MutableStateFlow(null)
- }
- }
-
- override fun getSelectedComponent(
- userHandle: UserHandle
- ): SelectedComponentRepository.SelectedComponent? {
- return _selectedComponentFlows[getUserHandle(userHandle)]?.value
- }
-
- override fun setSelectedComponent(
- selectedComponent: SelectedComponentRepository.SelectedComponent
- ) {
- val flow = _selectedComponentFlows.getOrPut(currentUserHandle) { MutableStateFlow(null) }
- flow.value = selectedComponent
- }
-
- override fun removeSelectedComponent() {
- _selectedComponentFlows[currentUserHandle]?.value = null
- }
- override fun shouldAddDefaultComponent(): Boolean = shouldAddDefaultPanel
-
- override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
- shouldAddDefaultPanel = shouldAdd
- }
-
- fun setCurrentUserHandle(userHandle: UserHandle) {
- currentUserHandle = userHandle
- }
- private fun getUserHandle(userHandle: UserHandle): UserHandle {
- return if (userHandle == UserHandle.CURRENT) {
- currentUserHandle
- } else {
- userHandle
- }
- }
-}
-
-val Kosmos.selectedComponentRepository by
- Kosmos.Fixture<FakeSelectedComponentRepository> { FakeSelectedComponentRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.kt
new file mode 100644
index 0000000..ee5b7e5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.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.controls.panels
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.settings.fakeUserFileManager
+import com.android.systemui.settings.fakeUserTracker
+
+var Kosmos.selectedComponentRepository: SelectedComponentRepository by
+ Kosmos.Fixture {
+ SelectedComponentRepositoryImpl(fakeUserFileManager, fakeUserTracker, testDispatcher)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.kt
new file mode 100644
index 0000000..207c3f7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.settings
+
+import android.content.SharedPreferences
+import com.android.systemui.util.FakeSharedPreferences
+import java.io.File
+
+class FakeUserFileManager : UserFileManager {
+ private val sharedPreferences = mutableMapOf<SharedPrefKey, FakeSharedPreferences>()
+
+ override fun getFile(fileName: String, userId: Int): File {
+ throw UnsupportedOperationException("getFile not implemented in fake")
+ }
+
+ override fun getSharedPreferences(fileName: String, mode: Int, userId: Int): SharedPreferences {
+ val key = SharedPrefKey(fileName, mode, userId)
+ return sharedPreferences.getOrPut(key) { FakeSharedPreferences() }
+ }
+
+ private data class SharedPrefKey(val fileName: String, val mode: Int, val userId: Int)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.kt
new file mode 100644
index 0000000..4d7a40a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.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.settings
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeUserFileManager by Kosmos.Fixture { FakeUserFileManager() }
+var Kosmos.userFileManager: UserFileManager by Kosmos.Fixture { fakeUserFileManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
index ff22ca0..01cac4c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
@@ -19,12 +19,14 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shared.notifications.domain.interactor.notificationSettingsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
val Kosmos.footerViewModel by Fixture {
FooterViewModel(
activeNotificationsInteractor = activeNotificationsInteractor,
+ notificationSettingsInteractor = notificationSettingsInteractor,
seenNotificationsInteractor = seenNotificationsInteractor,
shadeInteractor = shadeInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index 748d04d..489598c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -23,6 +23,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.notificationIconContainerShelfViewBinder
+import com.android.systemui.statusbar.notification.notificationActivityStarter
import com.android.systemui.statusbar.notification.stack.ui.view.notificationStatsLogger
import com.android.systemui.statusbar.notification.stack.displaySwitchNotificationsHiderTracker
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
@@ -36,9 +37,10 @@
configuration = configurationState,
falsingManager = falsingManager,
iconAreaController = notificationIconAreaController,
+ loggerOptional = Optional.of(notificationStatsLogger),
metricsLogger = metricsLogger,
hiderTracker = displaySwitchNotificationsHiderTracker,
nicBinder = notificationIconContainerShelfViewBinder,
- loggerOptional = Optional.of(notificationStatsLogger),
+ notificationActivityStarter = { notificationActivityStarter },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
index 33ed7e6..d4e9bfb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.policy
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
-val Kosmos.configurationController by Kosmos.Fixture { mock<ConfigurationController>() }
+var Kosmos.configurationController: ConfigurationController by
+ Kosmos.Fixture { fakeConfigurationController }
val Kosmos.fakeConfigurationController: FakeConfigurationController by
Kosmos.Fixture { FakeConfigurationController() }
diff --git a/ravenwood/README-ravenwood+mockito.md b/ravenwood/README-ravenwood+mockito.md
deleted file mode 100644
index 6adb6144..0000000
--- a/ravenwood/README-ravenwood+mockito.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Ravenwood and Mockito
-
-Last update: 2023-11-13
-
-- As of 2023-11-13, `external/mockito` is based on version 2.x.
-- Mockito didn't support static mocking before 3.4.0.
- See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48
-
-- Latest Mockito is 5.*. According to https://github.com/mockito/mockito:
- `Mockito 3 does not introduce any breaking API changes, but now requires Java 8 over Java 6 for Mockito 2. Mockito 4 removes deprecated API. Mockito 5 switches the default mockmaker to mockito-inline, and now requires Java 11.`
-
-- Mockito now supports Android natively.
- See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.1
- - But it's unclear at this point to omakoto@ how the `mockito-android` module is built.
-
-- Potential plan:
- - Ideal option:
- - If we can update `external/mockito`, that'd be great, but it may not work because
- Mockito has removed the deprecated APIs.
- - Second option:
- - Import the latest mockito as `external/mockito-new`, and require ravenwood
- to use this one.
- - The latest mockito needs be exposed to all of 1) device tests, 2) host tests, and 3) ravenwood tests.
- - This probably will require the latest `bytebuddy` and `objenesis`.
\ No newline at end of file
diff --git a/ravenwood/list-ravenwood-tests.sh b/ravenwood/list-ravenwood-tests.sh
new file mode 100755
index 0000000..fb9b823
--- /dev/null
+++ b/ravenwood/list-ravenwood-tests.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+# 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.
+
+# List all the ravenwood test modules.
+
+jq -r 'to_entries[] | select( .value.compatibility_suites | index("ravenwood-tests") ) | .key' "$OUT/module-info.json"
diff --git a/ravenwood/mockito/Android.bp b/ravenwood/mockito/Android.bp
index 4135022..a74bca4 100644
--- a/ravenwood/mockito/Android.bp
+++ b/ravenwood/mockito/Android.bp
@@ -7,16 +7,6 @@
default_applicable_licenses: ["frameworks_base_license"],
}
-// Ravenwood tests run on the hostside, so we need mockit of the host variant.
-// But we need to use it in modules of the android variant, so we "wash" the variant with it.
-java_host_for_device {
- name: "mockito_ravenwood",
- libs: [
- "mockito",
- "objenesis",
- ],
-}
-
android_ravenwood_test {
name: "RavenwoodMockitoTest",
@@ -26,8 +16,6 @@
static_libs: [
"junit",
"truth",
-
- "mockito_ravenwood",
],
libs: [
"android.test.mock",
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
index 364a86a..1284d64 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.Intent;
+import android.os.Parcel;
import android.platform.test.ravenwood.RavenwoodRule;
import org.junit.Rule;
@@ -78,4 +79,13 @@
assertThat(object.getPackageName()).isEqualTo("android");
}
+
+ @Test
+ public void testMockFinalClass() {
+ var object = mock(Parcel.class);
+
+ when(object.readInt()).thenReturn(123);
+
+ assertThat(object.readInt()).isEqualTo(123);
+ }
}
diff --git a/ravenwood/run-ravenwood-tests.sh b/ravenwood/run-ravenwood-tests.sh
new file mode 100755
index 0000000..3f4b8a7
--- /dev/null
+++ b/ravenwood/run-ravenwood-tests.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# 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.
+
+# Run all the ravenwood tests.
+
+# "echo" is to remove the newlines
+all_tests=$(echo $(${0%/*}/list-ravenwood-tests.sh) )
+
+echo "Running tests: $all_tests"
+atest $all_tests
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 63784ba..44144f8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -5725,10 +5725,10 @@
}
@Override
- public void attachAccessibilityOverlayToDisplay_enforcePermission(
+ public void attachAccessibilityOverlayToDisplay(
int displayId, SurfaceControl sc) {
mContext.enforceCallingPermission(
- INTERNAL_SYSTEM_WINDOW, "attachAccessibilityOverlayToDisplay_enforcePermission");
+ INTERNAL_SYSTEM_WINDOW, "attachAccessibilityOverlayToDisplay");
mMainHandler.sendMessage(
obtainMessage(
AccessibilityManagerService::attachAccessibilityOverlayToDisplayInternal,
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 727721d..532db12 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -12,4 +12,11 @@
namespace: "autofill"
description: "Guards against Autofill-Credman integration phase 2"
bug: "320730001"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "autofill_credman_dev_integration"
+ namespace: "autofill"
+ description: "Guards against Autofill-Credman Phase1 developer integration via new APIs"
+ bug: "320730001"
+}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index cd8be33..82d9377 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -17,6 +17,7 @@
package com.android.server;
import static android.app.Flags.modesApi;
+import static android.app.Flags.enableNightModeCache;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
@@ -135,7 +136,23 @@
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
- private int mNightMode = UiModeManager.MODE_NIGHT_NO;
+
+ private final NightMode mNightMode = new NightMode(){
+ private int mNightModeValue = UiModeManager.MODE_NIGHT_NO;
+
+ @Override
+ public int get() {
+ return mNightModeValue;
+ }
+
+ @Override
+ public void set(int mode) {
+ mNightModeValue = mode;
+ if (enableNightModeCache()) {
+ UiModeManager.invalidateNightModeCache();
+ }
+ }
+ };
private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
private int mAttentionModeThemeOverlay = UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0);
@@ -297,7 +314,7 @@
@Override
public void onTwilightStateChanged(@Nullable TwilightState state) {
synchronized (mLock) {
- if (mNightMode == UiModeManager.MODE_NIGHT_AUTO && mSystemReady) {
+ if (mNightMode.get() == UiModeManager.MODE_NIGHT_AUTO && mSystemReady) {
if (shouldApplyAutomaticChangesImmediately()) {
updateLocked(0, 0);
} else {
@@ -392,7 +409,7 @@
private void updateSystemProperties() {
int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
- mNightMode, 0);
+ mNightMode.get(), 0);
if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) {
mode = MODE_NIGHT_YES;
}
@@ -412,7 +429,7 @@
@Override
public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
mCurrentUser = to.getUserIdentifier();
- if (mNightMode == MODE_NIGHT_AUTO) persistComputedNightMode(from.getUserIdentifier());
+ if (mNightMode.get() == MODE_NIGHT_AUTO) persistComputedNightMode(from.getUserIdentifier());
getContext().getContentResolver().unregisterContentObserver(mSetupWizardObserver);
verifySetupWizardCompleted();
synchronized (mLock) {
@@ -471,12 +488,12 @@
verifySetupWizardCompleted();
final Resources res = context.getResources();
+ mNightMode.set(res.getInteger(
+ com.android.internal.R.integer.config_defaultNightMode));
mStartDreamImmediatelyOnDock = res.getBoolean(
com.android.internal.R.bool.config_startDreamImmediatelyOnDock);
mDreamsDisabledByAmbientModeSuppression = res.getBoolean(
com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
- mNightMode = res.getInteger(
- com.android.internal.R.integer.config_defaultNightMode);
mDefaultUiModeType = res.getInteger(
com.android.internal.R.integer.config_defaultUiModeType);
mCarModeKeepsScreenOn = (res.getInteger(
@@ -510,7 +527,7 @@
private final BroadcastReceiver mOnShutdown = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (mNightMode == MODE_NIGHT_AUTO) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO) {
persistComputedNightMode(mCurrentUser);
}
}
@@ -585,7 +602,7 @@
}
private void updateCustomTimeLocked() {
- if (mNightMode != MODE_NIGHT_CUSTOM) return;
+ if (mNightMode.get() != MODE_NIGHT_CUSTOM) return;
if (shouldApplyAutomaticChangesImmediately()) {
updateLocked(0, 0);
} else {
@@ -606,9 +623,9 @@
return;
}
if (mSetupWizardComplete) {
- mNightMode = Secure.getIntForUser(context.getContentResolver(),
+ mNightMode.set(Secure.getIntForUser(context.getContentResolver(),
Secure.UI_NIGHT_MODE, res.getInteger(
- com.android.internal.R.integer.config_defaultNightMode), userId);
+ com.android.internal.R.integer.config_defaultNightMode), userId));
mNightModeCustomType = Secure.getIntForUser(context.getContentResolver(),
Secure.UI_NIGHT_MODE_CUSTOM_TYPE, MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, userId);
mOverrideNightModeOn = Secure.getIntForUser(context.getContentResolver(),
@@ -623,7 +640,7 @@
Secure.getLongForUser(context.getContentResolver(),
Secure.DARK_THEME_CUSTOM_END_TIME,
DEFAULT_CUSTOM_NIGHT_END_TIME.toNanoOfDay() / 1000L, userId) * 1000);
- if (mNightMode == MODE_NIGHT_AUTO) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO) {
mComputedNightMode = Secure.getIntForUser(context.getContentResolver(),
Secure.UI_NIGHT_MODE_LAST_COMPUTED, 0, userId) != 0;
}
@@ -834,21 +851,23 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- if (mNightMode != mode || mNightModeCustomType != customModeType) {
- if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
+ if (mNightMode.get() != mode || mNightModeCustomType != customModeType) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO
+ || mNightMode.get() == MODE_NIGHT_CUSTOM) {
unregisterDeviceInactiveListenerLocked();
cancelCustomAlarm();
}
mNightModeCustomType = mode == MODE_NIGHT_CUSTOM
? customModeType
: MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
- mNightMode = mode;
+ mNightMode.set(mode);
//deactivates AttentionMode if user toggles DarkTheme
mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
resetNightModeOverrideLocked();
persistNightMode(user);
// on screen off will update configuration instead
- if ((mNightMode != MODE_NIGHT_AUTO && mNightMode != MODE_NIGHT_CUSTOM)
+ if ((mNightMode.get() != MODE_NIGHT_AUTO
+ && mNightMode.get() != MODE_NIGHT_CUSTOM)
|| shouldApplyAutomaticChangesImmediately()) {
unregisterDeviceInactiveListenerLocked();
updateLocked(0, 0);
@@ -865,7 +884,7 @@
@Override
public int getNightMode() {
synchronized (mLock) {
- return mNightMode;
+ return mNightMode.get();
}
}
@@ -999,18 +1018,19 @@
synchronized (mLock) {
final long ident = Binder.clearCallingIdentity();
try {
- if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO
+ || mNightMode.get() == MODE_NIGHT_CUSTOM) {
unregisterDeviceInactiveListenerLocked();
mOverrideNightModeOff = !active;
mOverrideNightModeOn = active;
mOverrideNightModeUser = mCurrentUser;
persistNightModeOverrides(mCurrentUser);
- } else if (mNightMode == UiModeManager.MODE_NIGHT_NO
+ } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_NO
&& active) {
- mNightMode = UiModeManager.MODE_NIGHT_YES;
- } else if (mNightMode == UiModeManager.MODE_NIGHT_YES
+ mNightMode.set(UiModeManager.MODE_NIGHT_YES);
+ } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_YES
&& !active) {
- mNightMode = UiModeManager.MODE_NIGHT_NO;
+ mNightMode.set(UiModeManager.MODE_NIGHT_NO);
}
updateConfigurationLocked();
applyConfigurationExternallyLocked();
@@ -1414,7 +1434,7 @@
private void onCustomTimeUpdated(int user) {
persistNightMode(user);
- if (mNightMode != MODE_NIGHT_CUSTOM) return;
+ if (mNightMode.get() != MODE_NIGHT_CUSTOM) return;
if (shouldApplyAutomaticChangesImmediately()) {
unregisterDeviceInactiveListenerLocked();
updateLocked(0, 0);
@@ -1431,8 +1451,8 @@
pw.print(" mStartDreamImmediatelyOnDock="); pw.print(mStartDreamImmediatelyOnDock);
- pw.print(" mNightMode="); pw.print(mNightMode); pw.print(" (");
- pw.print(Shell.nightModeToStr(mNightMode, mNightModeCustomType)); pw.print(") ");
+ pw.print(" mNightMode="); pw.print(mNightMode.get()); pw.print(" (");
+ pw.print(Shell.nightModeToStr(mNightMode.get(), mNightModeCustomType)); pw.print(") ");
pw.print(" mOverrideOn/Off="); pw.print(mOverrideNightModeOn);
pw.print("/"); pw.print(mOverrideNightModeOff);
pw.print(" mAttentionModeThemeOverlay="); pw.print(mAttentionModeThemeOverlay);
@@ -1623,7 +1643,7 @@
// Only persist setting if not in car mode
if (mCarModeEnabled || mCar) return;
Secure.putIntForUser(getContext().getContentResolver(),
- Secure.UI_NIGHT_MODE, mNightMode, user);
+ Secure.UI_NIGHT_MODE, mNightMode.get(), user);
Secure.putLongForUser(getContext().getContentResolver(),
Secure.UI_NIGHT_MODE_CUSTOM_TYPE, mNightModeCustomType, user);
Secure.putLongForUser(getContext().getContentResolver(),
@@ -1659,11 +1679,11 @@
uiMode = Configuration.UI_MODE_TYPE_VR_HEADSET;
}
- if (mNightMode == MODE_NIGHT_YES || mNightMode == UiModeManager.MODE_NIGHT_NO) {
- updateComputedNightModeLocked(mNightMode == MODE_NIGHT_YES);
+ if (mNightMode.get() == MODE_NIGHT_YES || mNightMode.get() == UiModeManager.MODE_NIGHT_NO) {
+ updateComputedNightModeLocked(mNightMode.get() == MODE_NIGHT_YES);
}
- if (mNightMode == MODE_NIGHT_AUTO) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO) {
boolean activateNightMode = mComputedNightMode;
if (mTwilightManager != null) {
mTwilightManager.registerListener(mTwilightListener, mHandler);
@@ -1677,7 +1697,7 @@
}
}
- if (mNightMode == MODE_NIGHT_CUSTOM) {
+ if (mNightMode.get() == MODE_NIGHT_CUSTOM) {
if (mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) {
updateComputedNightModeLocked(mLastBedtimeRequestedNightMode);
} else {
@@ -2010,7 +2030,7 @@
private void updateComputedNightModeLocked(boolean activate) {
boolean newComputedValue = activate;
- if (mNightMode != MODE_NIGHT_YES && mNightMode != UiModeManager.MODE_NIGHT_NO) {
+ if (mNightMode.get() != MODE_NIGHT_YES && mNightMode.get() != UiModeManager.MODE_NIGHT_NO) {
if (mOverrideNightModeOn && !newComputedValue) {
newComputedValue = true;
} else if (mOverrideNightModeOff && newComputedValue) {
@@ -2029,7 +2049,7 @@
mComputedNightMode = newComputedValue;
}
- if (mNightMode != MODE_NIGHT_AUTO || (mTwilightManager != null
+ if (mNightMode.get() != MODE_NIGHT_AUTO || (mTwilightManager != null
&& mTwilightManager.getLastTwilightState() != null)) {
resetNightModeOverrideLocked();
}
@@ -2279,4 +2299,13 @@
Sandman.startDreamWhenDockedIfAppropriate(context);
}
}
+
+ /**
+ * Interface to contain the value for system night mode. We make the night mode accessible
+ * through this class to ensure that the reassignment of this value invalidates the cache.
+ */
+ private interface NightMode {
+ int get();
+ void set(int mode);
+ }
}
diff --git a/services/core/java/com/android/server/am/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java
index 98129ed..856a15f 100644
--- a/services/core/java/com/android/server/am/AssistDataRequester.java
+++ b/services/core/java/com/android/server/am/AssistDataRequester.java
@@ -222,7 +222,7 @@
// Ensure that the current activity supports assist data
boolean isAssistDataAllowed = false;
try {
- isAssistDataAllowed = mActivityTaskManager.isAssistDataAllowedOnCurrentActivity();
+ isAssistDataAllowed = mActivityTaskManager.isAssistDataAllowed();
} catch (RemoteException e) {
// Should never happen
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index cd295b5..f1eea72 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2820,6 +2820,10 @@
return mDeviceInventory.isBluetoothAudioDeviceCategoryFixed(address);
}
+ /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
+ return mAudioService.isSADevice(deviceState);
+ }
+
//------------------------------------------------
// for testing purposes only
void clearDeviceInventory() {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 690c37a..102a960 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -307,9 +307,12 @@
&& di.mPeerDeviceAddress.equals(ads2.getDeviceAddress()))) {
continue;
}
- ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
- ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
- ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ if (mDeviceBroker.isSADevice(updatedDevice)
+ == mDeviceBroker.isSADevice(ads2)) {
+ ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+ ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+ ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ }
ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
mDeviceBroker.postUpdatedAdiDeviceState(ads2);
@@ -325,9 +328,12 @@
&& di.mDeviceAddress.equals(ads2.getDeviceAddress()))) {
continue;
}
- ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
- ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
- ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ if (mDeviceBroker.isSADevice(updatedDevice)
+ == mDeviceBroker.isSADevice(ads2)) {
+ ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+ ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+ ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ }
ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
mDeviceBroker.postUpdatedAdiDeviceState(ads2);
@@ -348,10 +354,11 @@
|| !updatedDevice.getDeviceAddress().equals(ads.getDeviceAddress())) {
continue;
}
-
- ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
- ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
- ads.setSAEnabled(updatedDevice.isSAEnabled());
+ if (mDeviceBroker.isSADevice(updatedDevice) == mDeviceBroker.isSADevice(ads)) {
+ ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
+ ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+ ads.setSAEnabled(updatedDevice.isSAEnabled());
+ }
ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
mDeviceBroker.postUpdatedAdiDeviceState(ads);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9f7c07e..bbbba26 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7855,7 +7855,7 @@
sDeviceLogger.enqueue(new EventLogger.StringEvent("BlutoothActiveDeviceChanged for "
+ BluetoothProfile.getProfileName(profile) + ", device update " + previousDevice
- + " -> " + newDevice));
+ + " -> " + newDevice).printLog(TAG));
AudioDeviceBroker.BtDeviceChangedData data =
new AudioDeviceBroker.BtDeviceChangedData(newDevice, previousDevice, info,
"AudioService");
@@ -10638,6 +10638,10 @@
mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
}
+ /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
+ return mSpatializerHelper.isSADevice(deviceState);
+ }
+
private boolean isBluetoothPrividged() {
if (!bluetoothMacAddressAnonymization()) {
return true;
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 8428f12..8d76731 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -813,7 +813,7 @@
return false;
}
- private boolean isSADevice(AdiDeviceState deviceState) {
+ /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
return deviceState.getDeviceType() == getCanonicalDeviceType(deviceState.getDeviceType(),
deviceState.getInternalDeviceType()) && isDeviceCompatibleWithSpatializationModes(
deviceState.getAudioDeviceAttributes());
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 93addcd..e930627 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -872,14 +872,15 @@
}
@VisibleForTesting
- void performTraversalInternal(SurfaceControl.Transaction t) {
+ void performTraversalInternal(SurfaceControl.Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions) {
synchronized (mSyncRoot) {
if (!mPendingTraversal) {
return;
}
mPendingTraversal = false;
- performTraversalLocked(t);
+ performTraversalLocked(t, displayTransactions);
}
// List is self-synchronized copy-on-write.
@@ -2593,7 +2594,8 @@
}
}
- private void performTraversalLocked(SurfaceControl.Transaction t) {
+ private void performTraversalLocked(SurfaceControl.Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions) {
// Clear all viewports before configuring displays so that we can keep
// track of which ones we have configured.
clearViewportsLocked();
@@ -2601,9 +2603,11 @@
// Configure each display device.
mLogicalDisplayMapper.forEachLocked((LogicalDisplay display) -> {
final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+ final SurfaceControl.Transaction displayTransaction =
+ displayTransactions.get(display.getDisplayIdLocked(), t);
if (device != null) {
- configureDisplayLocked(t, device);
- device.performTraversalLocked(t);
+ configureDisplayLocked(displayTransaction, device);
+ device.performTraversalLocked(displayTransaction);
}
});
@@ -4680,8 +4684,9 @@
}
@Override
- public void performTraversal(SurfaceControl.Transaction t) {
- performTraversalInternal(t);
+ public void performTraversal(SurfaceControl.Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions) {
+ performTraversalInternal(t, displayTransactions);
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index fff4939..cb00e44 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3185,6 +3185,24 @@
}
}
}
+
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ String ime = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
+ String defaultDeviceIme = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+ if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
+ + " device input method for user " + mSettings.getUserId()
+ + " - restoring " + defaultDeviceIme);
+ }
+ SecureSettingsWrapper.putString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
+ mSettings.getUserId());
+ }
+ }
+
// We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
@@ -5370,7 +5388,11 @@
if (!setSubtypeOnly) {
// Set InputMethod here
- mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
+ final String imeId = imi != null ? imi.getId() : "";
+ mSettings.putSelectedInputMethod(imeId);
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ mSettings.putSelectedDefaultDeviceInputMethod(imeId);
+ }
}
}
@@ -5513,6 +5535,9 @@
return false; // IME is not found or not enabled.
}
settings.putSelectedInputMethod(imeId);
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ settings.putSelectedDefaultDeviceInputMethod(imeId);
+ }
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
return true;
}
@@ -6559,6 +6584,9 @@
// Reset selected IME.
settings.putSelectedInputMethod(nextIme);
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ settings.putSelectedDefaultDeviceInputMethod(nextIme);
+ }
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
}
out.println("Reset current and enabled IMEs for user #" + userId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index a51002b..e444db1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -36,7 +36,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import java.util.function.Predicate;
/**
@@ -88,12 +87,6 @@
mMethodMap = methodMap;
mMethodList = methodMap.values();
mUserId = userId;
- String ime = getSelectedInputMethod();
- String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
- if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
- putSelectedInputMethod(defaultDeviceIme);
- putSelectedDefaultDeviceInputMethod(null);
- }
}
@AnyThread
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4da2cc9..1c9bbab 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2049,7 +2049,9 @@
if (!mUserProfiles.isProfileUser(userId)) {
mConditionProviders.onUserUnlocked(userId);
mListeners.onUserUnlocked(userId);
- mZenModeHelper.onUserUnlocked(userId);
+ if (!android.app.Flags.modesApi()) {
+ mZenModeHelper.onUserUnlocked(userId);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 41ff415..2f20bbe 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -328,6 +328,7 @@
}
}
+ // TODO: b/310620812 - Remove when MODES_API is inlined (no more callers).
public void onUserUnlocked(int user) {
loadConfigForUser(user, "onUserUnlocked");
}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index dbff442..722654a 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -43,6 +43,13 @@
}
flag {
+ name: "screenshare_notification_hiding"
+ namespace: "systemui"
+ description: "Enable hiding of notifications during screenshare"
+ bug: "312784809"
+}
+
+flag {
name: "sensitive_notification_app_protection"
namespace: "systemui"
description: "This flag controls the sensitive notification app protections while screen sharing"
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 86d05d9..25a39cc 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -257,7 +257,7 @@
private boolean matchesActorSignature(@NonNull AndroidPackage targetPackage,
@NonNull AndroidPackage overlayPackage, int userId) {
String targetOverlayableName = overlayPackage.getOverlayTargetOverlayableName();
- if (targetOverlayableName != null && !mPackageManager.getNamedActors().isEmpty()) {
+ if (targetOverlayableName != null) {
try {
OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget(
targetPackage.getPackageName(), targetOverlayableName, userId);
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index a61b03f..b9464d9 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -32,7 +32,6 @@
import static android.os.Trace.TRACE_TAG_RRO;
import static android.os.Trace.traceBegin;
import static android.os.Trace.traceEnd;
-
import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException;
import android.annotation.NonNull;
@@ -363,7 +362,7 @@
defaultPackages.add(packageName);
}
}
- return defaultPackages.toArray(new String[0]);
+ return defaultPackages.toArray(new String[defaultPackages.size()]);
}
private final class OverlayManagerPackageMonitor extends PackageMonitor {
@@ -1144,10 +1143,9 @@
};
private static final class PackageManagerHelperImpl implements PackageManagerHelper {
- private static final class PackageStateUsers {
+ private static class PackageStateUsers {
private PackageState mPackageState;
- private Boolean mDefinesOverlayable = null;
- private final ArraySet<Integer> mInstalledUsers = new ArraySet<>();
+ private final Set<Integer> mInstalledUsers = new ArraySet<>();
private PackageStateUsers(@NonNull PackageState packageState) {
this.mPackageState = packageState;
}
@@ -1162,7 +1160,7 @@
// state may lead to contradictions within OMS. Better then to lag
// behind until all pending intents have been processed.
private final ArrayMap<String, PackageStateUsers> mCache = new ArrayMap<>();
- private final ArraySet<Integer> mInitializedUsers = new ArraySet<>();
+ private final Set<Integer> mInitializedUsers = new ArraySet<>();
PackageManagerHelperImpl(Context context) {
mContext = context;
@@ -1178,7 +1176,8 @@
*/
@NonNull
public ArrayMap<String, PackageState> initializeForUser(final int userId) {
- if (mInitializedUsers.add(userId)) {
+ if (!mInitializedUsers.contains(userId)) {
+ mInitializedUsers.add(userId);
mPackageManagerInternal.forEachPackageState((packageState -> {
if (packageState.getPkg() != null
&& packageState.getUserStateOrDefault(userId).isInstalled()) {
@@ -1197,11 +1196,13 @@
return userPackages;
}
- private PackageStateUsers getRawPackageStateForUser(@NonNull final String packageName,
+ @Override
+ @Nullable
+ public PackageState getPackageStateForUser(@NonNull final String packageName,
final int userId) {
final PackageStateUsers pkg = mCache.get(packageName);
if (pkg != null && pkg.mInstalledUsers.contains(userId)) {
- return pkg;
+ return pkg.mPackageState;
}
try {
if (!mPackageManager.isPackageAvailable(packageName, userId)) {
@@ -1215,14 +1216,8 @@
return addPackageUser(packageName, userId);
}
- @Override
- public PackageState getPackageStateForUser(@NonNull final String packageName,
- final int userId) {
- final PackageStateUsers pkg = getRawPackageStateForUser(packageName, userId);
- return pkg != null ? pkg.mPackageState : null;
- }
-
- private PackageStateUsers addPackageUser(@NonNull final String packageName,
+ @NonNull
+ private PackageState addPackageUser(@NonNull final String packageName,
final int user) {
final PackageState pkg = mPackageManagerInternal.getPackageStateInternal(packageName);
if (pkg == null) {
@@ -1234,20 +1229,20 @@
}
@NonNull
- private PackageStateUsers addPackageUser(@NonNull final PackageState pkg,
+ private PackageState addPackageUser(@NonNull final PackageState pkg,
final int user) {
PackageStateUsers pkgUsers = mCache.get(pkg.getPackageName());
if (pkgUsers == null) {
pkgUsers = new PackageStateUsers(pkg);
mCache.put(pkg.getPackageName(), pkgUsers);
- } else if (pkgUsers.mPackageState != pkg) {
+ } else {
pkgUsers.mPackageState = pkg;
- pkgUsers.mDefinesOverlayable = null;
}
pkgUsers.mInstalledUsers.add(user);
- return pkgUsers;
+ return pkgUsers.mPackageState;
}
+
@NonNull
private void removePackageUser(@NonNull final String packageName, final int user) {
final PackageStateUsers pkgUsers = mCache.get(packageName);
@@ -1265,15 +1260,15 @@
}
}
+ @Nullable
public PackageState onPackageAdded(@NonNull final String packageName, final int userId) {
- final var pu = addPackageUser(packageName, userId);
- return pu != null ? pu.mPackageState : null;
+ return addPackageUser(packageName, userId);
}
+ @Nullable
public PackageState onPackageUpdated(@NonNull final String packageName,
final int userId) {
- final var pu = addPackageUser(packageName, userId);
- return pu != null ? pu.mPackageState : null;
+ return addPackageUser(packageName, userId);
}
public void onPackageRemoved(@NonNull final String packageName, final int userId) {
@@ -1313,30 +1308,22 @@
return (pkgs.length == 0) ? null : pkgs[0];
}
+ @Nullable
@Override
public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
@NonNull String targetOverlayableName, int userId)
throws IOException {
- final var psu = getRawPackageStateForUser(packageName, userId);
- final var pkg = (psu == null || psu.mPackageState == null)
- ? null : psu.mPackageState.getAndroidPackage();
+ var packageState = getPackageStateForUser(packageName, userId);
+ var pkg = packageState == null ? null : packageState.getAndroidPackage();
if (pkg == null) {
throw new IOException("Unable to get target package");
}
- if (Boolean.FALSE.equals(psu.mDefinesOverlayable)) {
- return null;
- }
-
ApkAssets apkAssets = null;
try {
apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(),
ApkAssets.PROPERTY_ONLY_OVERLAYABLES);
- if (psu.mDefinesOverlayable == null) {
- psu.mDefinesOverlayable = apkAssets.definesOverlayable();
- }
- return Boolean.FALSE.equals(psu.mDefinesOverlayable)
- ? null : apkAssets.getOverlayableInfo(targetOverlayableName);
+ return apkAssets.getOverlayableInfo(targetOverlayableName);
} finally {
if (apkAssets != null) {
try {
@@ -1350,29 +1337,24 @@
@Override
public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
throws IOException {
- final var psu = getRawPackageStateForUser(targetPackageName, userId);
- var pkg = (psu == null || psu.mPackageState == null)
- ? null : psu.mPackageState.getAndroidPackage();
+ var packageState = getPackageStateForUser(targetPackageName, userId);
+ var pkg = packageState == null ? null : packageState.getAndroidPackage();
if (pkg == null) {
throw new IOException("Unable to get target package");
}
- if (psu.mDefinesOverlayable == null) {
- ApkAssets apkAssets = null;
- try {
- apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(),
- ApkAssets.PROPERTY_ONLY_OVERLAYABLES);
- psu.mDefinesOverlayable = apkAssets.definesOverlayable();
- } finally {
- if (apkAssets != null) {
- try {
- apkAssets.close();
- } catch (Throwable ignored) {
- }
+ ApkAssets apkAssets = null;
+ try {
+ apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath());
+ return apkAssets.definesOverlayable();
+ } finally {
+ if (apkAssets != null) {
+ try {
+ apkAssets.close();
+ } catch (Throwable ignored) {
}
}
}
- return psu.mDefinesOverlayable;
}
@Override
@@ -1563,7 +1545,8 @@
final OverlayPaths frameworkOverlays =
mImpl.getEnabledOverlayPaths("android", userId, false);
for (final String targetPackageName : targetPackageNames) {
- final var list = new OverlayPaths.Builder(frameworkOverlays);
+ final OverlayPaths.Builder list = new OverlayPaths.Builder();
+ list.addAll(frameworkOverlays);
if (!"android".equals(targetPackageName)) {
list.addAll(mImpl.getEnabledOverlayPaths(targetPackageName, userId, true));
}
@@ -1575,21 +1558,17 @@
final HashSet<String> invalidPackages = new HashSet<>();
pm.setEnabledOverlayPackages(userId, pendingChanges, updatedPackages, invalidPackages);
- if (DEBUG || !invalidPackages.isEmpty()) {
- for (final String targetPackageName : targetPackageNames) {
- if (DEBUG) {
- Slog.d(TAG,
- "-> Updating overlay: target=" + targetPackageName + " overlays=["
- + pendingChanges.get(targetPackageName)
- + "] userId=" + userId);
- }
+ for (final String targetPackageName : targetPackageNames) {
+ if (DEBUG) {
+ Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=["
+ + pendingChanges.get(targetPackageName)
+ + "] userId=" + userId);
+ }
- if (invalidPackages.contains(targetPackageName)) {
- Slog.e(TAG, TextUtils.formatSimple(
- "Failed to change enabled overlays for %s user %d",
- targetPackageName,
- userId));
- }
+ if (invalidPackages.contains(targetPackageName)) {
+ Slog.e(TAG, TextUtils.formatSimple(
+ "Failed to change enabled overlays for %s user %d", targetPackageName,
+ userId));
}
}
return new ArrayList<>(updatedPackages);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index c1b6ccc..972c78d 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -772,20 +772,24 @@
OverlayPaths getEnabledOverlayPaths(@NonNull final String targetPackageName,
final int userId, boolean includeImmutableOverlays) {
- final var paths = new OverlayPaths.Builder();
- mSettings.forEachMatching(userId, null, targetPackageName, oi -> {
+ final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName,
+ userId);
+ final OverlayPaths.Builder paths = new OverlayPaths.Builder();
+ final int n = overlays.size();
+ for (int i = 0; i < n; i++) {
+ final OverlayInfo oi = overlays.get(i);
if (!oi.isEnabled()) {
- return;
+ continue;
}
if (!includeImmutableOverlays && !oi.isMutable) {
- return;
+ continue;
}
if (oi.isFabricated()) {
paths.addNonApkPath(oi.baseCodePath);
} else {
paths.addApkPath(oi.baseCodePath);
}
- });
+ }
return paths.build();
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index b8b49f3e..eae614a 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -47,7 +47,6 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
-import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -183,23 +182,6 @@
return CollectionUtils.map(items, SettingsItem::getOverlayInfo);
}
- void forEachMatching(int userId, String overlayName, String targetPackageName,
- @NonNull Consumer<OverlayInfo> consumer) {
- for (int i = 0, n = mItems.size(); i < n; i++) {
- final SettingsItem item = mItems.get(i);
- if (item.getUserId() != userId) {
- continue;
- }
- if (overlayName != null && !item.mOverlay.getPackageName().equals(overlayName)) {
- continue;
- }
- if (targetPackageName != null && !item.mTargetPackageName.equals(targetPackageName)) {
- continue;
- }
- consumer.accept(item.getOverlayInfo());
- }
- }
-
ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
final List<SettingsItem> items = selectWhereUser(userId);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 51790b8..b947aa3 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5016,6 +5016,8 @@
case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY:
case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: {
+ Slog.i(TAG, "Stylus buttons event: " + keyCode + " received. Should handle event? "
+ + mStylusButtonsEnabled);
if (mStylusButtonsEnabled) {
sendSystemKeyToStatusBarAsync(event);
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 8549957..37f3825 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -347,7 +347,7 @@
for (Rect crop : relativeCropHints) {
Rect originalRect = new Rect(crop);
originalRect.scale(wallpaper.mSampleSize);
- originalRect.offset(wallpaper.cropHint.left, wallpaper.cropHint.right);
+ originalRect.offset(wallpaper.cropHint.left, wallpaper.cropHint.top);
result.add(originalRect);
}
return result;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3397a3d..0def5a1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3505,8 +3505,9 @@
}
@Override
- public boolean isAssistDataAllowedOnCurrentActivity() {
+ public boolean isAssistDataAllowed() {
int userId;
+ boolean hasRestrictedWindow;
synchronized (mGlobalLock) {
final Task focusedRootTask = getTopDisplayFocusedRootTask();
if (focusedRootTask == null || focusedRootTask.isActivityTypeAssistant()) {
@@ -3518,8 +3519,17 @@
return false;
}
userId = activity.mUserId;
+ DisplayContent displayContent = activity.getDisplayContent();
+ if (displayContent == null) {
+ return false;
+ }
+ hasRestrictedWindow = displayContent.forAllWindows(windowState -> {
+ return windowState.isOnScreen() && UserManager.isUserTypePrivateProfile(
+ getUserManager().getProfileType(windowState.mShowUserId));
+ }, true /* traverseTopToBottom */);
}
- return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId);
+ return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId)
+ && !hasRestrictedWindow;
}
private void onLocalVoiceInteractionStartedLocked(IBinder activity,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 82dbf8d..716aee3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1429,14 +1429,6 @@
return mTokenMap.get(binder);
}
- ActivityRecord getActivityRecord(IBinder binder) {
- final WindowToken token = getWindowToken(binder);
- if (token == null) {
- return null;
- }
- return token.asActivityRecord();
- }
-
void addWindowToken(IBinder binder, WindowToken token) {
final DisplayContent dc = mWmService.mRoot.getWindowTokenDisplay(token);
if (dc != null) {
@@ -2250,7 +2242,6 @@
}
}
- mWmService.mDisplayManagerInternal.performTraversal(transaction);
if (shellTransitions) {
// Before setDisplayProjection is applied by the start transaction of transition,
// set the transform hint to avoid using surface in old rotation.
@@ -6985,7 +6976,7 @@
@Override
public void onAppTransitionFinishedLocked(IBinder token) {
- final ActivityRecord r = getActivityRecord(token);
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
// Ignore the animating recents so the fixed rotation transform won't be switched twice
// by finishing the recents animation and moving it to top. That also avoids flickering
// due to wait for previous activity to be paused if it supports PiP that ignores the
diff --git a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
index eae9951..d08f043 100644
--- a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
+++ b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.internal.perfetto.protos.PerfettoTrace;
import android.os.SystemClock;
+import android.os.Trace;
import android.tracing.perfetto.DataSourceParams;
import android.tracing.perfetto.InitArguments;
import android.tracing.perfetto.Producer;
@@ -57,6 +58,16 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logSentTransition");
+ try {
+ doLogSentTransition(transition, targets);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogSentTransition(
+ Transition transition, ArrayList<Transition.ChangeInfo> targets) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
@@ -91,6 +102,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logFinishedTransition");
+ try {
+ doLogFinishTransition(transition);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogFinishTransition(Transition transition) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
@@ -114,6 +134,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logAbortedTransition");
+ try {
+ doLogAbortedTransition(transition);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogAbortedTransition(Transition transition) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
@@ -131,6 +160,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logRemovingStartingWindow");
+ try {
+ doLogRemovingStartingWindow(startingData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ public void doLogRemovingStartingWindow(@NonNull StartingData startingData) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 587cc74..25646f1 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -249,6 +249,8 @@
/** Reference to default display so we can quickly look it up. */
private DisplayContent mDefaultDisplay;
private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
+ private final SparseArray<SurfaceControl.Transaction> mDisplayTransactions =
+ new SparseArray<>();
/** The current user */
int mCurrentUser;
@@ -569,22 +571,6 @@
}, true /* traverseTopToBottom */);
}
- /**
- * Returns the app window token for the input binder if it exist in the system.
- * NOTE: Only one AppWindowToken is allowed to exist in the system for a binder token, since
- * AppWindowToken represents an activity which can only exist on one display.
- */
- ActivityRecord getActivityRecord(IBinder binder) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final DisplayContent dc = mChildren.get(i);
- final ActivityRecord activity = dc.getActivityRecord(binder);
- if (activity != null) {
- return activity;
- }
- }
- return null;
- }
-
/** Returns the window token for the input binder if it exist in the system. */
WindowToken getWindowToken(IBinder binder) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
@@ -991,11 +977,13 @@
for (int j = 0; j < count; ++j) {
final DisplayContent dc = mChildren.get(j);
dc.applySurfaceChangesTransaction();
+ mDisplayTransactions.append(dc.mDisplayId, dc.getSyncTransaction());
}
// Give the display manager a chance to adjust properties like display rotation if it needs
// to.
- mWmService.mDisplayManagerInternal.performTraversal(t);
+ mWmService.mDisplayManagerInternal.performTraversal(t, mDisplayTransactions);
+ mDisplayTransactions.clear();
}
/**
diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
index bdb4588..967f415 100644
--- a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
+++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
@@ -36,8 +36,7 @@
import com.android.internal.protolog.common.ProtoLog;
import java.io.PrintWriter;
-import java.util.Map;
-import java.util.Set;
+import java.util.ArrayList;
public class ScreenRecordingCallbackController {
@@ -57,10 +56,10 @@
}
@GuardedBy("WindowManagerService.mGlobalLock")
- private final Map<IBinder, Callback> mCallbacks = new ArrayMap<>();
+ private final ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<>();
@GuardedBy("WindowManagerService.mGlobalLock")
- private final Map<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
+ private final ArrayMap<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
private final WindowManagerService mWms;
@@ -108,8 +107,8 @@
}
IBinder binder = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
- IMediaProjectionManager mediaProjectionManager =
- IMediaProjectionManager.Stub.asInterface(binder);
+ IMediaProjectionManager mediaProjectionManager = IMediaProjectionManager.Stub.asInterface(
+ binder);
long identityToken = Binder.clearCallingIdentity();
MediaProjectionInfo mediaProjectionInfo = null;
@@ -157,11 +156,14 @@
synchronized (mWms.mGlobalLock) {
IBinder binder = callback.asBinder();
Callback callbackInfo = mCallbacks.remove(binder);
+ if (callbackInfo == null) {
+ return;
+ }
binder.unlinkToDeath(callbackInfo, 0);
boolean uidHasCallback = false;
- for (Callback cb : mCallbacks.values()) {
- if (cb.mUid == callbackInfo.mUid) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ if (mCallbacks.valueAt(i).mUid == callbackInfo.mUid) {
uidHasCallback = true;
break;
}
@@ -213,7 +215,9 @@
return;
}
- dispatchCallbacks(Set.of(uid), processVisible);
+ ArraySet<Integer> uidSet = new ArraySet<>();
+ uidSet.add(uid);
+ dispatchCallbacks(uidSet, processVisible);
}
@GuardedBy("WindowManagerService.mGlobalLock")
@@ -233,8 +237,8 @@
}
@GuardedBy("WindowManagerService.mGlobalLock")
- private Set<Integer> getRecordedUids() {
- Set<Integer> result = new ArraySet<>();
+ private ArraySet<Integer> getRecordedUids() {
+ ArraySet<Integer> result = new ArraySet<>();
if (mRecordedWC == null) {
return result;
}
@@ -248,37 +252,43 @@
}
@GuardedBy("WindowManagerService.mGlobalLock")
- private void dispatchCallbacks(Set<Integer> uids, boolean visibleInScreenRecording) {
+ private void dispatchCallbacks(ArraySet<Integer> uids, boolean visibleInScreenRecording) {
if (uids.isEmpty()) {
return;
}
- for (Integer uid : uids) {
- mLastInvokedStateByUid.put(uid, visibleInScreenRecording);
+ for (int i = 0; i < uids.size(); i++) {
+ mLastInvokedStateByUid.put(uids.valueAt(i), visibleInScreenRecording);
}
- for (Callback callback : mCallbacks.values()) {
- if (!uids.contains(callback.mUid)) {
- continue;
- }
- try {
- callback.mCallback.onScreenRecordingStateChanged(visibleInScreenRecording);
- } catch (RemoteException e) {
- // Client has died. Cleanup is handled via DeathRecipient.
+ ArrayList<IScreenRecordingCallback> callbacks = new ArrayList<>();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ if (uids.contains(mCallbacks.valueAt(i).mUid)) {
+ callbacks.add(mCallbacks.valueAt(i).mCallback);
}
}
+
+ mWms.mH.post(() -> {
+ for (int i = 0; i < callbacks.size(); i++) {
+ try {
+ callbacks.get(i).onScreenRecordingStateChanged(visibleInScreenRecording);
+ } catch (RemoteException e) {
+ // Client has died. Cleanup is handled via DeathRecipient.
+ }
+ }
+ });
}
void dump(PrintWriter pw) {
pw.format("ScreenRecordingCallbackController:\n");
pw.format(" Registered callbacks:\n");
- for (Map.Entry<IBinder, Callback> entry : mCallbacks.entrySet()) {
- pw.format(" callback=%s uid=%s\n", entry.getKey(), entry.getValue().mUid);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ pw.format(" callback=%s uid=%s\n", mCallbacks.keyAt(i), mCallbacks.valueAt(i).mUid);
}
pw.format(" Last invoked states:\n");
- for (Map.Entry<Integer, Boolean> entry : mLastInvokedStateByUid.entrySet()) {
- pw.format(" uid=%s isVisibleInScreenRecording=%s\n", entry.getKey(),
- entry.getValue());
+ for (int i = 0; i < mLastInvokedStateByUid.size(); i++) {
+ pw.format(" uid=%s isVisibleInScreenRecording=%s\n", mLastInvokedStateByUid.keyAt(i),
+ mLastInvokedStateByUid.valueAt(i));
}
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8f9ed83..5b51776 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5070,7 +5070,7 @@
mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
if (waitingActivity != null) {
- mWmService.setWindowOpaqueLocked(waitingActivity.token, false);
+ waitingActivity.setMainWindowOpaque(false);
if (waitingActivity.attachedToProcess()) {
try {
waitingActivity.app.getThread().scheduleTranslucentConversionComplete(
diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
index f8edc2b..debe794 100644
--- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java
+++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
@@ -88,7 +88,17 @@
void addOverlay(SurfaceControlViewHost.SurfacePackage p, SurfaceControl currentParent) {
requireOverlaySurfaceControl();
- mOverlays.add(p);
+
+ boolean hasExistingOverlay = false;
+ for (int i = mOverlays.size() - 1; i >= 0; i--) {
+ SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i);
+ if (l.getSurfaceControl().isSameSurface(p.getSurfaceControl())) {
+ hasExistingOverlay = true;
+ }
+ }
+ if (!hasExistingOverlay) {
+ mOverlays.add(p);
+ }
SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
t.reparent(p.getSurfaceControl(), mSurfaceControl)
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 426694d..554cbce 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -158,6 +158,7 @@
import android.Manifest;
import android.Manifest.permission;
import android.animation.ValueAnimator;
+import android.annotation.EnforcePermission;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -1073,7 +1074,7 @@
@Override
public void onAppTransitionFinishedLocked(IBinder token) {
- final ActivityRecord atoken = mRoot.getActivityRecord(token);
+ final ActivityRecord atoken = ActivityRecord.forTokenLocked(token);
if (atoken == null) {
return;
}
@@ -3105,13 +3106,6 @@
return mRecentsAnimationController != null && mRecentsAnimationController.isTargetApp(r);
}
- void setWindowOpaqueLocked(IBinder token, boolean isOpaque) {
- final ActivityRecord wtoken = mRoot.getActivityRecord(token);
- if (wtoken != null) {
- wtoken.setMainWindowOpaque(isOpaque);
- }
- }
-
boolean isValidPictureInPictureAspectRatio(DisplayContent displayContent, float aspectRatio) {
return displayContent.getPinnedTaskController().isValidPictureInPictureAspectRatio(
aspectRatio);
@@ -3292,7 +3286,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.DISABLE_KEYGUARD)
+ @EnforcePermission(android.Manifest.permission.DISABLE_KEYGUARD)
/**
* @see android.app.KeyguardManager#exitKeyguardSecurely
*/
@@ -4520,7 +4514,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public SurfaceControl addShellRoot(int displayId, IWindow client,
@WindowManager.ShellRootLayer int shellRootLayer) {
@@ -4539,7 +4533,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public void setShellRootAccessibilityWindow(int displayId,
@WindowManager.ShellRootLayer int shellRootLayer, IWindow target) {
@@ -4562,7 +4556,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public void setDisplayWindowInsetsController(
int displayId, IDisplayWindowInsetsController insetsController) {
@@ -4581,7 +4575,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public void updateDisplayWindowRequestedVisibleTypes(
int displayId, @InsetsType int requestedVisibleTypes) {
@@ -5841,7 +5835,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void setForcedDisplaySize(int displayId, int width, int height) {
setForcedDisplaySize_enforcePermission();
@@ -5859,7 +5853,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void setForcedDisplayScalingMode(int displayId, int mode) {
setForcedDisplayScalingMode_enforcePermission();
@@ -5947,7 +5941,7 @@
return changed;
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void clearForcedDisplaySize(int displayId) {
clearForcedDisplaySize_enforcePermission();
@@ -6010,7 +6004,7 @@
return -1;
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void setForcedDisplayDensityForUser(int displayId, int density, int userId) {
setForcedDisplayDensityForUser_enforcePermission();
@@ -6036,7 +6030,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void clearForcedDisplayDensityForUser(int displayId, int userId) {
clearForcedDisplayDensityForUser_enforcePermission();
@@ -6536,7 +6530,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.STATUS_BAR)
+ @EnforcePermission(android.Manifest.permission.STATUS_BAR)
public void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled) {
setNavBarVirtualKeyHapticFeedbackEnabled_enforcePermission();
@@ -6578,7 +6572,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+ @EnforcePermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
@Override
public Region getCurrentImeTouchRegion() {
getCurrentImeTouchRegion_enforcePermission();
@@ -8468,12 +8462,13 @@
SurfaceControlViewHost.SurfacePackage overlay) {
if (overlay == null) {
throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
- } else if (overlay.getSurfaceControl() == null
- || !overlay.getSurfaceControl().isValid()) {
- throw new IllegalArgumentException(
- "Invalid overlay surfacecontrol passed in for task=" + taskId);
}
synchronized (mGlobalLock) {
+ if (overlay.getSurfaceControl() == null
+ || !overlay.getSurfaceControl().isValid()) {
+ throw new IllegalArgumentException(
+ "Invalid overlay surfacecontrol passed in for task=" + taskId);
+ }
final Task task = mRoot.getRootTask(taskId);
if (task == null) {
throw new IllegalArgumentException("no task with taskId" + taskId);
@@ -9956,13 +9951,17 @@
mTrustedPresentationListenerController.unregisterListener(listener, id);
}
+ @EnforcePermission(android.Manifest.permission.DETECT_SCREEN_RECORDING)
@Override
public boolean registerScreenRecordingCallback(IScreenRecordingCallback callback) {
+ registerScreenRecordingCallback_enforcePermission();
return mScreenRecordingCallbackController.register(callback);
}
+ @EnforcePermission(android.Manifest.permission.DETECT_SCREEN_RECORDING)
@Override
public void unregisterScreenRecordingCallback(IScreenRecordingCallback callback) {
+ unregisterScreenRecordingCallback_enforcePermission();
mScreenRecordingCallbackController.unregister(callback);
}
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 56423b9..897afbf 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -73,7 +73,6 @@
"androidx.annotation_annotation",
"androidx.test.rules",
"framework",
- "mockito_ravenwood",
"ravenwood-runtime",
"ravenwood-utils",
"services",
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 75febd9..42bcb33 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -103,6 +103,7 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.SparseArray;
import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.DisplayAdjustments;
@@ -432,7 +433,7 @@
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -507,7 +508,7 @@
assertTrue(expectedDisplayTypeToViewPortTypeMapping.keySet().contains(info.type));
}
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -559,7 +560,7 @@
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -594,7 +595,7 @@
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
@@ -632,7 +633,7 @@
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
@@ -667,7 +668,7 @@
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
@@ -947,7 +948,7 @@
PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -1439,7 +1440,7 @@
PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -1694,7 +1695,7 @@
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -1728,7 +1729,7 @@
null /* projection */, PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
assertNotNull(ddi);
@@ -1797,7 +1798,7 @@
mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
assertNotNull(ddi);
@@ -2912,6 +2913,11 @@
assertEquals(expectedMode, displayInfo.getMode());
}
+ private void performTraversalInternal(DisplayManagerService displayManager) {
+ displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class),
+ new SparseArray<>());
+ }
+
private int getDisplayIdForDisplayDevice(
DisplayManagerService displayManager,
DisplayManagerService.BinderService displayManagerBinderService,
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index f49f638..64fef68 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -81,7 +81,6 @@
"androidx.annotation_annotation",
"androidx.test.rules",
"truth",
- "mockito_ravenwood",
],
srcs: [
":power_stats_ravenwood_tests",
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 5611415..ad6e2c6 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -153,7 +153,6 @@
static_libs: [
"androidx.annotation_annotation",
"androidx.test.rules",
- "mockito_ravenwood",
"services.core",
],
srcs: [
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index 1c6d36b..ea84eb2 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -36,6 +36,7 @@
import android.os.IDumpstateListener;
import android.os.Process;
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;
@@ -48,6 +49,7 @@
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;
@@ -118,6 +120,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
public void testBugreportFileManagerFileExists() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
@@ -137,6 +140,7 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
+ @Ignore
public void testBugreportFileManagerKeepFilesOnRetrieval() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
@@ -150,6 +154,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
public void testBugreportFileManagerMultipleFiles() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 839cf7c..c247c08 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -82,6 +82,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IpcDataCache;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
@@ -174,6 +175,11 @@
@Before
+ public void disableProcessCaches() {
+ IpcDataCache.disableForTestMode();
+ }
+
+ @Before
public void setUp() {
// The AIDL stub will use PermissionEnforcer to check permission from the caller.
mPermissionEnforcer = new FakePermissionEnforcer();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 5d114f4..7462201 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -5202,7 +5202,7 @@
// rules for a missing package, created a long time ago and deleted a long time ago
config.deletedRules.put("del6", newZenRule("bad_pkg", twoMonthsAgo, twoMonthsAgo));
- mZenModeHelper.onUserUnlocked(42); // copies config and cleans it up.
+ mZenModeHelper.onUserSwitched(42); // copies config and cleans it up.
assertThat(mZenModeHelper.mConfig.automaticRules.keySet())
.containsExactly("ar1", "ar2", "ar3", "ar4");
diff --git a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
index 56c3ec0..c2a5824 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
@@ -146,7 +146,7 @@
private void setupMocks(boolean currentActivityAssistAllowed, boolean assistStructureAllowed,
boolean assistScreenshotAllowed) throws Exception {
- doReturn(currentActivityAssistAllowed).when(mAtm).isAssistDataAllowedOnCurrentActivity();
+ doReturn(currentActivityAssistAllowed).when(mAtm).isAssistDataAllowed();
doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
.noteOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString(), any(), any());
doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index 09f677e7..46e14d51 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -16,8 +16,6 @@
package com.android.server.wm;
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -38,13 +36,12 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
@@ -56,13 +53,8 @@
*/
@SmallTest
@Presubmit
-public class ClientLifecycleManagerTests {
-
- @Rule(order = 0)
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
-
- @Rule(order = 1)
- public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
+@RunWith(WindowTestRunner.class)
+public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
@Mock
private IBinder mClientBinder;
@@ -86,7 +78,7 @@
public void setup() {
MockitoAnnotations.initMocks(this);
- mWms = mSystemServices.getWindowManagerService();
+ mWms = mSystemServicesTestRule.getWindowManagerService();
mLifecycleManager = spy(new ClientLifecycleManager());
mLifecycleManager.setWindowManager(mWms);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index be83744..0ee78e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1802,8 +1802,7 @@
@Override
public void addStartingWindow(StartingWindowInfo info) {
synchronized (mWMService.mGlobalLock) {
- final ActivityRecord activity = mWMService.mRoot.getActivityRecord(
- info.appToken);
+ final ActivityRecord activity = ActivityRecord.forTokenLocked(info.appToken);
IWindow iWindow = mock(IWindow.class);
doReturn(mock(IBinder.class)).when(iWindow).asBinder();
final WindowState window = WindowTestsBase.createWindow(null,
@@ -1825,8 +1824,7 @@
final IBinder appToken = mTaskAppMap.get(removalInfo.taskId);
if (appToken != null) {
mTaskAppMap.remove(removalInfo.taskId);
- final ActivityRecord activity = mWMService.mRoot.getActivityRecord(
- appToken);
+ final ActivityRecord activity = ActivityRecord.forTokenLocked(appToken);
WindowState win = mAppWindowMap.remove(appToken);
activity.removeChild(win);
activity.mStartingWindow = null;
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 7ccc27e..0c324e6 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -649,12 +649,19 @@
}
/**
- * @return Reason for denial if the registration state is {@link #REGISTRATION_STATE_DENIED}.
- * Depending on {@code accessNetworkTechnology}, the values are defined in 3GPP TS 24.008
- * 10.5.3.6 for UMTS, 3GPP TS 24.301 9.9.3.9 for LTE, and 3GPP2 A.S0001 6.2.2.44 for CDMA
- * @hide
+ * Get the 3GPP/3GPP2 reason code indicating why registration failed.
+ *
+ * Returns the reason code for non-transient registration failures. Typically this method will
+ * only return the last reason code received during a network selection procedure. The reason
+ * code is system-specific; however, the reason codes for both 3GPP and 3GPP2 systems are
+ * largely equivalent across generations.
+ *
+ * @return registration reject cause if available, otherwise 0. Depending on
+ * {@link #getAccessNetworkTechnology}, the values are defined in 3GPP TS 24.008 10.5.3.6 for
+ * WCDMA/UMTS, 3GPP TS 24.301 9.9.3.9 for LTE/EPS, 3GPP 24.501 Annex A for NR/5GS, or 3GPP2
+ * A.S0001 6.2.2.44 for CDMA.
*/
- @SystemApi
+ @FlaggedApi(Flags.FLAG_NETWORK_REGISTRATION_INFO_REJECT_CAUSE)
public int getRejectCause() {
return mRejectCause;
}