Merge "Flag showing logo on bp" into main
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
index 3c361d7..95730e8 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
@@ -122,6 +122,8 @@
Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true)
.recycle();
}
+ source.recycle();
+ Runtime.getRuntime().gc();
}
@Test
@@ -141,6 +143,8 @@
Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true)
.recycle();
}
+ source.recycle();
+ Runtime.getRuntime().gc();
}
@Test
@@ -158,5 +162,7 @@
Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true)
.recycle();
}
+ source.recycle();
+ Runtime.getRuntime().gc();
}
}
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index de6f023..5d65d9d 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -1,6 +1,13 @@
package: "com.android.server.job"
flag {
+ name: "do_not_force_rush_execution_at_boot"
+ namespace: "backstage_power"
+ description: "Don't force rush job execution right after boot completion"
+ bug: "321598070"
+}
+
+flag {
name: "relax_prefetch_connectivity_constraint_only_on_charger"
namespace: "backstage_power"
description: "Only relax a prefetch job's connectivity constraint when the device is charging and battery is not low"
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index b0f378d..7a92cca 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -2720,8 +2720,10 @@
sc.maybeStartTrackingJobLocked(job, null);
}
});
- // GO GO GO!
- mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ if (!Flags.doNotForceRushExecutionAtBoot()) {
+ // GO GO GO!
+ mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ }
}
}
}
@@ -5441,6 +5443,8 @@
pw.println("Aconfig flags:");
pw.increaseIndent();
+ pw.print(Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT,
+ Flags.doNotForceRushExecutionAtBoot());
pw.print(Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE,
Flags.throwOnUnsupportedBiasUsage());
pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index c14efae..6f2393a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -344,11 +344,14 @@
final String flagName = getNextArgRequired();
switch (flagName) {
+ case android.app.job.Flags.FLAG_ENFORCE_MINIMUM_TIME_WINDOWS:
+ pw.println(android.app.job.Flags.enforceMinimumTimeWindows());
+ break;
case android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS:
pw.println(android.app.job.Flags.jobDebugInfoApis());
break;
- case android.app.job.Flags.FLAG_ENFORCE_MINIMUM_TIME_WINDOWS:
- pw.println(android.app.job.Flags.enforceMinimumTimeWindows());
+ case com.android.server.job.Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT:
+ pw.println(com.android.server.job.Flags.doNotForceRushExecutionAtBoot());
break;
case com.android.server.job.Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE:
pw.println(com.android.server.job.Flags.throwOnUnsupportedBiasUsage());
diff --git a/core/api/current.txt b/core/api/current.txt
index 20b9b0e..4f5e519 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -274,6 +274,7 @@
field public static final String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
field public static final String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE";
+ field @FlaggedApi("android.companion.flags.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE";
field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS";
@@ -1603,6 +1604,7 @@
field public static final int switchTextOff = 16843628; // 0x101036c
field public static final int switchTextOn = 16843627; // 0x101036b
field public static final int syncable = 16842777; // 0x1010019
+ field @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") public static final int systemUserOnly;
field public static final int tabStripEnabled = 16843453; // 0x10102bd
field public static final int tabStripLeft = 16843451; // 0x10102bb
field public static final int tabStripRight = 16843452; // 0x10102bc
@@ -9696,8 +9698,10 @@
method public void requestNotificationAccess(android.content.ComponentName);
method @FlaggedApi("android.companion.association_tag") public void setAssociationTag(int, @NonNull String);
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
+ method @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull android.companion.ObservingDevicePresenceRequest);
method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
+ method @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull android.companion.ObservingDevicePresenceRequest);
field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
field public static final int FLAG_CALL_METADATA = 1; // 0x1
@@ -9725,13 +9729,7 @@
method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
- method @FlaggedApi("android.companion.device_presence") @MainThread public void onDeviceEvent(@NonNull android.companion.AssociationInfo, int);
- field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_APPEARED = 0; // 0x0
- field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1; // 0x1
- field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BT_CONNECTED = 2; // 0x2
- field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; // 0x3
- field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; // 0x4
- field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; // 0x5
+ method @FlaggedApi("android.companion.device_presence") @MainThread public void onDevicePresenceEvent(@NonNull android.companion.DevicePresenceEvent);
field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
}
@@ -9744,6 +9742,38 @@
public class DeviceNotAssociatedException extends java.lang.RuntimeException {
}
+ @FlaggedApi("android.companion.device_presence") public final class DevicePresenceEvent implements android.os.Parcelable {
+ ctor public DevicePresenceEvent(int, int, @Nullable android.os.ParcelUuid);
+ method public int describeContents();
+ method public int getAssociationId();
+ method public int getEvent();
+ method @Nullable public android.os.ParcelUuid getUuid();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.DevicePresenceEvent> CREATOR;
+ field public static final int EVENT_BLE_APPEARED = 0; // 0x0
+ field public static final int EVENT_BLE_DISAPPEARED = 1; // 0x1
+ field public static final int EVENT_BT_CONNECTED = 2; // 0x2
+ field public static final int EVENT_BT_DISCONNECTED = 3; // 0x3
+ field public static final int EVENT_SELF_MANAGED_APPEARED = 4; // 0x4
+ field public static final int EVENT_SELF_MANAGED_DISAPPEARED = 5; // 0x5
+ field public static final int NO_ASSOCIATION = -1; // 0xffffffff
+ }
+
+ @FlaggedApi("android.companion.device_presence") public final class ObservingDevicePresenceRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAssociationId();
+ method @Nullable public android.os.ParcelUuid getUuid();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.ObservingDevicePresenceRequest> CREATOR;
+ }
+
+ public static final class ObservingDevicePresenceRequest.Builder {
+ ctor public ObservingDevicePresenceRequest.Builder();
+ method @NonNull public android.companion.ObservingDevicePresenceRequest build();
+ method @NonNull public android.companion.ObservingDevicePresenceRequest.Builder setAssociationId(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE) public android.companion.ObservingDevicePresenceRequest.Builder setUuid(@NonNull android.os.ParcelUuid);
+ }
+
public final class WifiDeviceFilter implements android.companion.DeviceFilter<android.net.wifi.ScanResult> {
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -12447,6 +12477,7 @@
method @NonNull public android.content.pm.PackageInstaller.Session openSession(int) throws java.io.IOException;
method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback);
method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback, @NonNull android.os.Handler);
+ method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalState(@NonNull android.content.pm.PackageInstaller.UnarchivalState) throws android.content.pm.PackageManager.NameNotFoundException;
method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException;
method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
@@ -12676,6 +12707,14 @@
field public static final int USER_ACTION_UNSPECIFIED = 0; // 0x0
}
+ @FlaggedApi("android.content.pm.archiving") public static final class PackageInstaller.UnarchivalState {
+ method @NonNull public static android.content.pm.PackageInstaller.UnarchivalState createGenericErrorState(int);
+ method @NonNull public static android.content.pm.PackageInstaller.UnarchivalState createInsufficientStorageState(int, long, @Nullable android.app.PendingIntent);
+ method @NonNull public static android.content.pm.PackageInstaller.UnarchivalState createNoConnectivityState(int);
+ method @NonNull public static android.content.pm.PackageInstaller.UnarchivalState createOkState(int);
+ method @NonNull public static android.content.pm.PackageInstaller.UnarchivalState createUserActionRequiredState(int, @NonNull android.app.PendingIntent);
+ }
+
public class PackageItemInfo {
ctor public PackageItemInfo();
ctor public PackageItemInfo(android.content.pm.PackageItemInfo);
@@ -18330,8 +18369,8 @@
field public static final int RGBX_8888 = 2; // 0x2
field public static final int RGB_565 = 4; // 0x4
field public static final int RGB_888 = 3; // 0x3
- field @FlaggedApi("com.android.graphics.hwui.flags.requested_formats_v") public static final int RG_1616_UINT = 58; // 0x3a
- field @FlaggedApi("com.android.graphics.hwui.flags.requested_formats_v") public static final int R_16_UINT = 57; // 0x39
+ field @FlaggedApi("com.android.graphics.hwui.flags.requested_formats_v") public static final int RG_1616 = 58; // 0x3a
+ field @FlaggedApi("com.android.graphics.hwui.flags.requested_formats_v") public static final int R_16 = 57; // 0x39
field @FlaggedApi("com.android.graphics.hwui.flags.requested_formats_v") public static final int R_8 = 56; // 0x38
field public static final int S_UI8 = 53; // 0x35
field public static final long USAGE_COMPOSER_OVERLAY = 2048L; // 0x800L
@@ -24261,7 +24300,7 @@
method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers();
method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context);
- method @FlaggedApi("com.android.media.flags.enable_cross_user_routing_in_media_router2") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MEDIA_CONTENT_CONTROL, android.Manifest.permission.MEDIA_ROUTING_CONTROL}) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.os.UserHandle);
+ method @FlaggedApi("com.android.media.flags.enable_cross_user_routing_in_media_router2") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MEDIA_CONTENT_CONTROL, android.Manifest.permission.MEDIA_ROUTING_CONTROL}) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String, @NonNull android.os.UserHandle);
method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") @Nullable public android.media.RouteListingPreference getRouteListingPreference();
method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes();
method @NonNull public android.media.MediaRouter2.RoutingController getSystemController();
@@ -36584,6 +36623,7 @@
field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = "android.settings.REQUEST_MEDIA_ROUTING_CONTROL";
field public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM";
field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String ACTION_SATELLITE_SETTING = "android.settings.SATELLITE_SETTING";
field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
field public static final String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
field public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
@@ -52425,9 +52465,9 @@
field protected static final int[] PRESSED_STATE_SET;
field protected static final int[] PRESSED_WINDOW_FOCUSED_STATE_SET;
field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = 0.0f;
- field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -120.0f;
- field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -30.0f;
- field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -60.0f;
+ field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -4.0f;
+ field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -2.0f;
+ field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -3.0f;
field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE = -1.0f;
field public static final android.util.Property<android.view.View,java.lang.Float> ROTATION;
field public static final android.util.Property<android.view.View,java.lang.Float> ROTATION_X;
@@ -57081,7 +57121,7 @@
method public abstract boolean getBuiltInZoomControls();
method public abstract int getCacheMode();
method public abstract String getCursiveFontFamily();
- method @Deprecated public abstract boolean getDatabaseEnabled();
+ method public abstract boolean getDatabaseEnabled();
method @Deprecated public abstract String getDatabasePath();
method public abstract int getDefaultFixedFontSize();
method public abstract int getDefaultFontSize();
@@ -57127,7 +57167,7 @@
method public abstract void setBuiltInZoomControls(boolean);
method public abstract void setCacheMode(int);
method public abstract void setCursiveFontFamily(String);
- method @Deprecated public abstract void setDatabaseEnabled(boolean);
+ method public abstract void setDatabaseEnabled(boolean);
method @Deprecated public abstract void setDatabasePath(String);
method public abstract void setDefaultFixedFontSize(int);
method public abstract void setDefaultFontSize(int);
@@ -59258,6 +59298,7 @@
ctor public RemoteViews(@NonNull java.util.Map<android.util.SizeF,android.widget.RemoteViews>);
ctor public RemoteViews(android.widget.RemoteViews);
ctor public RemoteViews(android.os.Parcel);
+ ctor @FlaggedApi("android.appwidget.flags.draw_data_parcel") public RemoteViews(@NonNull android.widget.RemoteViews.DrawInstructions);
method public void addStableView(@IdRes int, @NonNull android.widget.RemoteViews, int);
method public void addView(@IdRes int, android.widget.RemoteViews);
method public android.view.View apply(android.content.Context, android.view.ViewGroup);
@@ -59366,6 +59407,15 @@
ctor public RemoteViews.ActionException(String);
}
+ @FlaggedApi("android.appwidget.flags.draw_data_parcel") public static final class RemoteViews.DrawInstructions {
+ method @FlaggedApi("android.appwidget.flags.draw_data_parcel") public void appendInstructions(@NonNull byte[]);
+ }
+
+ @FlaggedApi("android.appwidget.flags.draw_data_parcel") public static final class RemoteViews.DrawInstructions.Builder {
+ ctor @FlaggedApi("android.appwidget.flags.draw_data_parcel") public RemoteViews.DrawInstructions.Builder(@NonNull java.util.List<byte[]>);
+ method @FlaggedApi("android.appwidget.flags.draw_data_parcel") @NonNull public android.widget.RemoteViews.DrawInstructions build();
+ }
+
public static final class RemoteViews.RemoteCollectionItems implements android.os.Parcelable {
method public int describeContents();
method public int getItemCount();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fe32bad..0b17e03 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -56,7 +56,7 @@
field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE";
field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
field public static final String BIND_DISPLAY_HASHING_SERVICE = "android.permission.BIND_DISPLAY_HASHING_SERVICE";
- field @FlaggedApi("com.android.internal.telephony.flags.ap_domain_selection_enabled") public static final String BIND_DOMAIN_SELECTION_SERVICE = "android.permission.BIND_DOMAIN_SELECTION_SERVICE";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String BIND_DOMAIN_SELECTION_SERVICE = "android.permission.BIND_DOMAIN_SELECTION_SERVICE";
field public static final String BIND_DOMAIN_VERIFICATION_AGENT = "android.permission.BIND_DOMAIN_VERIFICATION_AGENT";
field public static final String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE";
field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE";
@@ -190,6 +190,7 @@
field public static final String MANAGE_DEFAULT_APPLICATIONS = "android.permission.MANAGE_DEFAULT_APPLICATIONS";
field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final String MANAGE_DEVICE_POLICY_APP_EXEMPTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS";
+ field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String MANAGE_ENHANCED_CONFIRMATION_STATES = "android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES";
field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS";
field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
field public static final String MANAGE_GAME_ACTIVITY = "android.permission.MANAGE_GAME_ACTIVITY";
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 949e2ba..e288b42 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6834,23 +6834,24 @@
PackageManager.GET_SHARED_LIBRARY_FILES,
UserHandle.myUserId());
- if (mActivities.size() > 0) {
- for (ActivityClientRecord ar : mActivities.values()) {
- if (ar.activityInfo.applicationInfo.packageName
- .equals(packageName)) {
- ar.activityInfo.applicationInfo = aInfo;
- ar.packageInfo = pkgInfo;
+ if (aInfo != null) {
+ if (mActivities.size() > 0) {
+ for (ActivityClientRecord ar : mActivities.values()) {
+ if (ar.activityInfo.applicationInfo.packageName
+ .equals(packageName)) {
+ ar.activityInfo.applicationInfo = aInfo;
+ ar.packageInfo = pkgInfo;
+ }
}
}
- }
- final String[] oldResDirs = { pkgInfo.getResDir() };
+ final String[] oldResDirs = {pkgInfo.getResDir()};
- final ArrayList<String> oldPaths = new ArrayList<>();
- LoadedApk.makePaths(this, pkgInfo.getApplicationInfo(), oldPaths);
- pkgInfo.updateApplicationInfo(aInfo, oldPaths);
+ final ArrayList<String> oldPaths = new ArrayList<>();
+ LoadedApk.makePaths(
+ this, pkgInfo.getApplicationInfo(), oldPaths);
+ pkgInfo.updateApplicationInfo(aInfo, oldPaths);
- synchronized (mResourcesManager) {
// Update affected Resources objects to use new ResourcesImpl
mResourcesManager.appendPendingAppInfoUpdate(oldResDirs,
aInfo);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d8d136a..ccd8456 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2958,7 +2958,8 @@
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
new AppOpInfo.Builder(OP_READ_SYSTEM_GRAMMATICAL_GENDER,
OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER, "READ_SYSTEM_GRAMMATICAL_GENDER")
- .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
+ // will make it an app-op permission in the future.
+ // .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
.build(),
};
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 7370fc3..5b044f6 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -119,7 +119,7 @@
oneway void setShowWhenLocked(in IBinder token, boolean showWhenLocked);
oneway void setInheritShowWhenLocked(in IBinder token, boolean setInheritShownWhenLocked);
- oneway void setTurnScreenOn(in IBinder token, boolean turnScreenOn);
+ void setTurnScreenOn(in IBinder token, boolean turnScreenOn);
oneway void setAllowCrossUidActivitySwitchFromBelow(in IBinder token, boolean allowed);
oneway void reportActivityFullyDrawn(in IBinder token, boolean restoredFromBundle);
oneway void overrideActivityTransition(IBinder token, boolean open, int enterAnim, int exitAnim,
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 63f37f1..36b03c1 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -261,6 +261,13 @@
public static final String COMMAND_GOING_TO_SLEEP = "android.wallpaper.goingtosleep";
/**
+ * Command for {@link #sendWallpaperCommand}: reported when a physical display switch event
+ * happens, e.g. fold and unfold.
+ * @hide
+ */
+ public static final String COMMAND_DISPLAY_SWITCH = "android.wallpaper.displayswitch";
+
+ /**
* Command for {@link #sendWallpaperCommand}: reported when the wallpaper that was already
* set is re-applied by the user.
* @hide
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 35ce102..b3ecd92 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -55,3 +55,10 @@
description: "Guards a bugfix that ends the credential input flow if the managed user has not stopped."
bug: "293441361"
}
+
+flag {
+ name: "default_sms_personal_app_suspension_fix_enabled"
+ namespace: "enterprise"
+ description: "Exempt the default sms app of the context user for suspension when calling setPersonalAppsSuspended"
+ bug: "309183330"
+}
diff --git a/core/java/android/app/backup/BackupHelperWithLogger.java b/core/java/android/app/backup/BackupHelperWithLogger.java
new file mode 100644
index 0000000..1a59a53
--- /dev/null
+++ b/core/java/android/app/backup/BackupHelperWithLogger.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Utility class for writing BackupHelpers with added logging capabilities.
+ * Used for passing a logger object to Helper in key shared backup agents
+ *
+ * @hide
+ */
+public abstract class BackupHelperWithLogger implements BackupHelper {
+ private BackupRestoreEventLogger mLogger;
+ private boolean mIsLoggerSet = false;
+
+ public abstract void writeNewStateDescription(ParcelFileDescriptor newState);
+
+ public abstract void restoreEntity(BackupDataInputStream data);
+
+ public abstract void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState);
+
+ /**
+ * Gets the logger so that the backuphelper can log success/error for each datatype handled
+ */
+ public BackupRestoreEventLogger getLogger() {
+ return mLogger;
+ }
+
+ /**
+ * Allow the shared backup agent to pass a logger to each of its backup helper
+ */
+ public void setLogger(BackupRestoreEventLogger logger) {
+ mLogger = logger;
+ mIsLoggerSet = true;
+ }
+
+ /**
+ * Allow the helper to check if its shared backup agent has passed a logger
+ */
+ public boolean isLoggerSet() {
+ return mIsLoggerSet;
+ }
+}
diff --git a/core/java/android/app/backup/BlobBackupHelper.java b/core/java/android/app/backup/BlobBackupHelper.java
index 82d0a94c..a55ff48 100644
--- a/core/java/android/app/backup/BlobBackupHelper.java
+++ b/core/java/android/app/backup/BlobBackupHelper.java
@@ -39,7 +39,7 @@
*
* @hide
*/
-public abstract class BlobBackupHelper implements BackupHelper {
+public abstract class BlobBackupHelper extends BackupHelperWithLogger {
private static final String TAG = "BlobBackupHelper";
private static final boolean DEBUG = false;
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 672e3439..d743992 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1038,6 +1038,7 @@
}
}
+ // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
/**
* Register to receive callbacks whenever the associated device comes in and out of range.
*
@@ -1094,7 +1095,7 @@
callingUid, callingPid);
}
}
-
+ // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
/**
* Unregister for receiving callbacks whenever the associated device comes in and out of range.
*
@@ -1137,6 +1138,64 @@
}
/**
+ * Register to receive callbacks whenever the associated device comes in and out of range.
+ *
+ * <p>The app doesn't need to remain running in order to receive its callbacks.</p>
+ *
+ * <p>Calling app must check for feature presence of
+ * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} before calling this API.</p>
+ *
+ * <p>For Bluetooth LE devices, this is based on scanning for device with the given address.
+ * The system will scan for the device when Bluetooth is ON or Bluetooth scanning is ON.</p>
+ *
+ * <p>For Bluetooth classic devices this is triggered when the device connects/disconnects.</p>
+ *
+ * <p>WiFi devices are not supported.</p>
+ *
+ * <p>If a Bluetooth LE device wants to use a rotating mac address, it is recommended to use
+ * Resolvable Private Address, and ensure the device is bonded to the phone so that android OS
+ * is able to resolve the address.</p>
+ *
+ * @param request A request for setting the types of device for observing device presence.
+ *
+ * @see ObservingDevicePresenceRequest.Builder
+ * @see CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)
+ */
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+ @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
+ public void startObservingDevicePresence(@NonNull ObservingDevicePresenceRequest request) {
+ Objects.requireNonNull(request, "request cannot be null");
+
+ try {
+ mService.startObservingDevicePresence(
+ request, mContext.getOpPackageName(), mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregister for receiving callbacks whenever the associated device comes in and out of range.
+ *
+ * Calling app must check for feature presence of
+ * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} before calling this API.
+ *
+ * @param request A request for setting the types of device for observing device presence.
+ */
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+ @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
+ public void stopObservingDevicePresence(@NonNull ObservingDevicePresenceRequest request) {
+ Objects.requireNonNull(request, "request cannot be null");
+
+ try {
+ mService.stopObservingDevicePresence(
+ request, mContext.getOpPackageName(), mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Dispatch a message to system for processing. It should only be called by
* {@link CompanionDeviceService#dispatchMessageToSystem(int, int, byte[])}
*
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 4d0267c..5ad2348 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -18,7 +18,6 @@
package android.companion;
import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -33,8 +32,6 @@
import java.io.InputStream;
import java.io.OutputStream;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -123,62 +120,6 @@
*/
public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
- /** @hide */
- @IntDef(prefix = {"DEVICE_EVENT"}, value = {
- DEVICE_EVENT_BLE_APPEARED,
- DEVICE_EVENT_BLE_DISAPPEARED,
- DEVICE_EVENT_BT_CONNECTED,
- DEVICE_EVENT_BT_DISCONNECTED,
- DEVICE_EVENT_SELF_MANAGED_APPEARED,
- DEVICE_EVENT_SELF_MANAGED_DISAPPEARED
- })
-
- @Retention(RetentionPolicy.SOURCE)
- public @interface DeviceEvent {}
-
- /**
- * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
- * with this event if the device comes into BLE range.
- */
- @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
- public static final int DEVICE_EVENT_BLE_APPEARED = 0;
-
- /**
- * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
- * with this event if the device is no longer in BLE range.
- */
- @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
- public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1;
-
- /**
- * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
- * with this event when the bluetooth device is connected.
- */
- @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
- public static final int DEVICE_EVENT_BT_CONNECTED = 2;
-
- /**
- * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
- * with this event if the bluetooth device is disconnected.
- */
- @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
- public static final int DEVICE_EVENT_BT_DISCONNECTED = 3;
-
- /**
- * A companion app for a self-managed device will receive the callback
- * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has appeared on its
- * own.
- */
- @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
- public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4;
-
- /**
- * A companion app for a self-managed device will receive the callback
- * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has disappeared on
- * its own.
- */
- @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
- public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5;
private final Stub mRemote = new Stub();
@@ -306,6 +247,7 @@
.detachSystemDataTransport(associationId);
}
+ // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
/**
* Called by system whenever a device associated with this app is connected.
*
@@ -318,6 +260,7 @@
}
}
+ // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
/**
* Called by system whenever a device associated with this app is disconnected.
*
@@ -331,27 +274,13 @@
}
/**
- * Called by the system during device events.
+ * Called by the system during device events.
*
- * <p>E.g. Event {@link #DEVICE_EVENT_BLE_APPEARED} will be called when the associated
- * companion device comes into BLE range.
- * <p>Event {@link #DEVICE_EVENT_BLE_DISAPPEARED} will be called when the associated
- * companion device is no longer in BLE range.
- * <p> Event {@link #DEVICE_EVENT_BT_CONNECTED} will be called when the associated
- * companion device is connected.
- * <p>Event {@link #DEVICE_EVENT_BT_DISCONNECTED} will be called when the associated
- * companion device is disconnected.
- * Note that app must receive {@link #DEVICE_EVENT_BLE_APPEARED} first before
- * {@link #DEVICE_EVENT_BLE_DISAPPEARED} and {@link #DEVICE_EVENT_BT_CONNECTED}
- * before {@link #DEVICE_EVENT_BT_DISCONNECTED}.
- *
- * @param associationInfo A record for the companion device.
- * @param event Associated companion device's event.
+ * @see CompanionDeviceManager#startObservingDevicePresence(ObservingDevicePresenceRequest)
*/
@FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
@MainThread
- public void onDeviceEvent(@NonNull AssociationInfo associationInfo,
- @DeviceEvent int event) {
+ public void onDevicePresenceEvent(@NonNull DevicePresenceEvent event) {
// Do nothing. Companion apps can override this function.
}
@@ -390,9 +319,10 @@
}
@Override
- public void onDeviceEvent(AssociationInfo associationInfo, int event) {
- mMainHandler.postAtFrontOfQueue(
- () -> mService.onDeviceEvent(associationInfo, event));
+ public void onDevicePresenceEvent(DevicePresenceEvent event) {
+ if (Flags.devicePresence()) {
+ mMainHandler.postAtFrontOfQueue(() -> mService.onDevicePresenceEvent(event));
+ }
}
}
}
diff --git a/core/java/android/companion/DevicePresenceEvent.aidl b/core/java/android/companion/DevicePresenceEvent.aidl
new file mode 100644
index 0000000..1521574
--- /dev/null
+++ b/core/java/android/companion/DevicePresenceEvent.aidl
@@ -0,0 +1,19 @@
+ /*
+ * 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 android.companion;
+
+ parcelable DevicePresenceEvent;
diff --git a/core/java/android/companion/DevicePresenceEvent.java b/core/java/android/companion/DevicePresenceEvent.java
new file mode 100644
index 0000000..30439a5
--- /dev/null
+++ b/core/java/android/companion/DevicePresenceEvent.java
@@ -0,0 +1,218 @@
+/*
+ * 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 android.companion;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Event for observing device presence.
+ *
+ * @see CompanionDeviceManager#startObservingDevicePresence(ObservingDevicePresenceRequest)
+ * @see ObservingDevicePresenceRequest.Builder#setUuid(ParcelUuid)
+ * @see ObservingDevicePresenceRequest.Builder#setAssociationId(int)
+ */
+@FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+public final class DevicePresenceEvent implements Parcelable {
+
+ /** @hide */
+ @IntDef(prefix = {"EVENT"}, value = {
+ EVENT_BLE_APPEARED,
+ EVENT_BLE_DISAPPEARED,
+ EVENT_BT_CONNECTED,
+ EVENT_BT_DISCONNECTED,
+ EVENT_SELF_MANAGED_APPEARED,
+ EVENT_SELF_MANAGED_DISAPPEARED
+ })
+
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Event {}
+
+ /**
+ * Indicate observing device presence base on the ParcelUuid but not association id.
+ */
+ public static final int NO_ASSOCIATION = -1;
+
+ /**
+ * Companion app receives
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} callback
+ * with this event if the device comes into BLE range.
+ */
+ public static final int EVENT_BLE_APPEARED = 0;
+
+ /**
+ * Companion app receives
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} callback
+ * with this event if the device is no longer in BLE range.
+ */
+ public static final int EVENT_BLE_DISAPPEARED = 1;
+
+ /**
+ * Companion app receives
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} callback
+ * with this event when the bluetooth device is connected.
+ */
+ public static final int EVENT_BT_CONNECTED = 2;
+
+ /**
+ * Companion app receives
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} callback
+ * with this event if the bluetooth device is disconnected.
+ */
+ public static final int EVENT_BT_DISCONNECTED = 3;
+
+ /**
+ * A companion app for a self-managed device will receive the callback
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)}
+ * if it reports that a device has appeared on its
+ * own.
+ */
+ public static final int EVENT_SELF_MANAGED_APPEARED = 4;
+
+ /**
+ * A companion app for a self-managed device will receive the callback
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} if it reports
+ * that a device has disappeared on its own.
+ */
+ public static final int EVENT_SELF_MANAGED_DISAPPEARED = 5;
+ private final int mAssociationId;
+ private final int mEvent;
+ @Nullable
+ private final ParcelUuid mUuid;
+
+ private static final int PARCEL_UUID_NULL = 0;
+
+ private static final int PARCEL_UUID_NOT_NULL = 1;
+
+ /**
+ * Create a new DevicePresenceEvent.
+ */
+ public DevicePresenceEvent(
+ int associationId, @Event int event, @Nullable ParcelUuid uuid) {
+ mAssociationId = associationId;
+ mEvent = event;
+ mUuid = uuid;
+ }
+
+ /**
+ * @return The association id has been used to observe device presence.
+ *
+ * Caller will receive the valid association id if only if using
+ * {@link ObservingDevicePresenceRequest.Builder#setAssociationId(int)}, otherwise
+ * return {@link #NO_ASSOCIATION}.
+ *
+ * @see ObservingDevicePresenceRequest.Builder#setAssociationId(int)
+ */
+ public int getAssociationId() {
+ return mAssociationId;
+ }
+
+ /**
+ * @return Associated companion device's event.
+ */
+ public int getEvent() {
+ return mEvent;
+ }
+
+ /**
+ * @return The ParcelUuid has been used to observe device presence.
+ *
+ * Caller will receive the ParcelUuid if only if using
+ * {@link ObservingDevicePresenceRequest.Builder#setUuid(ParcelUuid)}, otherwise return null.
+ *
+ * @see ObservingDevicePresenceRequest.Builder#setUuid(ParcelUuid)
+ */
+
+ @Nullable
+ public ParcelUuid getUuid() {
+ return mUuid;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mAssociationId);
+ dest.writeInt(mEvent);
+ if (mUuid == null) {
+ // Write 0 to the parcel to indicate the ParcelUuid is null.
+ dest.writeInt(PARCEL_UUID_NULL);
+ } else {
+ dest.writeInt(PARCEL_UUID_NOT_NULL);
+ mUuid.writeToParcel(dest, flags);
+ }
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof DevicePresenceEvent that)) return false;
+
+ return Objects.equals(mUuid, that.mUuid)
+ && mAssociationId == that.mAssociationId
+ && mEvent == that.mEvent;
+ }
+
+ @Override
+ public String toString() {
+ return "ObservingDevicePresenceResult { "
+ + "Association Id= " + mAssociationId + ","
+ + "ParcelUuid= " + mUuid + ","
+ + "Event= " + mEvent + "}";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAssociationId, mEvent, mUuid);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<DevicePresenceEvent> CREATOR =
+ new Parcelable.Creator<DevicePresenceEvent>() {
+ @Override
+ public DevicePresenceEvent[] newArray(int size) {
+ return new DevicePresenceEvent[size];
+ }
+
+ @Override
+ public DevicePresenceEvent createFromParcel(@NonNull Parcel in) {
+ return new DevicePresenceEvent(in);
+ }
+ };
+
+ private DevicePresenceEvent(@NonNull Parcel in) {
+ mAssociationId = in.readInt();
+ mEvent = in.readInt();
+ if (in.readInt() == PARCEL_UUID_NULL) {
+ mUuid = null;
+ } else {
+ mUuid = ParcelUuid.CREATOR.createFromParcel(in);
+ }
+ }
+}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 22689f3..57d59e5 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -24,8 +24,11 @@
import android.companion.ISystemDataTransferCallback;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
+import android.companion.ObservingDevicePresenceRequest;
import android.companion.datatransfer.PermissionSyncRequest;
import android.content.ComponentName;
+import android.os.ParcelUuid;
+
/**
* Interface for communication with the core companion device manager service.
@@ -132,4 +135,10 @@
byte[] getBackupPayload(int userId);
void applyRestoredPayload(in byte[] payload, int userId);
+
+ @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
+ void startObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
+
+ @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
+ void stopObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
}
diff --git a/core/java/android/companion/ICompanionDeviceService.aidl b/core/java/android/companion/ICompanionDeviceService.aidl
index 2a311bf..f5401d2 100644
--- a/core/java/android/companion/ICompanionDeviceService.aidl
+++ b/core/java/android/companion/ICompanionDeviceService.aidl
@@ -17,10 +17,12 @@
package android.companion;
import android.companion.AssociationInfo;
+import android.companion.DevicePresenceEvent;
+import android.os.ParcelUuid;
/** @hide */
oneway interface ICompanionDeviceService {
void onDeviceAppeared(in AssociationInfo associationInfo);
void onDeviceDisappeared(in AssociationInfo associationInfo);
- void onDeviceEvent(in AssociationInfo associationInfo, int state);
+ void onDevicePresenceEvent(in DevicePresenceEvent event);
}
diff --git a/core/java/android/companion/ObservingDevicePresenceRequest.aidl b/core/java/android/companion/ObservingDevicePresenceRequest.aidl
new file mode 100644
index 0000000..fed0607
--- /dev/null
+++ b/core/java/android/companion/ObservingDevicePresenceRequest.aidl
@@ -0,0 +1,19 @@
+ /*
+ * 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 android.companion;
+
+ parcelable ObservingDevicePresenceRequest;
\ No newline at end of file
diff --git a/core/java/android/companion/ObservingDevicePresenceRequest.java b/core/java/android/companion/ObservingDevicePresenceRequest.java
new file mode 100644
index 0000000..f1d594e
--- /dev/null
+++ b/core/java/android/companion/ObservingDevicePresenceRequest.java
@@ -0,0 +1,208 @@
+/*
+ * 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 android.companion;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.provider.OneTimeUseBuilder;
+
+import java.util.Objects;
+
+/**
+ * A request for setting the types of device for observing device presence.
+ *
+ * <p>Only supports association id or ParcelUuid and calling app must declare uses-permission
+ * {@link android.Manifest.permission#REQUEST_OBSERVE_DEVICE_UUID_PRESENCE} if using
+ * {@link Builder#setUuid(ParcelUuid)}.</p>
+ *
+ * Calling apps must use either ObservingDevicePresenceRequest.Builder#setUuid(ParcelUuid) or
+ * ObservingDevicePresenceRequest.Builder#setAssociationId(int), but not both.
+ *
+ * @see Builder#setUuid(ParcelUuid)
+ * @see Builder#setAssociationId(int)
+ * @see CompanionDeviceManager#startObservingDevicePresence(ObservingDevicePresenceRequest)
+ */
+@FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+public final class ObservingDevicePresenceRequest implements Parcelable {
+ private final int mAssociationId;
+ @Nullable private final ParcelUuid mUuid;
+
+ private static final int PARCEL_UUID_NULL = 0;
+
+ private static final int PARCEL_UUID_NOT_NULL = 1;
+
+ private ObservingDevicePresenceRequest(int associationId, ParcelUuid uuid) {
+ mAssociationId = associationId;
+ mUuid = uuid;
+ }
+
+ private ObservingDevicePresenceRequest(@NonNull Parcel in) {
+ mAssociationId = in.readInt();
+ if (in.readInt() == PARCEL_UUID_NULL) {
+ mUuid = null;
+ } else {
+ mUuid = ParcelUuid.CREATOR.createFromParcel(in);
+ }
+ }
+
+ /**
+ * @return the association id for observing device presence. It will return
+ * {@link DevicePresenceEvent#NO_ASSOCIATION} if using
+ * {@link Builder#setUuid(ParcelUuid)}.
+ */
+ public int getAssociationId() {
+ return mAssociationId;
+ }
+
+ /**
+ * @return the ParcelUuid for observing device presence.
+ */
+ @Nullable
+ public ParcelUuid getUuid() {
+ return mUuid;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mAssociationId);
+ if (mUuid == null) {
+ // Write 0 to the parcel to indicate the ParcelUuid is null.
+ dest.writeInt(PARCEL_UUID_NULL);
+ } else {
+ dest.writeInt(PARCEL_UUID_NOT_NULL);
+ mUuid.writeToParcel(dest, flags);
+ }
+
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ObservingDevicePresenceRequest> CREATOR =
+ new Parcelable.Creator<ObservingDevicePresenceRequest>() {
+ @Override
+ public ObservingDevicePresenceRequest[] newArray(int size) {
+ return new ObservingDevicePresenceRequest[size];
+ }
+
+ @Override
+ public ObservingDevicePresenceRequest createFromParcel(@NonNull Parcel in) {
+ return new ObservingDevicePresenceRequest(in);
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "ObservingDevicePresenceRequest { "
+ + "Association Id= " + mAssociationId + ","
+ + "ParcelUuid= " + mUuid + "}";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ObservingDevicePresenceRequest that)) return false;
+
+ return Objects.equals(mUuid, that.mUuid) && mAssociationId == that.mAssociationId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAssociationId, mUuid);
+ }
+
+ /**
+ * A builder for {@link ObservingDevicePresenceRequest}
+ */
+ public static final class Builder extends OneTimeUseBuilder<ObservingDevicePresenceRequest> {
+ // Initial the association id to {@link DevicePresenceEvent.NO_ASSOCIATION}
+ // to indicate the value is not set yet.
+ private int mAssociationId = DevicePresenceEvent.NO_ASSOCIATION;
+ private ParcelUuid mUuid;
+
+ public Builder() {}
+
+ /**
+ * Set the association id to be observed for device presence.
+ *
+ * <p>The provided device must be {@link CompanionDeviceManager#associate associated}
+ * with the calling app before calling this method if using this API.
+ *
+ * Caller must implement a single {@link CompanionDeviceService} which will be bound to and
+ * receive callbacks to
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)}.</p>
+ *
+ * <p>Calling apps must use either {@link #setUuid(ParcelUuid)}
+ * or this API, but not both.</p>
+ *
+ * @param associationId The association id for observing device presence.
+ */
+ @NonNull
+ public Builder setAssociationId(int associationId) {
+ checkNotUsed();
+ this.mAssociationId = associationId;
+ return this;
+ }
+
+ /**
+ * Set the ParcelUuid to be observed for device presence.
+ *
+ * <p>It does not require to create the association before calling this API.
+ * This only supports classic Bluetooth scan and caller must implement
+ * a single {@link CompanionDeviceService} which will be bound to and receive callbacks to
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)}.</p>
+ *
+ * <p>The Uuid should be matching one of the ParcelUuid form
+ * {@link android.bluetooth.BluetoothDevice#getUuids()}</p>
+ *
+ * <p>Calling apps must use either this API or {@link #setAssociationId(int)},
+ * but not both.</p>
+ *
+ * @param uuid The ParcelUuid for observing device presence.
+ */
+ @NonNull
+ @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
+ public Builder setUuid(@NonNull ParcelUuid uuid) {
+ checkNotUsed();
+ this.mUuid = uuid;
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public ObservingDevicePresenceRequest build() {
+ markUsed();
+ if (mUuid != null && mAssociationId != DevicePresenceEvent.NO_ASSOCIATION) {
+ throw new IllegalStateException("Cannot observe device presence based on "
+ + "both ParcelUuid and association ID. Choose one or the other.");
+ } else if (mUuid == null && mAssociationId <= 0) {
+ throw new IllegalStateException("Must provide either a ParcelUuid or "
+ + "a valid association ID to observe device presence.");
+ }
+
+ return new ObservingDevicePresenceRequest(mAssociationId, mUuid);
+ }
+ }
+}
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 9e410b8..d634b64 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -33,4 +33,4 @@
namespace: "companion"
description: "Expose perm sync user consent API"
bug: "309528663"
-}
\ No newline at end of file
+}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index c7a75ed..e9b94c9 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -41,6 +41,7 @@
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.SQLException;
+import android.multiuser.Flags;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
@@ -146,6 +147,7 @@
private boolean mExported;
private boolean mNoPerms;
private boolean mSingleUser;
+ private boolean mSystemUserOnly;
private SparseBooleanArray mUsersRedirectedToOwnerForMedia = new SparseBooleanArray();
private ThreadLocal<AttributionSource> mCallingAttributionSource;
@@ -377,7 +379,9 @@
!= PermissionChecker.PERMISSION_GRANTED
&& getContext().checkUriPermission(userUri, Binder.getCallingPid(),
callingUid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
- != PackageManager.PERMISSION_GRANTED) {
+ != PackageManager.PERMISSION_GRANTED
+ && !deniedAccessSystemUserOnlyProvider(callingUserId,
+ mSystemUserOnly)) {
FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
enumCheckUriPermission,
callingUid, uri.getAuthority(), type);
@@ -865,6 +869,10 @@
boolean checkUser(int pid, int uid, Context context) {
final int callingUserId = UserHandle.getUserId(uid);
+ if (deniedAccessSystemUserOnlyProvider(callingUserId, mSystemUserOnly)) {
+ return false;
+ }
+
if (callingUserId == context.getUserId() || mSingleUser) {
return true;
}
@@ -987,6 +995,9 @@
// last chance, check against any uri grants
final int callingUserId = UserHandle.getUserId(uid);
+ if (deniedAccessSystemUserOnlyProvider(callingUserId, mSystemUserOnly)) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
final Uri userUri = (mSingleUser && !UserHandle.isSameUser(mMyUid, uid))
? maybeAddUserId(uri, callingUserId) : uri;
if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
@@ -2623,6 +2634,7 @@
setPathPermissions(info.pathPermissions);
mExported = info.exported;
mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
+ mSystemUserOnly = (info.flags & ProviderInfo.FLAG_SYSTEM_USER_ONLY) != 0;
setAuthorities(info.authority);
}
if (Build.IS_DEBUGGABLE) {
@@ -2756,6 +2768,11 @@
String auth = uri.getAuthority();
if (!mSingleUser) {
int userId = getUserIdFromAuthority(auth, UserHandle.USER_CURRENT);
+ if (deniedAccessSystemUserOnlyProvider(mContext.getUserId(),
+ mSystemUserOnly)) {
+ throw new SecurityException("Trying to query a SYSTEM user only content"
+ + " provider from user:" + mContext.getUserId());
+ }
if (userId != UserHandle.USER_CURRENT
&& userId != mContext.getUserId()
// Since userId specified in content uri, the provider userId would be
@@ -2929,4 +2946,16 @@
Trace.traceBegin(traceTag, methodName + subInfo);
}
}
+ /**
+ * Return true if access to content provider is denied because it's a SYSTEM user only
+ * provider and the calling user is not the SYSTEM user.
+ *
+ * @param callingUserId UserId of the caller accessing the content provider.
+ * @param systemUserOnly true when the content provider is only available for the SYSTEM user.
+ */
+ private static boolean deniedAccessSystemUserOnlyProvider(int callingUserId,
+ boolean systemUserOnly) {
+ return Flags.enableSystemUserOnlyForServicesAndProviders()
+ && (callingUserId != UserHandle.USER_SYSTEM && systemUserOnly);
+ }
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 22926fe..c4bf18d 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -738,7 +738,7 @@
/**
* The set of error types that can be set for
- * {@link #reportUnarchivalStatus(int, int, PendingIntent)}.
+ * {@link #reportUnarchivalState}.
*
* @hide
*/
@@ -2421,6 +2421,7 @@
* facilitate the unarchival flow (e.g. user needs to log in).
* @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists
*/
+ // TODO(b/314960798) Remove old API once it's unused
@RequiresPermission(anyOf = {
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.REQUEST_INSTALL_PACKAGES})
@@ -2438,6 +2439,30 @@
}
}
+ /**
+ * Reports the state of an unarchival to the system.
+ *
+ * @see UnarchivalState for the different state options.
+ * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.REQUEST_INSTALL_PACKAGES})
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public void reportUnarchivalState(@NonNull UnarchivalState unarchivalState)
+ throws PackageManager.NameNotFoundException {
+ Objects.requireNonNull(unarchivalState);
+ try {
+ mInstaller.reportUnarchivalStatus(unarchivalState.getUnarchiveId(),
+ unarchivalState.getStatus(), unarchivalState.getRequiredStorageBytes(),
+ unarchivalState.getUserActionIntent(), new UserHandle(mUserId));
+ } catch (ParcelableException e) {
+ e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
// (b/239722738) This class serves as a bridge between the PackageLite class, which
// is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java)
// This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or
@@ -4741,10 +4766,10 @@
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
inputSignatures = "private final @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate final @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate final @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.PackageInstaller.PreapprovalDetails> CREATOR\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\npublic @java.lang.Override int describeContents()\nclass PreapprovalDetails extends java.lang.Object implements [android.os.Parcelable]\nprivate @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate @android.annotation.NonNull java.lang.String mPackageName\nprivate long mBuilderFieldsSet\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setIcon(android.graphics.Bitmap)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLabel(java.lang.CharSequence)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLocale(android.icu.util.ULocale)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setPackageName(java.lang.String)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails build()\nprivate void checkNotUsed()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true)")
+
@Deprecated
private void __metadata() {}
-
//@formatter:on
// End of generated code
@@ -5135,13 +5160,188 @@
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final boolean mDeviceIdleRequired\nprivate final boolean mAppNotForegroundRequired\nprivate final boolean mAppNotInteractingRequired\nprivate final boolean mAppNotTopVisibleRequired\nprivate final boolean mNotInCallRequired\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mDeviceIdleRequired\nprivate boolean mAppNotForegroundRequired\nprivate boolean mAppNotInteractingRequired\nprivate boolean mAppNotTopVisibleRequired\nprivate boolean mNotInCallRequired\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setDeviceIdleRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotForegroundRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotInteractingRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotTopVisibleRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setNotInCallRequired()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genEqualsHashCode=true)")
+
@Deprecated
private void __metadata() {}
-
//@formatter:on
// End of generated code
}
+ /**
+ * Used to communicate the unarchival state in {@link #reportUnarchivalState}.
+ */
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final class UnarchivalState {
+
+ /**
+ * The caller is able to facilitate the unarchival for the given {@code unarchiveId}.
+ *
+ * @param unarchiveId the ID provided by the system as part of the intent.action.UNARCHIVE
+ * broadcast with EXTRA_UNARCHIVE_ID.
+ */
+ @NonNull
+ public static UnarchivalState createOkState(int unarchiveId) {
+ return new UnarchivalState(unarchiveId, UNARCHIVAL_OK, /* requiredStorageBytes= */ -1,
+ /* userActionIntent= */ null);
+ }
+
+ /**
+ * User action is required before commencing with the unarchival for the given
+ * {@code unarchiveId}. E.g., this could be used if it's necessary for the user to sign-in
+ * first.
+ *
+ * @param unarchiveId the ID provided by the system as part of the
+ * intent.action.UNARCHIVE
+ * broadcast with EXTRA_UNARCHIVE_ID.
+ * @param userActionIntent optional intent to start a follow up action required to
+ * facilitate the unarchival flow (e.g. user needs to log in).
+ */
+ @NonNull
+ public static UnarchivalState createUserActionRequiredState(int unarchiveId,
+ @NonNull PendingIntent userActionIntent) {
+ Objects.requireNonNull(userActionIntent);
+ return new UnarchivalState(unarchiveId, UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
+ /* requiredStorageBytes= */ -1, userActionIntent);
+ }
+
+ /**
+ * There is not enough storage to start the unarchival for the given {@code unarchiveId}.
+ *
+ * @param unarchiveId the ID provided by the system as part of the
+ * intent.action.UNARCHIVE
+ * broadcast with EXTRA_UNARCHIVE_ID.
+ * @param requiredStorageBytes ff the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this
+ * field should be set to specify how many additional bytes of
+ * storage are required to unarchive the app.
+ * @param userActionIntent can optionally be set to provide a custom storage-clearing
+ * action.
+ */
+ @NonNull
+ public static UnarchivalState createInsufficientStorageState(int unarchiveId,
+ long requiredStorageBytes, @Nullable PendingIntent userActionIntent) {
+ return new UnarchivalState(unarchiveId, UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
+ requiredStorageBytes, userActionIntent);
+ }
+
+ /**
+ * The device has no data connectivity and unarchival cannot be started for the given
+ * {@code unarchiveId}.
+ *
+ * @param unarchiveId the ID provided by the system as part of the intent.action.UNARCHIVE
+ * broadcast with EXTRA_UNARCHIVE_ID.
+ */
+ @NonNull
+ public static UnarchivalState createNoConnectivityState(int unarchiveId) {
+ return new UnarchivalState(unarchiveId, UNARCHIVAL_ERROR_NO_CONNECTIVITY,
+ /* requiredStorageBytes= */ -1,/* userActionIntent= */ null);
+ }
+
+ /**
+ * Generic error state for all cases that are not covered by other methods in this class.
+ *
+ * @param unarchiveId the ID provided by the system as part of the intent.action.UNARCHIVE
+ * broadcast with EXTRA_UNARCHIVE_ID.
+ */
+ @NonNull
+ public static UnarchivalState createGenericErrorState(int unarchiveId) {
+ return new UnarchivalState(unarchiveId, UNARCHIVAL_GENERIC_ERROR,
+ /* requiredStorageBytes= */ -1,/* userActionIntent= */ null);
+ }
+
+
+ /**
+ * The ID provided by the system as part of the intent.action.UNARCHIVE broadcast with
+ * EXTRA_UNARCHIVE_ID.
+ */
+ private final int mUnarchiveId;
+
+ /** Used for the system to provide the user with necessary follow-up steps or errors. */
+ @UnarchivalStatus
+ private final int mStatus;
+
+ /**
+ * If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field should be set to specify
+ * how many additional bytes of storage are required to unarchive the app.
+ */
+ private final long mRequiredStorageBytes;
+
+ /**
+ * Optional intent to start a follow up action required to facilitate the unarchival flow
+ * (e.g., user needs to log in).
+ */
+ @Nullable
+ private final PendingIntent mUserActionIntent;
+
+ /**
+ * Creates a new UnarchivalState.
+ *
+ * @param unarchiveId The ID provided by the system as part of the
+ * intent.action.UNARCHIVE broadcast with
+ * EXTRA_UNARCHIVE_ID.
+ * @param status Used for the system to provide the user with necessary
+ * follow-up steps or errors.
+ * @param requiredStorageBytes If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this
+ * field should be set to specify
+ * how many additional bytes of storage are required to
+ * unarchive the app.
+ * @param userActionIntent Optional intent to start a follow up action required to
+ * facilitate the unarchival flow
+ * (e.g,. user needs to log in).
+ * @hide
+ */
+ private UnarchivalState(
+ int unarchiveId,
+ @UnarchivalStatus int status,
+ long requiredStorageBytes,
+ @Nullable PendingIntent userActionIntent) {
+ this.mUnarchiveId = unarchiveId;
+ this.mStatus = status;
+ com.android.internal.util.AnnotationValidations.validate(
+ UnarchivalStatus.class, null, mStatus);
+ this.mRequiredStorageBytes = requiredStorageBytes;
+ this.mUserActionIntent = userActionIntent;
+ }
+
+ /**
+ * The ID provided by the system as part of the intent.action.UNARCHIVE broadcast with
+ * EXTRA_UNARCHIVE_ID.
+ *
+ * @hide
+ */
+ int getUnarchiveId() {
+ return mUnarchiveId;
+ }
+
+ /**
+ * Used for the system to provide the user with necessary follow-up steps or errors.
+ *
+ * @hide
+ */
+ @UnarchivalStatus int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field should be set to specify
+ * how many additional bytes of storage are required to unarchive the app.
+ *
+ * @hide
+ */
+ long getRequiredStorageBytes() {
+ return mRequiredStorageBytes;
+ }
+
+ /**
+ * Optional intent to start a follow up action required to facilitate the unarchival flow
+ * (e.g. user needs to log in).
+ *
+ * @hide
+ */
+ @Nullable PendingIntent getUserActionIntent() {
+ return mUserActionIntent;
+ }
+ }
+
}
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
index 9e553db..de33fa8 100644
--- a/core/java/android/content/pm/ProviderInfo.java
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -89,6 +89,15 @@
public static final int FLAG_VISIBLE_TO_INSTANT_APP = 0x100000;
/**
+ * Bit in {@link #flags}: If set, this provider will only be available
+ * for the system user.
+ * Set from the android.R.attr#systemUserOnly attribute.
+ * In Sync with {@link ActivityInfo#FLAG_SYSTEM_USER_ONLY}
+ * @hide
+ */
+ public static final int FLAG_SYSTEM_USER_ONLY = ActivityInfo.FLAG_SYSTEM_USER_ONLY;
+
+ /**
* Bit in {@link #flags}: If set, a single instance of the provider will
* run for all users on the device. Set from the
* {@link android.R.attr#singleUser} attribute.
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index ae46c027..2b378b1 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -101,6 +101,14 @@
public static final int FLAG_VISIBLE_TO_INSTANT_APP = 0x100000;
/**
+ * @hide Bit in {@link #flags}: If set, this service will only be available
+ * for the system user.
+ * Set from the android.R.attr#systemUserOnly attribute.
+ * In Sync with {@link ActivityInfo#FLAG_SYSTEM_USER_ONLY}
+ */
+ public static final int FLAG_SYSTEM_USER_ONLY = ActivityInfo.FLAG_SYSTEM_USER_ONLY;
+
+ /**
* Bit in {@link #flags}: If set, a single instance of the service will
* run for all users on the device. Set from the
* {@link android.R.attr#singleUser} attribute.
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 5bfc012..9644d80 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -84,4 +84,11 @@
namespace: "profile_experiences"
description: "Enable auto-locking private space on device restarts"
bug: "296993385"
-}
\ No newline at end of file
+}
+flag {
+ name: "enable_system_user_only_for_services_and_providers"
+ namespace: "multiuser"
+ description: "Enable systemUserOnly manifest attribute for services and providers."
+ bug: "302354856"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/overlay/OverlayPaths.java b/core/java/android/content/pm/overlay/OverlayPaths.java
index a4db733..bd74b0b 100644
--- a/core/java/android/content/pm/overlay/OverlayPaths.java
+++ b/core/java/android/content/pm/overlay/OverlayPaths.java
@@ -49,6 +49,13 @@
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/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 796a57b..2e63664 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -32,6 +32,7 @@
import android.content.Context;
import android.content.IntentSender;
import android.os.Binder;
+import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.ICancellationSignal;
@@ -58,6 +59,9 @@
@SystemService(Context.CREDENTIAL_SERVICE)
public final class CredentialManager {
private static final String TAG = "CredentialManager";
+ private static final Bundle OPTIONS_SENDER_BAL_OPTIN = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle();
/** @hide */
@IntDef(
@@ -757,9 +761,7 @@
public void onPendingIntent(PendingIntent pendingIntent) {
try {
mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0,
- ActivityOptions.makeBasic()
- .setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle());
+ OPTIONS_SENDER_BAL_OPTIN);
} catch (IntentSender.SendIntentException e) {
Log.e(
TAG,
@@ -817,7 +819,8 @@
@Override
public void onPendingIntent(PendingIntent pendingIntent) {
try {
- mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0,
+ OPTIONS_SENDER_BAL_OPTIN);
} catch (IntentSender.SendIntentException e) {
Log.e(
TAG,
diff --git a/core/java/android/credentials/PrepareGetCredentialResponse.java b/core/java/android/credentials/PrepareGetCredentialResponse.java
index 212f571..75d671b 100644
--- a/core/java/android/credentials/PrepareGetCredentialResponse.java
+++ b/core/java/android/credentials/PrepareGetCredentialResponse.java
@@ -22,9 +22,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.Context;
import android.content.IntentSender;
+import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.OutcomeReceiver;
import android.util.Log;
@@ -41,6 +43,10 @@
*/
public final class PrepareGetCredentialResponse {
+ private static final Bundle OPTIONS_SENDER_BAL_OPTIN = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle();
+
/**
* A handle that represents a pending get-credential operation. Pass this handle to {@link
* CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal,
@@ -80,7 +86,8 @@
@Override
public void onPendingIntent(PendingIntent pendingIntent) {
try {
- context.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ context.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0,
+ OPTIONS_SENDER_BAL_OPTIN);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "startIntentSender() failed for intent for show()", e);
executor.execute(() -> callback.onError(
@@ -101,7 +108,8 @@
});
try {
- context.startIntentSender(mPendingIntent.getIntentSender(), null, 0, 0, 0);
+ context.startIntentSender(mPendingIntent.getIntentSender(), null, 0, 0, 0,
+ OPTIONS_SENDER_BAL_OPTIN);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "startIntentSender() failed for intent for show()", e);
executor.execute(() -> callback.onError(
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index f5b3a7b..0047b7d 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -67,8 +67,8 @@
S_UI8,
YCBCR_P010,
R_8,
- R_16_UINT,
- RG_1616_UINT,
+ R_16,
+ RG_1616,
RGBA_10101010,
})
public @interface Format {
@@ -119,13 +119,13 @@
* implicit unsigned normalized.
*/
@FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
- public static final int R_16_UINT = 0x39;
+ public static final int R_16 = 0x39;
/**
* Format: 16 bits each red, green. Bits should be represented in unsigned integer,
* instead of the implicit unsigned normalized.
*/
@FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
- public static final int RG_1616_UINT = 0x3a;
+ public static final int RG_1616 = 0x3a;
/** Format: 10 bits each red, green, blue, alpha */
@FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
public static final int RGBA_10101010 = 0x3b;
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 365f913..594ec18 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -16,6 +16,7 @@
package android.net;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static android.app.ActivityManager.procStateToString;
import static android.content.pm.PackageManager.GET_SIGNATURES;
@@ -170,6 +171,8 @@
public static final String FIREWALL_CHAIN_NAME_RESTRICTED = "restricted";
/** @hide */
public static final String FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY = "low_power_standby";
+ /** @hide */
+ public static final String FIREWALL_CHAIN_NAME_BACKGROUND = "background";
private static final boolean ALLOW_PLATFORM_APP_POLICY = true;
@@ -180,6 +183,9 @@
/** @hide */
public static final int TOP_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_BOUND_TOP;
+ /** @hide */
+ public static final int BACKGROUND_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_TOP_SLEEPING;
+
/**
* {@link Intent} extra that indicates which {@link NetworkTemplate} rule it
* applies to.
@@ -264,6 +270,16 @@
* @hide
*/
public static final int ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST = 1 << 6;
+
+ /**
+ * Flag to indicate that the app is exempt from always-on background network restrictions.
+ * Note that this is explicitly different to the flag NOT_FOREGROUND which is used to grant
+ * shared exception to apps from power restrictions like doze, battery saver and app-standby.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_REASON_NOT_IN_BACKGROUND = 1 << 7;
+
/**
* Flag to indicate that app is exempt from certain metered network restrictions because user
* explicitly exempted it.
@@ -822,6 +838,21 @@
}
/**
+ * This is currently only used as an implementation detail for
+ * {@link com.android.server.net.NetworkPolicyManagerService}.
+ * Only put here to be together with other isProcStateAllowed* methods.
+ *
+ * @hide
+ */
+ public static boolean isProcStateAllowedNetworkWhileBackground(@Nullable UidState uidState) {
+ if (uidState == null) {
+ return false;
+ }
+ return uidState.procState < BACKGROUND_THRESHOLD_STATE
+ || (uidState.capability & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0;
+ }
+
+ /**
* Returns true if {@param procState} is considered foreground and as such will be allowed
* to access network when the device is in data saver mode. Otherwise, false.
* @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d946430..ecd6f22 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -138,6 +138,20 @@
public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
/**
+ * Activity Action: Show settings to provide guide about carrier satellite messaging.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public static final String ACTION_SATELLITE_SETTING = "android.settings.SATELLITE_SETTING";
+
+ /**
* Activity Action: Show settings to allow configuration of APNs.
* <p>
* Input: Nothing.
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 1994058..43163b3 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -58,3 +58,10 @@
bug: "290312729"
is_fixed_read_only: true
}
+
+flag {
+ name: "report_primary_auth_attempts"
+ namespace: "biometrics"
+ description: "Report primary auth attempts from LockSettingsService"
+ bug: "285053096"
+}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 1a2be15..76e0c25 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -16,6 +16,7 @@
package android.service.wallpaper;
+import static android.app.WallpaperManager.COMMAND_DISPLAY_SWITCH;
import static android.app.WallpaperManager.COMMAND_FREEZE;
import static android.app.WallpaperManager.COMMAND_UNFREEZE;
import static android.app.WallpaperManager.SetWallpaperFlags;
@@ -153,6 +154,7 @@
static final boolean DEBUG = false;
static final float MIN_PAGE_ALLOWED_MARGIN = .05f;
private static final int MIN_BITMAP_SCREENSHOT_WIDTH = 64;
+ private static final long PRESERVE_VISIBLE_TIMEOUT_MS = 1000;
private static final long DEFAULT_UPDATE_SCREENSHOT_DURATION = 60 * 1000; //Once per minute
private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
new RectF(0, 0, 1, 1);
@@ -165,6 +167,7 @@
private static final int MSG_UPDATE_SURFACE = 10000;
private static final int MSG_VISIBILITY_CHANGED = 10010;
+ private static final int MSG_REFRESH_VISIBILITY = 10011;
private static final int MSG_WALLPAPER_OFFSETS = 10020;
private static final int MSG_WALLPAPER_COMMAND = 10025;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -248,6 +251,11 @@
*/
private boolean mIsScreenTurningOn;
boolean mReportedVisible;
+ /**
+ * This is used with {@link #PRESERVE_VISIBLE_TIMEOUT_MS} to avoid intermediate visibility
+ * changes if the display may be toggled in a short time, e.g. display switch.
+ */
+ boolean mPreserveVisible;
boolean mDestroyed;
// Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false
// after receiving WallpaperManager#COMMAND_UNFREEZE. COMMAND_FREEZE is fully applied once
@@ -1084,6 +1092,9 @@
if (pendingCount != 0) {
out.print(prefix); out.print("mPendingResizeCount="); out.println(pendingCount);
}
+ if (mPreserveVisible) {
+ out.print(prefix); out.print("mPreserveVisible=true");
+ }
synchronized (mLock) {
out.print(prefix); out.print("mPendingXOffset="); out.print(mPendingXOffset);
out.print(" mPendingXOffset="); out.println(mPendingXOffset);
@@ -1643,7 +1654,8 @@
? false
: mIWallpaperEngine.mInfo.supportsAmbientMode();
// Report visibility only if display is fully on or wallpaper supports ambient mode.
- boolean visible = mVisible && (displayFullyOn || supportsAmbientMode);
+ final boolean visible = (mVisible && (displayFullyOn || supportsAmbientMode))
+ || mPreserveVisible;
if (DEBUG) {
Log.v(
TAG,
@@ -2080,6 +2092,9 @@
if (!mDestroyed) {
if (COMMAND_FREEZE.equals(cmd.action) || COMMAND_UNFREEZE.equals(cmd.action)) {
updateFrozenState(/* frozenRequested= */ !COMMAND_UNFREEZE.equals(cmd.action));
+ } else if (COMMAND_DISPLAY_SWITCH.equals(cmd.action)) {
+ handleDisplaySwitch(cmd.z == 1 /* startToSwitch */);
+ return;
}
result = onCommand(cmd.action, cmd.x, cmd.y, cmd.z,
cmd.extras, cmd.sync);
@@ -2095,6 +2110,23 @@
}
}
+ private void handleDisplaySwitch(boolean startToSwitch) {
+ if (startToSwitch && mReportedVisible) {
+ // The display may be off/on in a short time when the display is switching.
+ // Keep the visible state until onScreenTurnedOn or !startToSwitch is received, so
+ // the rendering thread can be active to redraw in time when receiving size change.
+ mPreserveVisible = true;
+ mCaller.removeMessages(MSG_REFRESH_VISIBILITY);
+ mCaller.sendMessageDelayed(mCaller.obtainMessage(MSG_REFRESH_VISIBILITY),
+ PRESERVE_VISIBLE_TIMEOUT_MS);
+ } else if (!startToSwitch && mPreserveVisible) {
+ // The switch is finished, so restore to actual visibility.
+ mPreserveVisible = false;
+ mCaller.removeMessages(MSG_REFRESH_VISIBILITY);
+ reportVisibility(false /* forceReport */);
+ }
+ }
+
private void updateFrozenState(boolean frozenRequested) {
if (mIWallpaperEngine.mInfo == null
// Procees the unfreeze command in case the wallaper became static while
@@ -2638,6 +2670,10 @@
+ ": " + message.arg1);
mEngine.doVisibilityChanged(message.arg1 != 0);
break;
+ case MSG_REFRESH_VISIBILITY:
+ mEngine.mPreserveVisible = false;
+ mEngine.reportVisibility(false /* forceReport */);
+ break;
case MSG_UPDATE_SCREEN_TURNING_ON:
if (DEBUG) {
Log.v(TAG,
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 7b9cb6a..9286049 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -40,6 +40,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
+import com.android.text.flags.Flags;
import java.lang.ref.WeakReference;
@@ -1276,8 +1277,21 @@
}
public void onSpanRemoved(Spannable s, Object o, int start, int end) {
- if (o instanceof UpdateLayout)
- transformAndReflow(s, start, end);
+ if (o instanceof UpdateLayout) {
+ if (Flags.insertModeCrashWhenDelete()) {
+ final DynamicLayout dynamicLayout = mLayout.get();
+ if (dynamicLayout != null && dynamicLayout.mDisplay instanceof OffsetMapping) {
+ // It's possible that a Span is removed when the text covering it is
+ // deleted, in this case, the original start and end of the span might be
+ // OOB. So it'll reflow the entire string instead.
+ reflow(s, 0, 0, s.length());
+ } else {
+ reflow(s, start, end - start, end - start);
+ }
+ } else {
+ transformAndReflow(s, start, end);
+ }
+ }
}
public void onSpanChanged(Spannable s, Object o, int start, int end, int nstart, int nend) {
@@ -1287,8 +1301,21 @@
// instead of causing an exception
start = 0;
}
- transformAndReflow(s, start, end);
- transformAndReflow(s, nstart, nend);
+ if (Flags.insertModeCrashWhenDelete()) {
+ final DynamicLayout dynamicLayout = mLayout.get();
+ if (dynamicLayout != null && dynamicLayout.mDisplay instanceof OffsetMapping) {
+ // When text is changed, it'll also trigger onSpanChanged. In this case we
+ // can't determine the updated range in the transformed text. So it'll
+ // reflow the entire range instead.
+ reflow(s, 0, 0, s.length());
+ } else {
+ reflow(s, start, end - start, end - start);
+ reflow(s, nstart, nend - nstart, nend - nstart);
+ }
+ } else {
+ transformAndReflow(s, start, end);
+ transformAndReflow(s, nstart, nend);
+ }
}
}
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index bf1a596..6e45fea 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -89,3 +89,10 @@
description: "Feature flag for clearing focus when the escape key is pressed."
bug: "312921137"
}
+
+flag {
+ name: "insert_mode_crash_when_delete"
+ namespace: "text"
+ description: "A feature flag for fixing the crash while delete text in insert mode."
+ bug: "314254153"
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c98d1d7..1b22fda 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5546,11 +5546,11 @@
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public static final float REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE = -1;
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -30;
+ public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -2;
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -60;
+ public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -3;
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -120;
+ public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -4;
/**
* Simple constructor to use when creating a view from code.
@@ -28492,6 +28492,7 @@
surface.destroy();
}
session.kill();
+ surfaceControl.release();
}
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index d12eda3..14c5348 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1203,11 +1203,7 @@
* changes to this setting after that point.
*
* @param flag {@code true} if the WebView should use the database storage API
- * @deprecated WebSQL is deprecated and this method will become a no-op on all
- * Android versions once support is removed in Chromium. See
- * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
- @Deprecated
public abstract void setDatabaseEnabled(boolean flag);
/**
@@ -1240,11 +1236,7 @@
*
* @return {@code true} if the database storage API is enabled
* @see #setDatabaseEnabled
- * @deprecated WebSQL is deprecated and this method will become a no-op on all
- * Android versions once support is removed in Chromium. See
- * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
- @Deprecated
public abstract boolean getDatabaseEnabled();
/**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index ddcfb40..57d268c 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -148,6 +148,7 @@
import com.android.internal.util.GrowingArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.view.FloatingActionMode;
+import com.android.text.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -2343,6 +2344,13 @@
*/
void invalidateTextDisplayList(Layout layout, int start, int end) {
if (mTextRenderNodes != null && layout instanceof DynamicLayout) {
+ if (Flags.insertModeCrashWhenDelete()
+ && mTextView.isOffsetMappingAvailable()) {
+ // Text is transformed with an OffsetMapping, and we can't know the changed range
+ // on the transformed text. Invalidate the all display lists instead.
+ invalidateTextDisplayList();
+ return;
+ }
final int startTransformed =
mTextView.originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CHARACTER);
final int endTransformed =
diff --git a/core/java/android/widget/RemoteCanvas.java b/core/java/android/widget/RemoteCanvas.java
new file mode 100644
index 0000000..9a0898c
--- /dev/null
+++ b/core/java/android/widget/RemoteCanvas.java
@@ -0,0 +1,117 @@
+/*
+ * 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 android.widget;
+
+import static android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL;
+
+import android.annotation.AttrRes;
+import android.annotation.FlaggedApi;
+import android.annotation.StyleRes;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.function.IntConsumer;
+
+/**
+ * {@link RemoteCanvas} is designed to support arbitrary protocols between two processes using
+ * {@link RemoteViews.DrawInstructions}. Upon instantiation in the host process,
+ * {@link RemoteCanvas#setDrawInstructions(RemoteViews.DrawInstructions)} is called so that the
+ * host process can render the {@link RemoteViews.DrawInstructions} from the provider process
+ * accordingly.
+ *
+ * @hide
+ */
+@FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+public class RemoteCanvas extends View {
+
+ private static final String TAG = "RemoteCanvas";
+
+ @Nullable
+ private SparseArray<Runnable> mCallbacks;
+
+ private final IntConsumer mOnClickHandler = (viewId) -> {
+ if (mCallbacks == null) {
+ Log.w(TAG, "Cannot find callback for " + viewId
+ + ", in fact there were no callbacks from this RemoteViews at all.");
+ return;
+ }
+ final Runnable cb = getCallbacks().get(viewId);
+ if (cb != null) {
+ cb.run();
+ } else {
+ Log.w(TAG, "Cannot find callback for " + viewId);
+ }
+ };
+
+ RemoteCanvas(@NonNull Context context) {
+ super(context);
+ }
+
+ RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ /**
+ * Setter method for the {@link RemoteViews.DrawInstructions} from the provider process for
+ * the host process to render accordingly.
+ *
+ * @param instructions {@link RemoteViews.DrawInstructions} from the provider process.
+ */
+ void setDrawInstructions(@NonNull final RemoteViews.DrawInstructions instructions) {
+ setTag(instructions);
+ // TODO: handle draw instructions
+ // TODO: attach mOnClickHandler
+ }
+
+ /**
+ * Adds a callback function to a clickable area in the RemoteCanvas.
+ *
+ * @param viewId the viewId of the clickable area
+ * @param cb the callback function to be triggered when clicked
+ */
+ void addOnClickHandler(final int viewId, @NonNull final Runnable cb) {
+ getCallbacks().set(viewId, cb);
+ }
+
+ /**
+ * Returns all callbacks added to the RemoteCanvas through
+ * {@link #addOnClickHandler(int, Runnable)}.
+ */
+ @VisibleForTesting
+ public SparseArray<Runnable> getCallbacks() {
+ if (mCallbacks == null) {
+ mCallbacks = new SparseArray<>();
+ }
+ return mCallbacks;
+ }
+}
diff --git a/core/java/android/widget/RemoteViews.aidl b/core/java/android/widget/RemoteViews.aidl
index 6a5fc03..19a5f25 100644
--- a/core/java/android/widget/RemoteViews.aidl
+++ b/core/java/android/widget/RemoteViews.aidl
@@ -18,3 +18,4 @@
parcelable RemoteViews;
parcelable RemoteViews.RemoteCollectionItems;
+parcelable RemoteViews.DrawInstructions;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0d499a1..0654add 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL;
+import static android.appwidget.flags.Flags.drawDataParcel;
import static android.appwidget.flags.Flags.remoteAdapterConversion;
import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
@@ -243,6 +245,7 @@
private static final int ATTRIBUTE_REFLECTION_ACTION_TAG = 32;
private static final int SET_REMOTE_ADAPTER_TAG = 33;
private static final int SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG = 34;
+ private static final int SET_DRAW_INSTRUCTION_TAG = 35;
/** @hide **/
@IntDef(prefix = "MARGIN_", value = {
@@ -442,6 +445,19 @@
@Nullable
private LayoutInflater.Factory2 mLayoutInflaterFactory2;
+ /**
+ * Indicates whether this {@link RemoteViews} was instantiated with a {@link DrawInstructions}
+ * object. {@link DrawInstructions} serves as an alternative protocol for the host process
+ * to render.
+ */
+ private boolean mHasDrawInstructions;
+
+ @Nullable
+ private SparseArray<PendingIntent> mPendingIntentTemplate;
+
+ @Nullable
+ private SparseArray<Intent> mFillInIntent;
+
private static final InteractionHandler DEFAULT_INTERACTION_HANDLER =
(view, pendingIntent, response) ->
startPendingIntent(view, pendingIntent, response.getLaunchOptions(view));
@@ -1463,6 +1479,11 @@
@Override
public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
+ if (hasDrawInstructions() && root instanceof RemoteCanvas target) {
+ target.addOnClickHandler(mViewId, () ->
+ mResponse.handleViewInteraction(root, params.handler));
+ return;
+ }
final View target = root.findViewById(mViewId);
if (target == null) return;
@@ -3851,6 +3872,45 @@
}
}
+ private static class SetDrawInstructionAction extends Action {
+
+ @Nullable
+ private final DrawInstructions mInstructions;
+
+ SetDrawInstructionAction(@NonNull final DrawInstructions instructions) {
+ mInstructions = instructions;
+ }
+
+ SetDrawInstructionAction(@NonNull final Parcel in) {
+ if (drawDataParcel()) {
+ mInstructions = DrawInstructions.readFromParcel(in);
+ } else {
+ mInstructions = null;
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (drawDataParcel()) {
+ DrawInstructions.writeToParcel(mInstructions, dest, flags);
+ }
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params)
+ throws ActionException {
+ if (drawDataParcel() && mInstructions != null
+ && root instanceof RemoteCanvas remoteCanvas) {
+ remoteCanvas.setDrawInstructions(mInstructions);
+ }
+ }
+
+ @Override
+ public int getActionTag() {
+ return SET_DRAW_INSTRUCTION_TAG;
+ }
+ }
+
/**
* Create a new RemoteViews object that will display the views contained
* in the specified layout file.
@@ -4080,6 +4140,7 @@
mClassCookies = src.mClassCookies;
mIdealSize = src.mIdealSize;
mProviderInstanceId = src.mProviderInstanceId;
+ mHasDrawInstructions = src.mHasDrawInstructions;
if (src.hasLandscapeAndPortraitLayouts()) {
mLandscape = createInitializedFrom(src.mLandscape, hierarchyRoot);
@@ -4114,12 +4175,26 @@
/**
* Reads a RemoteViews object from a parcel.
*
- * @param parcel
+ * @param parcel the parcel object
*/
public RemoteViews(Parcel parcel) {
this(parcel, /* rootData= */ null, /* info= */ null, /* depth= */ 0);
}
+ /**
+ * Instantiates a RemoteViews object using {@link DrawInstructions}, which serves as an
+ * alternative to XML layout. {@link DrawInstructions} objects contains the instructions which
+ * can be interpreted and rendered accordingly in the host process.
+ *
+ * @param drawInstructions The {@link DrawInstructions} object
+ */
+ @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+ public RemoteViews(@NonNull final DrawInstructions drawInstructions) {
+ Objects.requireNonNull(drawInstructions);
+ mHasDrawInstructions = true;
+ addAction(new SetDrawInstructionAction(drawInstructions));
+ }
+
private RemoteViews(@NonNull Parcel parcel, @Nullable HierarchyRootData rootData,
@Nullable ApplicationInfo info, int depth) {
if (depth > MAX_NESTED_VIEWS
@@ -4178,6 +4253,7 @@
}
mApplyFlags = parcel.readInt();
mProviderInstanceId = parcel.readLong();
+ mHasDrawInstructions = parcel.readBoolean();
// Ensure that all descendants have their caches set up recursively.
if (mIsRoot) {
@@ -4254,6 +4330,8 @@
return new AttributeReflectionAction(parcel);
case SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG:
return new SetOnStylusHandwritingResponse(parcel);
+ case SET_DRAW_INSTRUCTION_TAG:
+ return new SetDrawInstructionAction(parcel);
default:
throw new ActionException("Tag " + tag + " not found");
}
@@ -4747,7 +4825,12 @@
* by a child of viewId and executed when that child is clicked
*/
public void setPendingIntentTemplate(@IdRes int viewId, PendingIntent pendingIntentTemplate) {
- addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
+ if (hasDrawInstructions()) {
+ getPendingIntentTemplate().set(viewId, pendingIntentTemplate);
+ tryAddRemoteResponse(viewId);
+ } else {
+ addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
+ }
}
/**
@@ -4768,7 +4851,12 @@
* in order to determine the on-click behavior of the view specified by viewId
*/
public void setOnClickFillInIntent(@IdRes int viewId, Intent fillInIntent) {
- setOnClickResponse(viewId, RemoteResponse.fromFillInIntent(fillInIntent));
+ if (hasDrawInstructions()) {
+ getFillInIntent().set(viewId, fillInIntent);
+ tryAddRemoteResponse(viewId);
+ } else {
+ setOnClickResponse(viewId, RemoteResponse.fromFillInIntent(fillInIntent));
+ }
}
/**
@@ -5791,6 +5879,10 @@
}
}
+ private boolean hasDrawInstructions() {
+ return mHasDrawInstructions;
+ }
+
private RemoteViews getRemoteViewsToApply(Context context) {
if (hasLandscapeAndPortraitLayouts()) {
int orientation = context.getResources().getConfiguration().orientation;
@@ -5973,6 +6065,10 @@
if (applyThemeResId != 0) {
inflationContext = new ContextThemeWrapper(inflationContext, applyThemeResId);
}
+ // If the RemoteViews contains draw instructions, just use it instead.
+ if (rv.hasDrawInstructions()) {
+ return new RemoteCanvas(inflationContext);
+ }
LayoutInflater inflater = LayoutInflater.from(context);
// Clone inflater so we load resources from correct context and
@@ -6236,7 +6332,7 @@
/** @hide */
public boolean canRecycleView(@Nullable View v) {
- if (v == null) {
+ if (v == null || hasDrawInstructions()) {
return false;
}
Integer previousLayoutId = (Integer) v.getTag(R.id.widget_frame);
@@ -6388,6 +6484,32 @@
return context;
}
+ @NonNull
+ private SparseArray<PendingIntent> getPendingIntentTemplate() {
+ if (mPendingIntentTemplate == null) {
+ mPendingIntentTemplate = new SparseArray<>();
+ }
+ return mPendingIntentTemplate;
+ }
+
+ @NonNull
+ private SparseArray<Intent> getFillInIntent() {
+ if (mFillInIntent == null) {
+ mFillInIntent = new SparseArray<>();
+ }
+ return mFillInIntent;
+ }
+
+ private void tryAddRemoteResponse(final int viewId) {
+ final PendingIntent pendingIntent = getPendingIntentTemplate().get(viewId);
+ final Intent intent = getFillInIntent().get(viewId);
+ if (pendingIntent != null && intent != null) {
+ addAction(new SetOnClickResponse(viewId,
+ RemoteResponse.fromPendingIntentTemplateAndFillInIntent(
+ pendingIntent, intent)));
+ }
+ }
+
/**
* Utility class to hold all the options when applying the remote views
* @hide
@@ -6624,6 +6746,7 @@
}
dest.writeInt(mApplyFlags);
dest.writeLong(mProviderInstanceId);
+ dest.writeBoolean(mHasDrawInstructions);
dest.restoreAllowSquashing(prevSquashingAllowed);
}
@@ -6926,6 +7049,14 @@
return response;
}
+ private static RemoteResponse fromPendingIntentTemplateAndFillInIntent(
+ @NonNull final PendingIntent pendingIntent, @NonNull final Intent intent) {
+ RemoteResponse response = new RemoteResponse();
+ response.mPendingIntent = pendingIntent;
+ response.mFillIntent = intent;
+ return response;
+ }
+
/**
* Adds a shared element to be transferred as part of the transition between Activities
* using cross-Activity scene animations. The position of the first element will be used as
@@ -6964,8 +7095,8 @@
private void writeToParcel(Parcel dest, int flags) {
PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest);
- if (mPendingIntent == null) {
- // Only write the intent if pending intent is null
+ dest.writeBoolean((mFillIntent != null));
+ if (mFillIntent != null) {
dest.writeTypedObject(mFillIntent, flags);
}
dest.writeInt(mInteractionType);
@@ -6975,9 +7106,7 @@
private void readFromParcel(Parcel parcel) {
mPendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
- if (mPendingIntent == null) {
- mFillIntent = parcel.readTypedObject(Intent.CREATOR);
- }
+ mFillIntent = parcel.readBoolean() ? parcel.readTypedObject(Intent.CREATOR) : null;
mInteractionType = parcel.readInt();
int[] viewIds = parcel.createIntArray();
mViewIds = viewIds == null ? null : IntArray.wrap(viewIds);
@@ -7054,7 +7183,7 @@
/** @hide */
public Pair<Intent, ActivityOptions> getLaunchOptions(View view) {
- Intent intent = mPendingIntent != null ? new Intent() : new Intent(mFillIntent);
+ Intent intent = mFillIntent == null ? new Intent() : new Intent(mFillIntent);
intent.setSourceBounds(getSourceBounds(view));
if (view instanceof CompoundButton
@@ -7413,6 +7542,98 @@
}
/**
+ * A data parcel that carries the instructions to draw the RemoteViews, as an alternative to
+ * XML layout.
+ */
+ @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+ public static final class DrawInstructions {
+
+ @NonNull
+ private final List<byte[]> mInstructions;
+
+ private DrawInstructions() {
+ throw new UnsupportedOperationException(
+ "DrawInstructions cannot be instantiate without instructions");
+ }
+
+ private DrawInstructions(@NonNull List<byte[]> instructions) {
+ // Create and retain an immutable copy of given instructions.
+ mInstructions = new ArrayList<>(instructions.size());
+ for (byte[] instruction : instructions) {
+ final int len = instruction.length;
+ final byte[] target = new byte[len];
+ System.arraycopy(instruction, 0, target, 0, len);
+ mInstructions.add(target);
+ }
+ }
+
+ @Nullable
+ private static DrawInstructions readFromParcel(@NonNull final Parcel in) {
+ int size = in.readInt();
+ if (size == -1) {
+ return null;
+ }
+ byte[] instruction;
+ final List<byte[]> instructions = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ instruction = new byte[in.readInt()];
+ in.readByteArray(instruction);
+ instructions.add(instruction);
+ }
+ return new DrawInstructions(instructions);
+ }
+ private static void writeToParcel(@Nullable final DrawInstructions drawInstructions,
+ @NonNull final Parcel dest, final int flags) {
+ if (drawInstructions == null) {
+ dest.writeInt(-1);
+ return;
+ }
+ final List<byte[]> instructions = drawInstructions.mInstructions;
+ dest.writeInt(instructions.size());
+ for (byte[] instruction : instructions) {
+ dest.writeInt(instruction.length);
+ dest.writeByteArray(instruction);
+ }
+ }
+
+ /**
+ * Append additional instructions to this {@link DrawInstructions} object.
+ */
+ @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+ public void appendInstructions(@NonNull final byte[] instructions) {
+ mInstructions.add(instructions);
+ }
+
+ /**
+ * Builder class for {@link DrawInstructions} objects.
+ */
+ @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+ public static final class Builder {
+
+ private final List<byte[]> mInstructions;
+
+ /**
+ * Constructor.
+ *
+ * @param instructions Information to draw the RemoteViews.
+ */
+ @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+ public Builder(@NonNull final List<byte[]> instructions) {
+ mInstructions = new ArrayList<>(instructions);
+ }
+
+ /**
+ * Creates a {@link DrawInstructions} instance.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+ public DrawInstructions build() {
+ return new DrawInstructions(mInstructions);
+ }
+ }
+ }
+
+ /**
* Get the ID of the top-level view of the XML layout, if set using
* {@link RemoteViews#RemoteViews(String, int, int)}.
*/
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index c20b278..7f5331b 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -167,6 +167,11 @@
+ ", reported config=" + currentConfig
+ ", updated config=" + newConfig);
}
+ // Update display first. In case callers want to obtain display information(
+ // ex: DisplayMetrics) in #onConfigurationChanged callback.
+ if (displayChanged) {
+ context.updateDisplay(newDisplayId);
+ }
if (shouldUpdateResources) {
// TODO(ag/9789103): update resource manager logic to track non-activity tokens
mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
@@ -195,9 +200,6 @@
}
}
}
- if (displayChanged) {
- context.updateDisplay(newDisplayId);
- }
}
/**
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
index 5d82d04..12aff1c 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
@@ -29,6 +29,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.multiuser.Flags;
import android.os.Build;
import android.os.PatternMatcher;
import android.util.Slog;
@@ -126,6 +127,10 @@
.setFlags(provider.getFlags() | flag(ProviderInfo.FLAG_SINGLE_USER,
R.styleable.AndroidManifestProvider_singleUser, sa));
+ if (Flags.enableSystemUserOnlyForServicesAndProviders()) {
+ provider.setFlags(provider.getFlags() | flag(ProviderInfo.FLAG_SYSTEM_USER_ONLY,
+ R.styleable.AndroidManifestProvider_systemUserOnly, sa));
+ }
visibleToEphemeral = sa.getBoolean(
R.styleable.AndroidManifestProvider_visibleToInstantApps, false);
if (visibleToEphemeral) {
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
index a1dd19a3..4ac542f8 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
@@ -29,6 +29,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.multiuser.Flags;
import android.os.Build;
import com.android.internal.R;
@@ -105,6 +106,11 @@
| flag(ServiceInfo.FLAG_SINGLE_USER,
R.styleable.AndroidManifestService_singleUser, sa)));
+ if (Flags.enableSystemUserOnlyForServicesAndProviders()) {
+ service.setFlags(service.getFlags() | flag(ServiceInfo.FLAG_SYSTEM_USER_ONLY,
+ R.styleable.AndroidManifestService_systemUserOnly, sa));
+ }
+
visibleToEphemeral = sa.getBoolean(
R.styleable.AndroidManifestService_visibleToInstantApps, false);
if (visibleToEphemeral) {
diff --git a/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl b/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl
new file mode 100644
index 0000000..25e3003
--- /dev/null
+++ b/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl
@@ -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.internal.widget;
+
+/**
+ * Callback interface between LockSettingService and other system services to be notified about the
+ * state of primary authentication (i.e. PIN/pattern/password).
+ * @hide
+ */
+oneway interface ILockSettingsStateListener {
+ /**
+ * Defines behavior in response to a successful authentication
+ * @param userId The user Id for the requested authentication
+ */
+ void onAuthenticationSucceeded(int userId);
+
+ /**
+ * Defines behavior in response to a failed authentication
+ * @param userId The user Id for the requested authentication
+ */
+ void onAuthenticationFailed(int userId);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 8114e1f..627e877 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -166,4 +166,16 @@
* Refreshes pending strong auth timeout with the latest admin requirement set by device policy.
*/
public abstract void refreshStrongAuthTimeout(int userId);
+
+ /**
+ * Register a LockSettingsStateListener
+ * @param listener The listener to be registered
+ */
+ public abstract void registerLockSettingsStateListener(ILockSettingsStateListener listener);
+
+ /**
+ * Unregister a LockSettingsStateListener
+ * @param listener The listener to be unregistered
+ */
+ public abstract void unregisterLockSettingsStateListener(ILockSettingsStateListener listener);
}
diff --git a/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java
index ce9ab82..2ff6225 100644
--- a/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java
+++ b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java
@@ -21,6 +21,7 @@
import android.app.backup.BackupDataInputStream;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupHelper;
+import android.app.backup.BackupHelperWithLogger;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SyncAdapterType;
@@ -56,7 +57,7 @@
* sync settings are backed up as a JSON object containing all the necessary information for
* restoring the sync settings later.
*/
-public class AccountSyncSettingsBackupHelper implements BackupHelper {
+public class AccountSyncSettingsBackupHelper extends BackupHelperWithLogger {
private static final String TAG = "AccountSyncSettingsBackupHelper";
private static final boolean DEBUG = false;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 52cf679..100259e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2961,7 +2961,7 @@
<p>Protection level: signature
@SystemApi
@hide
- @FlaggedApi("com.android.internal.telephony.flags.ap_domain_selection_enabled")
+ @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service")
-->
<permission android:name="android.permission.BIND_DOMAIN_SELECTION_SERVICE"
android:protectionLevel="signature" />
@@ -3775,6 +3775,13 @@
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL"
android:protectionLevel="internal|role" />
+ <!-- Allows an application to access EnhancedConfirmationManager.
+ @SystemApi
+ @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled")
+ @hide This is not a third-party API (intended for OEMs and system apps). -->
+ <permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES"
+ android:protectionLevel="signature|installer" />
+
<!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.-->
<permission android:name="android.permission.PROVISION_DEMO_DEVICE"
android:protectionLevel="signature|setup|knownSigner"
@@ -5737,6 +5744,14 @@
android:description="@string/permdesc_observeCompanionDevicePresence"
android:protectionLevel="normal" />
+ <!-- Allows an application to subscribe to notifications about the nearby devices' presence
+ status change base on the UUIDs.
+ <p>Not for use by third-party applications.</p>
+ @FlaggedApi("android.companion.flags.device_presence")
+ -->
+ <permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows an application to deliver companion messages to system
-->
<permission android:name="android.permission.DELIVER_COMPANION_MESSAGES"
@@ -7946,11 +7961,11 @@
<!-- @SystemApi Allows an application to read the system grammatical gender.
@FlaggedApi("android.app.system_terms_of_address_enabled")
- <p>Protection level: signature|privileged|appop
+ <p>Protection level: signature|privileged
@hide
-->
<permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"
- android:protectionLevel="signature|privileged|appop"/>
+ android:protectionLevel="signature|privileged"/>
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
diff --git a/core/res/res/drawable/autofill_half_sheet_divider.xml b/core/res/res/drawable/autofill_half_sheet_divider.xml
new file mode 100644
index 0000000..1a96c7d
--- /dev/null
+++ b/core/res/res/drawable/autofill_half_sheet_divider.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copied from //frameworks/base/core/res/res/drawable/list_divider_material.xml. -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="@color/foreground_material_light">
+ <solid android:color="#1f000000" />
+ <size
+ android:height="1dp"
+ android:width="1dp"/>
+</shape>
\ No newline at end of file
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index 27f8138..ddedca2 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -31,11 +31,11 @@
android:gravity="center_horizontal"
android:orientation="vertical">
<ScrollView
+ android:id="@+id/autofill_sheet_scroll_view"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:fillViewport="true"
- android:layout_weight="1"
- android:layout_marginBottom="8dp">
+ android:layout_weight="1">
<LinearLayout
android:layout_marginStart="@dimen/autofill_save_outer_margin"
android:layout_marginEnd="@dimen/autofill_save_outer_margin"
@@ -66,16 +66,25 @@
android:layout_height="wrap_content"
android:minHeight="0dp"
android:visibility="gone"/>
-
+ <View
+ android:id="@+id/autofill_sheet_scroll_view_space"
+ android:layout_width="match_parent"
+ android:layout_height="16dp"/>
</LinearLayout>
</ScrollView>
+ <View
+ android:id="@+id/autofill_sheet_divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ style="@style/AutofillHalfSheetDivider" />
+
<com.android.internal.widget.ButtonBarLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="end"
android:clipToPadding="false"
- android:layout_marginTop="16dp"
+ android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton"
android:orientation="horizontal"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 29086a45..35276bf 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -506,6 +506,12 @@
receivers, and providers; it can not be used with activities. -->
<attr name="singleUser" format="boolean" />
+ <!-- If set to true, only a single instance of this component will
+ run and be available for the SYSTEM user. Non SYSTEM users will not be
+ allowed to access the component if this flag is enabled.
+ This flag can be used with services, receivers, providers and activities. -->
+ <attr name="systemUserOnly" format="boolean" />
+
<!-- Specify a specific process that the associated code is to run in.
Use with the application tag (to supply a default process for all
application components), or with the activity, receiver, service,
@@ -2865,6 +2871,7 @@
Context.createAttributionContext() using the first attribution tag
contained here. -->
<attr name="attributionTags" />
+ <attr name="systemUserOnly" format="boolean" />
</declare-styleable>
<!-- Attributes that can be supplied in an AndroidManifest.xml
@@ -3023,6 +3030,7 @@
ignored when the process is bound into a shared isolated process by a client.
-->
<attr name="allowSharedIsolatedProcess" format="boolean" />
+ <attr name="systemUserOnly" format="boolean" />
</declare-styleable>
<!-- @hide The <code>apex-system-service</code> tag declares an apex system service
@@ -3150,7 +3158,7 @@
<attr name="uiOptions" />
<attr name="parentActivityName" />
<attr name="singleUser" />
- <!-- @hide This broadcast receiver or activity will only receive broadcasts for the
+ <!-- This broadcast receiver or activity will only receive broadcasts for the
system user-->
<attr name="systemUserOnly" format="boolean" />
<attr name="persistableMode" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 916a4ea..0d1a987 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6901,4 +6901,11 @@
<!-- Defines suitability of the built-in speaker route.
Refer to {@link MediaRoute2Info} to see supported values. -->
<integer name="config_mediaRouter_builtInSpeakerSuitability">0</integer>
+
+ <!-- Whether to show a percentage text next to the progressbar while preparing to update the
+ device -->
+ <bool name="config_showPercentageTextDuringRebootToUpdate">true</bool>
+
+ <!-- Defines the minimum interval (in ms) between two input-based user-activity poke events. -->
+ <integer name="config_minMillisBetweenInputUserActivityEvents">100</integer>
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 7d22885..b8fc052 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -121,6 +121,8 @@
<public name="adServiceTypes" />
<!-- @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") -->
<public name="featureFlag"/>
+ <!-- @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") -->
+ <public name="systemUserOnly"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 619ec31..22d028c 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1515,6 +1515,11 @@
<item name="background">@drawable/btn_outlined</item>
</style>
+ <!-- @hide Divider for Autofill half screen dialog -->
+ <style name="AutofillHalfSheetDivider">
+ <item name="android:background">@drawable/autofill_half_sheet_divider</item>
+ </style>
+
<!-- @hide Autofill background for popup window (not for fullscreen) -->
<style name="AutofillDatasetPicker">
<item name="elevation">4dp</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 14755be..3c1a42f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3704,6 +3704,10 @@
<java-symbol type="id" name="autofill_dataset_list"/>
<java-symbol type="id" name="autofill_dataset_picker"/>
<java-symbol type="id" name="autofill_dataset_title" />
+ <java-symbol type="id" name="autofill_sheet_divider"/>
+ <java-symbol type="id" name="autofill_sheet_scroll_view"/>
+ <java-symbol type="id" name="autofill_sheet_scroll_view_space"/>
+
<java-symbol type="id" name="autofill_save_custom_subtitle" />
<java-symbol type="id" name="autofill_save_icon" />
<java-symbol type="id" name="autofill_save_no" />
@@ -5313,4 +5317,9 @@
<!-- Android MediaRouter framework configs. -->
<java-symbol type="integer" name="config_mediaRouter_builtInSpeakerSuitability" />
+
+ <!-- Shutdown thread config flags -->
+ <java-symbol type="bool" name="config_showPercentageTextDuringRebootToUpdate" />
+
+ <java-symbol type="integer" name="config_minMillisBetweenInputUserActivityEvents" />
</resources>
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 15c9047..543d73b 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.appwidget.flags.Flags.drawDataParcel;
+
import static com.android.internal.R.id.pending_intent_tag;
import static org.junit.Assert.assertArrayEquals;
@@ -63,6 +65,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
@@ -414,6 +417,48 @@
assertNotNull(view.findViewById(R.id.light_background_text));
}
+ @Test
+ public void remoteCanvasCanAccessDrawInstructions() {
+ if (!drawDataParcel()) {
+ return;
+ }
+ final RemoteViews.DrawInstructions drawInstructions = getDrawInstructions();
+ final RemoteViews rv = new RemoteViews(drawInstructions);
+ final View view = rv.apply(mContext, mContainer);
+ assertTrue(view instanceof RemoteCanvas);
+ assertEquals(drawInstructions, view.getTag());
+ }
+
+ @Test
+ public void remoteCanvasWiresClickHandlers() {
+ if (!drawDataParcel()) {
+ return;
+ }
+ final RemoteViews.DrawInstructions drawInstructions = getDrawInstructions();
+ final RemoteViews rv = new RemoteViews(drawInstructions);
+ final PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+ new Intent(Intent.ACTION_VIEW), PendingIntent.FLAG_IMMUTABLE);
+ final Intent i = new Intent().putExtra("TEST", "Success");
+ final int viewId = 1;
+ rv.setPendingIntentTemplate(viewId, pi);
+ rv.setOnClickFillInIntent(viewId, i);
+ final View view = rv.apply(mContext, mContainer);
+ assertTrue(view instanceof RemoteCanvas);
+ RemoteCanvas target = (RemoteCanvas) view;
+ assertEquals(1, target.getCallbacks().size());
+ assertNotNull(target.getCallbacks().get(viewId));
+ }
+
+ private RemoteViews.DrawInstructions getDrawInstructions() {
+ final byte[] first = new byte[] {'f', 'i', 'r', 's', 't'};
+ final byte[] second = new byte[] {'s', 'e', 'c', 'o', 'n', 'd'};
+ final RemoteViews.DrawInstructions drawInstructions =
+ new RemoteViews.DrawInstructions.Builder(
+ Collections.singletonList(first)).build();
+ drawInstructions.appendInstructions(second);
+ return drawInstructions;
+ }
+
private RemoteViews createViewChained(int depth, String... texts) {
RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host);
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 294b8ae..2de305f 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -421,6 +421,8 @@
<permission name="android.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING" />
<permission name="android.permission.REQUEST_COMPANION_PROFILE_COMPUTER" />
<permission name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
+ <permission name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE" />
+
<!-- Permission required for testing registering pull atom callbacks. -->
<permission name="android.permission.REGISTER_STATS_PULL_ATOM"/>
<!-- Permission required for testing system audio effect APIs. -->
@@ -637,4 +639,8 @@
<permission name="com.android.voicemail.permission.READ_VOICEMAIL"/>
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
+
+ <privapp-permissions package="com.android.devicediagnostics">
+ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ </privapp-permissions>
</permissions>
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index 471acaa..f1a6b69 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -80,3 +80,9 @@
},
},
}
+
+/////////////////////////////////
+// Move `fontchain_lint` to `core/tasks/fontchain_lint.mk`.
+// Because `system.img` is a dependency of `fontchain_lint`, it cannot be
+// converted to Android.bp for now.
+// After system.img can be generated by Soong, then it can be converted to Android.bp.
diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk
deleted file mode 100644
index a322b82..0000000
--- a/data/fonts/Android.mk
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright (C) 2011 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.
-
-LOCAL_PATH := $(call my-dir)
-
-# Run sanity tests on fonts on checkbuild
-checkbuild: fontchain_lint
-
-FONTCHAIN_LINTER := $(HOST_OUT_EXECUTABLES)/fontchain_linter
-ifeq ($(MINIMAL_FONT_FOOTPRINT),true)
-CHECK_EMOJI := false
-else
-CHECK_EMOJI := true
-endif
-
-fontchain_lint_timestamp := $(call intermediates-dir-for,PACKAGING,fontchain_lint)/stamp
-
-.PHONY: fontchain_lint
-fontchain_lint: $(fontchain_lint_timestamp)
-
-fontchain_lint_deps := \
- external/unicode/DerivedAge.txt \
- external/unicode/emoji-data.txt \
- external/unicode/emoji-sequences.txt \
- external/unicode/emoji-variation-sequences.txt \
- external/unicode/emoji-zwj-sequences.txt \
- external/unicode/additions/emoji-data.txt \
- external/unicode/additions/emoji-sequences.txt \
- external/unicode/additions/emoji-zwj-sequences.txt \
-
-$(fontchain_lint_timestamp): $(FONTCHAIN_LINTER) $(TARGET_OUT)/etc/fonts.xml $(PRODUCT_OUT)/system.img $(fontchain_lint_deps)
- @echo Running fontchain lint
- $(FONTCHAIN_LINTER) $(TARGET_OUT) $(CHECK_EMOJI) external/unicode
- touch $@
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index d659ddd..4e88b0e 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -607,7 +607,8 @@
}
@Override
- public final void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) {
+ public final void drawMesh(@NonNull Mesh mesh, @Nullable BlendMode blendMode,
+ @NonNull Paint paint) {
if (blendMode == null) {
blendMode = BlendMode.MODULATE;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 83d555c..b2e5b75 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -17,6 +17,7 @@
package androidx.window.extensions.embedding;
import static android.app.ActivityManager.START_SUCCESS;
+import static android.app.ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -41,10 +42,10 @@
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
-import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
+import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
import android.annotation.CallbackExecutor;
@@ -110,6 +111,10 @@
static final boolean ENABLE_SHELL_TRANSITIONS =
SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
+ // TODO(b/295993745): remove after prebuilt library is updated.
+ private static final String KEY_ACTIVITY_STACK_TOKEN =
+ "androidx.window.extensions.embedding.ActivityStackToken";
+
@VisibleForTesting
@GuardedBy("mLock")
final SplitPresenter mPresenter;
@@ -569,7 +574,8 @@
final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
final WindowContainerTransaction wct = transactionRecord.getTransaction();
- mPresenter.applyActivityStackAttributes(wct, container, attributes);
+ mPresenter.applyActivityStackAttributes(wct, container, attributes,
+ container.getMinDimensions());
transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
@@ -1562,7 +1568,8 @@
private TaskFragmentContainer createEmptyExpandedContainer(
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@Nullable Activity launchingActivity) {
- return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity,
+ return createEmptyContainer(wct, intent, taskId,
+ new ActivityStackAttributes.Builder().build(), launchingActivity,
null /* overlayTag */, null /* launchOptions */);
}
@@ -1576,8 +1583,9 @@
@Nullable
TaskFragmentContainer createEmptyContainer(
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
- @NonNull Rect bounds, @Nullable Activity launchingActivity,
- @Nullable String overlayTag, @Nullable Bundle launchOptions) {
+ @NonNull ActivityStackAttributes activityStackAttributes,
+ @Nullable Activity launchingActivity, @Nullable String overlayTag,
+ @Nullable Bundle launchOptions) {
// We need an activity in the organizer process in the same Task to use as the owner
// activity, as well as to get the Task window info.
final Activity activityInTask;
@@ -1600,43 +1608,21 @@
// Note that taskContainer will not exist before calling #newContainer if the container
// is the first embedded TF in the task.
final TaskContainer taskContainer = container.getTaskContainer();
- final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics().getBounds();
- final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+ // TODO(b/265271880): remove redundant logic after all TF operations take fragmentToken.
+ final Rect taskBounds = taskContainer.getBounds();
+ final Rect sanitizedBounds = sanitizeBounds(activityStackAttributes.getRelativeBounds(),
+ getMinDimensions(intent), taskBounds);
final int windowingMode = taskContainer
.getWindowingModeForTaskFragment(sanitizedBounds);
mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
sanitizedBounds, windowingMode);
- mPresenter.updateAnimationParams(wct, taskFragmentToken,
- TaskFragmentAnimationParams.DEFAULT);
- mPresenter.setTaskFragmentIsolatedNavigation(wct, taskFragmentToken,
- overlayTag != null && !sanitizedBounds.isEmpty());
+ mPresenter.applyActivityStackAttributes(wct, container, activityStackAttributes,
+ getMinDimensions(intent));
return container;
}
/**
- * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
- * covered by the task bounds. Otherwise, returns {@code bounds}.
- */
- @NonNull
- private static Rect sanitizeBounds(@NonNull Rect bounds, @NonNull Intent intent,
- @NonNull Rect taskBounds) {
- if (bounds.isEmpty()) {
- // Don't need to check if the bounds follows the task bounds.
- return bounds;
- }
- if (boundsSmallerThanMinDimensions(bounds, getMinDimensions(intent))) {
- // Expand the bounds if the bounds are smaller than minimum dimensions.
- return new Rect();
- }
- if (!taskBounds.contains(bounds)) {
- // Expand the bounds if the bounds exceed the task bounds.
- return new Rect();
- }
- return bounds;
- }
-
- /**
* Returns a container for the new activity intent to launch into as splitting with the primary
* activity.
*/
@@ -1953,6 +1939,12 @@
return;
}
+ if (mActivityStackAttributesCalculator == null) {
+ Log.e(TAG, "ActivityStackAttributesCalculator is not set. Thus the overlay container"
+ + " can not be updated.");
+ return;
+ }
+
if (mActivityStackAttributesCalculator != null) {
final ActivityStackAttributesCalculatorParams params =
new ActivityStackAttributesCalculatorParams(
@@ -1962,7 +1954,8 @@
container.getLaunchOptions());
final ActivityStackAttributes attributes = mActivityStackAttributesCalculator
.apply(params);
- mPresenter.applyActivityStackAttributes(wct, container, attributes);
+ mPresenter.applyActivityStackAttributes(wct, container, attributes,
+ container.getMinDimensions());
}
}
@@ -2598,15 +2591,15 @@
mPresenter.createParentContainerInfoFromTaskProperties(
mPresenter.getTaskProperties(launchActivity)), overlayTag, options);
// Fallback to expand the bounds if there's no activityStackAttributes calculator.
- final Rect relativeBounds = mActivityStackAttributesCalculator != null
- ? new Rect(mActivityStackAttributesCalculator.apply(params).getRelativeBounds())
- : new Rect();
- final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(relativeBounds,
- getMinDimensions(intent));
- // Expand the bounds if the requested bounds are smaller than minimum dimensions.
- if (shouldExpandContainer) {
- relativeBounds.setEmpty();
+ final ActivityStackAttributes attrs;
+ if (mActivityStackAttributesCalculator != null) {
+ attrs = mActivityStackAttributesCalculator.apply(params);
+ } else {
+ attrs = new ActivityStackAttributes.Builder().build();
+ Log.e(TAG, "ActivityStackAttributesCalculator isn't set. Fallback to set overlay "
+ + "container as expected.");
}
+
final int taskId = getTaskId(launchActivity);
if (!overlayContainers.isEmpty()) {
for (final TaskFragmentContainer overlayContainer : overlayContainers) {
@@ -2626,20 +2619,8 @@
}
if (overlayTag.equals(overlayContainer.getOverlayTag())
&& taskId == overlayContainer.getTaskId()) {
- // If there's an overlay container with the same tag and task ID, we treat
- // the OverlayCreateParams as the update to the container.
- final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
- final TaskContainer taskContainer = overlayContainer.getTaskContainer();
- final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics()
- .getBounds();
- final Rect sanitizedBounds = sanitizeBounds(relativeBounds, intent, taskBounds);
-
- mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
- final int windowingMode = taskContainer
- .getWindowingModeForTaskFragment(sanitizedBounds);
- mPresenter.updateWindowingMode(wct, overlayToken, windowingMode);
- mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayContainer,
- !sanitizedBounds.isEmpty());
+ mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
+ getMinDimensions(intent));
// We can just return the updated overlay container and don't need to
// check other condition since we only have one OverlayCreateParams, and
// if the tag and task are matched, it's impossible to match another task
@@ -2649,7 +2630,7 @@
}
}
// Launch the overlay container to the task with taskId.
- return createEmptyContainer(wct, intent, taskId, relativeBounds, launchActivity, overlayTag,
+ return createEmptyContainer(wct, intent, taskId, attrs, launchActivity, overlayTag,
options);
}
@@ -2779,8 +2760,17 @@
// TODO(b/232042367): Consolidate the activity create handling so that we can handle
// cross-process the same as normal.
+ IBinder activityStackToken = options.getBinder(KEY_ACTIVITY_STACK_TOKEN);
+ if (activityStackToken != null) {
+ // Put activityStack token to #KEY_LAUNCH_TASK_FRAGMENT_TOKEN to launch the activity
+ // into the taskFragment associated with the token.
+ options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, activityStackToken);
+ }
+
// Early return if the launching taskfragment is already been set.
- if (options.getBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN) != null) {
+ // TODO(b/295993745): Use KEY_LAUNCH_TASK_FRAGMENT_TOKEN after WM Jetpack migrates to
+ // bundle. This is still needed to support #setLaunchingActivityStack.
+ if (options.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN) != null) {
synchronized (mLock) {
mCurrentIntent = intent;
}
@@ -2837,7 +2827,7 @@
// Amend the request to let the WM know that the activity should be placed in
// the dedicated container.
// TODO(b/229680885): skip override launching TaskFragment token by split-rule
- options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
+ options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
launchedInTaskFragment.getTaskFragmentToken());
mCurrentIntent = intent;
} else {
@@ -2855,8 +2845,7 @@
if (mCurrentIntent != null && result != START_SUCCESS) {
// Clear the pending appeared intent if the activity was not started
// successfully.
- final IBinder token = bOptions.getBinder(
- ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
+ final IBinder token = bOptions.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
if (token != null) {
final TaskFragmentContainer container = getContainer(token);
if (container != null) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 8b7fd10..2f2da8c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -16,8 +16,6 @@
package androidx.window.extensions.embedding;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.PackageManager.MATCH_ALL;
import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
@@ -426,7 +424,8 @@
* creation has not been reported from the server yet.
*/
// TODO(b/190433398): Handle resize if the fragment hasn't appeared yet.
- private void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct,
+ @VisibleForTesting
+ void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container,
@Nullable Rect relBounds) {
if (container.getInfo() == null) {
@@ -435,7 +434,8 @@
resizeTaskFragment(wct, container.getTaskFragmentToken(), relBounds);
}
- private void updateTaskFragmentWindowingModeIfRegistered(
+ @VisibleForTesting
+ void updateTaskFragmentWindowingModeIfRegistered(
@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container,
@WindowingMode int windowingMode) {
@@ -579,13 +579,53 @@
super.setCompanionTaskFragment(wct, primary, secondary);
}
- void applyActivityStackAttributes(@NonNull WindowContainerTransaction wct,
- @NonNull TaskFragmentContainer container, @NonNull ActivityStackAttributes attributes) {
- final Rect bounds = attributes.getRelativeBounds();
+ void applyActivityStackAttributes(
+ @NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container,
+ @NonNull ActivityStackAttributes attributes,
+ @Nullable Size minDimensions) {
+ final Rect taskBounds = container.getTaskContainer().getBounds();
+ final Rect relativeBounds = sanitizeBounds(attributes.getRelativeBounds(), minDimensions,
+ taskBounds);
+ final boolean isFillParent = relativeBounds.isEmpty();
+ final boolean isIsolatedNavigated = !isFillParent && container.isOverlay();
+ final boolean dimOnTask = !isFillParent
+ && attributes.getWindowAttributes().getDimArea() == DIM_AREA_ON_TASK
+ && Flags.fullscreenDimFlag();
+ final IBinder fragmentToken = container.getTaskFragmentToken();
- resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds);
- updateWindowingMode(wct, container.getTaskFragmentToken(),
- bounds.isEmpty() ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_MULTI_WINDOW);
+ // TODO(b/243518738): Update to resizeTaskFragment after we migrate WCT#setRelativeBounds
+ // and WCT#setWindowingMode to take fragmentToken.
+ resizeTaskFragmentIfRegistered(wct, container, relativeBounds);
+ int windowingMode = container.getTaskContainer().getWindowingModeForTaskFragment(
+ relativeBounds);
+ updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
+ // Always use default animation for standalone ActivityStack.
+ updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
+ setTaskFragmentIsolatedNavigation(wct, container, isIsolatedNavigated);
+ setTaskFragmentDimOnTask(wct, fragmentToken, dimOnTask);
+ }
+
+ /**
+ * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
+ * covered by the task bounds. Otherwise, returns {@code bounds}.
+ */
+ @NonNull
+ static Rect sanitizeBounds(@NonNull Rect bounds, @Nullable Size minDimension,
+ @NonNull Rect taskBounds) {
+ if (bounds.isEmpty()) {
+ // Don't need to check if the bounds follows the task bounds.
+ return bounds;
+ }
+ if (boundsSmallerThanMinDimensions(bounds, minDimension)) {
+ // Expand the bounds if the bounds are smaller than minimum dimensions.
+ return new Rect();
+ }
+ if (!taskBounds.contains(bounds)) {
+ // Expand the bounds if the bounds exceed the task bounds.
+ return new Rect();
+ }
+ return bounds;
}
@Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 71195b6..73109e2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -126,6 +126,11 @@
}
@NonNull
+ Rect getBounds() {
+ return mConfiguration.windowConfiguration.getBounds();
+ }
+
+ @NonNull
TaskProperties getTaskProperties() {
return new TaskProperties(mDisplayId, mConfiguration);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 4e7b760..34d43ad 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -16,14 +16,17 @@
package androidx.window.extensions.embedding;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
+import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -36,7 +39,6 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -56,6 +58,8 @@
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.Size;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
@@ -266,62 +270,21 @@
}
@Test
- public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_smallerThanMinDimens_expandOverlay() {
+ public void testSanitizeBounds_smallerThanMinDimens_expandOverlay() {
mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
MinimumDimensionActivity.class));
-
final Rect bounds = new Rect(0, 0, 100, 100);
- mSplitController.setActivityStackAttributesCalculator(params ->
- new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
- final TaskFragmentContainer overlayContainer =
- createOrUpdateOverlayTaskFragmentIfNeeded("test");
- final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
- .containsExactly(overlayContainer);
- assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
- false);
-
- // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
- clearInvocations(mSplitPresenter);
- createOrUpdateOverlayTaskFragmentIfNeeded("test");
-
- verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
- verify(mSplitPresenter).updateWindowingMode(mTransaction, overlayToken,
- WINDOWING_MODE_UNDEFINED);
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayContainer,
- false);
+ SplitPresenter.sanitizeBounds(bounds, SplitPresenter.getMinDimensions(mIntent),
+ TASK_BOUNDS);
}
@Test
- public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() {
+ public void testSanitizeBounds_notInTaskBounds_expandOverlay() {
final Rect bounds = new Rect(TASK_BOUNDS);
bounds.offset(10, 10);
- mSplitController.setActivityStackAttributesCalculator(params ->
- new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
- final TaskFragmentContainer overlayContainer =
- createOrUpdateOverlayTaskFragmentIfNeeded("test");
- final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
-
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
- .containsExactly(overlayContainer);
- assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
- false);
-
- // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
- clearInvocations(mSplitPresenter);
- createOrUpdateOverlayTaskFragmentIfNeeded("test");
-
- verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
- verify(mSplitPresenter).updateWindowingMode(mTransaction,
- overlayToken, WINDOWING_MODE_UNDEFINED);
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction,
- overlayContainer, false);
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
- .containsExactly(overlayContainer);
+ SplitPresenter.sanitizeBounds(bounds, null, TASK_BOUNDS);
}
@Test
@@ -331,6 +294,7 @@
new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
final TaskFragmentContainer overlayContainer =
createOrUpdateOverlayTaskFragmentIfNeeded("test");
+ setupTaskFragmentInfo(overlayContainer, mActivity);
assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
.containsExactly(overlayContainer);
@@ -437,7 +401,7 @@
assertThrows(NullPointerException.class, () ->
mSplitController.updateActivityStackAttributes(new Binder(), null));
- verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+ verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
}
@Test
@@ -447,7 +411,7 @@
mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
new ActivityStackAttributes.Builder().build());
- verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+ verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
}
@Test
@@ -457,19 +421,20 @@
mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
new ActivityStackAttributes.Builder().build());
- verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+ verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
}
@Test
public void testUpdateActivityStackAttributes() {
final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test");
- doNothing().when(mSplitPresenter).applyActivityStackAttributes(any(), any(), any());
+ doNothing().when(mSplitPresenter).applyActivityStackAttributes(any(), any(), any(), any());
final ActivityStackAttributes attrs = new ActivityStackAttributes.Builder().build();
final IBinder token = container.getTaskFragmentToken();
mSplitController.updateActivityStackAttributes(token, attrs);
- verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs));
+ verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs),
+ any());
}
@Test
@@ -521,6 +486,89 @@
.that(taskContainer.getOverlayContainer()).isNull();
}
+ @Test
+ public void testApplyActivityStackAttributesForExpandedContainer() {
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+ final IBinder token = container.getTaskFragmentToken();
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder().build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ null /* minDimensions */);
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ attributes.getRelativeBounds());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_UNDEFINED);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+ }
+
+ @Test
+ public void testApplyActivityStackAttributesForOverlayContainer() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+ final IBinder token = container.getTaskFragmentToken();
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
+ .setRelativeBounds(new Rect(0, 0, 200, 200))
+ .setWindowAttributes(new WindowAttributes(DIM_AREA_ON_TASK))
+ .build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ null /* minDimensions */);
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ attributes.getRelativeBounds());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_MULTI_WINDOW);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true);
+ }
+
+ @Test
+ public void testApplyActivityStackAttributesForExpandedOverlayContainer() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+ final IBinder token = container.getTaskFragmentToken();
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder().build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ null /* minDimensions */);
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ attributes.getRelativeBounds());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_UNDEFINED);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+ }
+
+ @Test
+ public void testApplyActivityStackAttributesForOverlayContainer_exceedsMinDimensions() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+ final IBinder token = container.getTaskFragmentToken();
+ final Rect relativeBounds = new Rect(0, 0, 200, 200);
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
+ .setRelativeBounds(relativeBounds)
+ .setWindowAttributes(new WindowAttributes(DIM_AREA_ON_TASK))
+ .build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ new Size(relativeBounds.width() + 1, relativeBounds.height()));
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ new Rect());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_UNDEFINED);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+ }
+
/**
* A simplified version of {@link SplitController.ActivityStartMonitor
* #createOrUpdateOverlayTaskFragmentIfNeeded}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
new file mode 100644
index 0000000..4c76168
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.ComponentName
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewTaskController
+
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleTaskViewTest {
+
+ private lateinit var bubbleTaskView: BubbleTaskView
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+
+ @Before
+ fun setUp() {
+ val taskView = TaskView(context, mock<TaskViewTaskController>())
+ bubbleTaskView = BubbleTaskView(taskView, directExecutor())
+ }
+
+ @Test
+ fun onTaskCreated_updatesState() {
+ val componentName = ComponentName(context, "TestClass")
+ bubbleTaskView.listener.onTaskCreated(123, componentName)
+
+ assertThat(bubbleTaskView.taskId).isEqualTo(123)
+ assertThat(bubbleTaskView.componentName).isEqualTo(componentName)
+ assertThat(bubbleTaskView.isCreated).isTrue()
+ }
+
+ @Test
+ fun onTaskCreated_callsDelegateListener() {
+ var actualTaskId = -1
+ var actualComponentName: ComponentName? = null
+ val delegateListener = object : TaskView.Listener {
+ override fun onTaskCreated(taskId: Int, name: ComponentName) {
+ actualTaskId = taskId
+ actualComponentName = name
+ }
+ }
+ bubbleTaskView.delegateListener = delegateListener
+
+ val componentName = ComponentName(context, "TestClass")
+ bubbleTaskView.listener.onTaskCreated(123, componentName)
+
+ assertThat(actualTaskId).isEqualTo(123)
+ assertThat(actualComponentName).isEqualTo(componentName)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index f3fe895..9f7d0ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -74,7 +74,6 @@
import com.android.wm.shell.common.AlphaOptimizedButton;
import com.android.wm.shell.common.TriangleShape;
import com.android.wm.shell.taskview.TaskView;
-import com.android.wm.shell.taskview.TaskViewTaskController;
import java.io.PrintWriter;
@@ -146,7 +145,6 @@
private AlphaOptimizedButton mManageButton;
private TaskView mTaskView;
- private TaskViewTaskController mTaskViewTaskController;
private BubbleOverflowContainerView mOverflowView;
private int mTaskId = INVALID_TASK_ID;
@@ -434,7 +432,8 @@
* Initialize {@link BubbleController} and {@link BubbleStackView} here, this method must need
* to be called after view inflate.
*/
- void initialize(BubbleController controller, BubbleStackView stackView, boolean isOverflow) {
+ void initialize(BubbleController controller, BubbleStackView stackView, boolean isOverflow,
+ @Nullable BubbleTaskView bubbleTaskView) {
mController = controller;
mStackView = stackView;
mIsOverflow = isOverflow;
@@ -451,18 +450,22 @@
bringChildToFront(mOverflowView);
mManageButton.setVisibility(GONE);
} else {
- mTaskViewTaskController = new TaskViewTaskController(mContext,
- mController.getTaskOrganizer(),
- mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
- mTaskView = new TaskView(mContext, mTaskViewTaskController);
- mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener);
+ mTaskView = bubbleTaskView.getTaskView();
+ bubbleTaskView.setDelegateListener(mTaskViewListener);
// set a fixed width so it is not recalculated as part of a rotation. the width will be
// updated manually after the rotation.
FrameLayout.LayoutParams lp =
new FrameLayout.LayoutParams(getContentWidth(), MATCH_PARENT);
+ if (mTaskView.getParent() != null) {
+ ((ViewGroup) mTaskView.getParent()).removeView(mTaskView);
+ }
mExpandedViewContainer.addView(mTaskView, lp);
bringChildToFront(mTaskView);
+ if (bubbleTaskView.isCreated()) {
+ mTaskViewListener.onTaskCreated(
+ bubbleTaskView.getTaskId(), bubbleTaskView.getComponentName());
+ }
}
}
@@ -876,7 +879,7 @@
return;
}
boolean isNew = mBubble == null || didBackingContentChange(bubble);
- if (isNew || bubble != null && bubble.getKey().equals(mBubble.getKey())) {
+ if (isNew || bubble.getKey().equals(mBubble.getKey())) {
mBubble = bubble;
mManageButton.setContentDescription(getResources().getString(
R.string.bubbles_settings_button_description, bubble.getAppName()));
@@ -1107,7 +1110,8 @@
* has been removed.
*
* If this view should be reused after this method is called, then
- * {@link #initialize(BubbleController, BubbleStackView, boolean)} must be invoked first.
+ * {@link #initialize(BubbleController, BubbleStackView, boolean, BubbleTaskView)}
+ * must be invoked first.
*/
public void cleanUpExpandedState() {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 22e836a..e5d9ace 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -29,6 +29,7 @@
import android.view.LayoutInflater
import android.view.View.VISIBLE
import android.widget.FrameLayout
+import androidx.core.content.ContextCompat
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView
@@ -57,10 +58,16 @@
/** Call before use and again if cleanUpExpandedState was called. */
fun initialize(controller: BubbleController, forBubbleBar: Boolean) {
if (forBubbleBar) {
- createBubbleBarExpandedView().initialize(controller, true /* isOverflow */)
+ createBubbleBarExpandedView()
+ .initialize(controller, /* isOverflow= */ true, /* bubbleTaskView= */ null)
} else {
createExpandedView()
- .initialize(controller, controller.stackView, true /* isOverflow */)
+ .initialize(
+ controller,
+ controller.stackView,
+ /* isOverflow= */ true,
+ /* bubbleTaskView= */ null
+ )
}
}
@@ -113,7 +120,10 @@
context,
res.getDimensionPixelSize(R.dimen.bubble_size),
res.getDimensionPixelSize(R.dimen.bubble_badge_size),
- res.getColor(com.android.launcher3.icons.R.color.important_conversation),
+ ContextCompat.getColor(
+ context,
+ com.android.launcher3.icons.R.color.important_conversation
+ ),
res.getDimensionPixelSize(com.android.internal.R.dimen.importance_ring_stroke_width)
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
new file mode 100644
index 0000000..2fcd133
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.wm.shell.bubbles
+
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.content.ComponentName
+import androidx.annotation.VisibleForTesting
+import com.android.wm.shell.taskview.TaskView
+import java.util.concurrent.Executor
+
+/**
+ * A wrapper class around [TaskView] for bubble expanded views.
+ *
+ * [delegateListener] allows callers to change listeners after a task has been created.
+ */
+class BubbleTaskView(val taskView: TaskView, executor: Executor) {
+
+ /** Whether the task is already created. */
+ var isCreated = false
+ private set
+
+ /** The task id. */
+ var taskId = INVALID_TASK_ID
+ private set
+
+ /** The component name of the application running in the task. */
+ var componentName: ComponentName? = null
+ private set
+
+ /** [TaskView.Listener] for users of this class. */
+ var delegateListener: TaskView.Listener? = null
+
+ /** A [TaskView.Listener] that delegates to [delegateListener]. */
+ @get:VisibleForTesting
+ val listener = object : TaskView.Listener {
+ override fun onInitialized() {
+ delegateListener?.onInitialized()
+ }
+
+ override fun onReleased() {
+ delegateListener?.onReleased()
+ }
+
+ override fun onTaskCreated(taskId: Int, name: ComponentName) {
+ delegateListener?.onTaskCreated(taskId, name)
+ this@BubbleTaskView.taskId = taskId
+ isCreated = true
+ componentName = name
+ }
+
+ override fun onTaskVisibilityChanged(taskId: Int, visible: Boolean) {
+ delegateListener?.onTaskVisibilityChanged(taskId, visible)
+ }
+
+ override fun onTaskRemovalStarted(taskId: Int) {
+ delegateListener?.onTaskRemovalStarted(taskId)
+ }
+
+ override fun onBackPressedOnTaskRoot(taskId: Int) {
+ delegateListener?.onBackPressedOnTaskRoot(taskId)
+ }
+ }
+
+ init {
+ taskView.setListener(executor, listener)
+ }
+}
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 f6c382f..5855a81 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
@@ -35,10 +35,7 @@
import androidx.annotation.Nullable;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.taskview.TaskView;
-import com.android.wm.shell.taskview.TaskViewTaskController;
/**
* Handles creating and updating the {@link TaskView} associated with a {@link Bubble}.
@@ -65,7 +62,6 @@
private final Context mContext;
private final BubbleController mController;
- private final @ShellMainThread ShellExecutor mMainExecutor;
private final BubbleTaskViewHelper.Listener mListener;
private final View mParentView;
@@ -73,7 +69,6 @@
private Bubble mBubble;
@Nullable
private PendingIntent mPendingIntent;
- private TaskViewTaskController mTaskViewTaskController;
@Nullable
private TaskView mTaskView;
private int mTaskId = INVALID_TASK_ID;
@@ -204,17 +199,18 @@
public BubbleTaskViewHelper(Context context,
BubbleController controller,
BubbleTaskViewHelper.Listener listener,
+ BubbleTaskView bubbleTaskView,
View parent) {
mContext = context;
mController = controller;
- mMainExecutor = mController.getMainExecutor();
mListener = listener;
mParentView = parent;
- mTaskViewTaskController = new TaskViewTaskController(mContext,
- mController.getTaskOrganizer(),
- mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
- mTaskView = new TaskView(mContext, mTaskViewTaskController);
- mTaskView.setListener(mMainExecutor, mTaskViewListener);
+ mTaskView = bubbleTaskView.getTaskView();
+ bubbleTaskView.setDelegateListener(mTaskViewListener);
+ if (bubbleTaskView.isCreated()) {
+ mTaskId = bubbleTaskView.getTaskId();
+ mListener.onTaskCreated();
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index bb30c5e..c3d899e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -46,6 +46,8 @@
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
+import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewTaskController;
import java.lang.ref.WeakReference;
import java.util.Objects;
@@ -173,10 +175,12 @@
BubbleViewInfo info = new BubbleViewInfo();
if (!skipInflation && !b.isInflated()) {
+ BubbleTaskView bubbleTaskView = createBubbleTaskView(c, controller);
LayoutInflater inflater = LayoutInflater.from(c);
info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
- info.bubbleBarExpandedView.initialize(controller, false /* isOverflow */);
+ info.bubbleBarExpandedView.initialize(
+ controller, false /* isOverflow */, bubbleTaskView);
}
if (!populateCommonInfo(info, c, b, iconFactory)) {
@@ -201,9 +205,11 @@
R.layout.bubble_view, stackView, false /* attachToRoot */);
info.imageView.initialize(controller.getPositioner());
+ BubbleTaskView bubbleTaskView = createBubbleTaskView(c, controller);
info.expandedView = (BubbleExpandedView) inflater.inflate(
R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
- info.expandedView.initialize(controller, stackView, false /* isOverflow */);
+ info.expandedView.initialize(
+ controller, stackView, false /* isOverflow */, bubbleTaskView);
}
if (!populateCommonInfo(info, c, b, iconFactory)) {
@@ -219,6 +225,15 @@
}
return info;
}
+
+ private static BubbleTaskView createBubbleTaskView(
+ Context context, BubbleController controller) {
+ TaskViewTaskController taskViewTaskController = new TaskViewTaskController(context,
+ controller.getTaskOrganizer(),
+ controller.getTaskViewTransitions(), controller.getSyncTransactionQueue());
+ TaskView taskView = new TaskView(context, taskViewTaskController);
+ return new BubbleTaskView(taskView, controller.getMainExecutor());
+ }
}
/**
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 66c0c96..3cf23ac 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
@@ -16,6 +16,8 @@
package com.android.wm.shell.bubbles.bar;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
@@ -27,6 +29,7 @@
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
@@ -35,6 +38,7 @@
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleOverflowContainerView;
+import com.android.wm.shell.bubbles.BubbleTaskView;
import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.taskview.TaskView;
@@ -130,7 +134,8 @@
}
/** Set the BubbleController on the view, must be called before doing anything else. */
- public void initialize(BubbleController controller, boolean isOverflow) {
+ public void initialize(BubbleController controller, boolean isOverflow,
+ @Nullable BubbleTaskView bubbleTaskView) {
mController = controller;
mIsOverflow = isOverflow;
@@ -140,14 +145,19 @@
mOverflowView.setBubbleController(mController);
addView(mOverflowView);
} else {
-
+ mTaskView = bubbleTaskView.getTaskView();
mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController,
- /* listener= */ this,
+ /* listener= */ this, bubbleTaskView,
/* viewParent= */ this);
- mTaskView = mBubbleTaskViewHelper.getTaskView();
- addView(mTaskView);
+ if (mTaskView.getParent() != null) {
+ ((ViewGroup) mTaskView.getParent()).removeView(mTaskView);
+ }
+ FrameLayout.LayoutParams lp =
+ new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
+ addView(mTaskView, lp);
mTaskView.setEnableSurfaceClipping(true);
mTaskView.setCornerRadius(mCornerRadius);
+ mTaskView.setVisibility(VISIBLE);
// Handle view needs to draw on top of task view.
bringChildToFront(mHandleView);
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 0b42c88..f526a28 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -230,7 +230,7 @@
* stencil buffer may be needed. Views that use a functor to draw will be forced onto a layer.
*/
void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
- if (mDamageGenerationId == info.damageGenerationId) {
+ if (mDamageGenerationId == info.damageGenerationId && mDamageGenerationId != 0) {
// We hit the same node a second time in the same tree. We don't know the minimal
// damage rect anymore, so just push the biggest we can onto our parent's transform
// We push directly onto parent in case we are clipped to bounds but have moved position.
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 1f3834be..c904542 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -262,7 +262,7 @@
DisplayList mDisplayList;
DisplayList mStagingDisplayList;
- int64_t mDamageGenerationId;
+ int64_t mDamageGenerationId = 0;
friend class AnimatorManager;
AnimatorManager mAnimatorManager;
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index bba9c97..f84107e 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -111,7 +111,11 @@
: PointerController(
policy, looper, spriteController, enabled,
[](const sp<android::gui::WindowInfosListener>& listener) {
- SurfaceComposerClient::getDefault()->addWindowInfosListener(listener);
+ auto initialInfo = std::make_pair(std::vector<android::gui::WindowInfo>{},
+ std::vector<android::gui::DisplayInfo>{});
+ SurfaceComposerClient::getDefault()->addWindowInfosListener(listener,
+ &initialInfo);
+ return initialInfo.second;
},
[](const sp<android::gui::WindowInfosListener>& listener) {
SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener);
@@ -119,8 +123,9 @@
PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
const sp<Looper>& looper, SpriteController& spriteController,
- bool enabled, WindowListenerConsumer registerListener,
- WindowListenerConsumer unregisterListener)
+ bool enabled,
+ const WindowListenerRegisterConsumer& registerListener,
+ WindowListenerUnregisterConsumer unregisterListener)
: mEnabled(enabled),
mContext(policy, looper, spriteController, *this),
mCursorController(mContext),
@@ -128,7 +133,8 @@
mUnregisterWindowInfosListener(std::move(unregisterListener)) {
std::scoped_lock lock(getLock());
mLocked.presentation = Presentation::SPOT;
- registerListener(mDisplayInfoListener);
+ const auto& initialDisplayInfos = registerListener(mDisplayInfoListener);
+ onDisplayInfosChangedLocked(initialDisplayInfos);
}
PointerController::~PointerController() {
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index a8b9633..6ee5707 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -79,14 +79,16 @@
std::string dump() override;
protected:
- using WindowListenerConsumer =
+ using WindowListenerRegisterConsumer = std::function<std::vector<gui::DisplayInfo>(
+ const sp<android::gui::WindowInfosListener>&)>;
+ using WindowListenerUnregisterConsumer =
std::function<void(const sp<android::gui::WindowInfosListener>&)>;
// Constructor used to test WindowInfosListener registration.
PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
SpriteController& spriteController, bool enabled,
- WindowListenerConsumer registerListener,
- WindowListenerConsumer unregisterListener);
+ const WindowListenerRegisterConsumer& registerListener,
+ WindowListenerUnregisterConsumer unregisterListener);
PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
SpriteController& spriteController, bool enabled);
@@ -129,7 +131,7 @@
};
sp<DisplayInfoListener> mDisplayInfoListener;
- const WindowListenerConsumer mUnregisterWindowInfosListener;
+ const WindowListenerUnregisterConsumer mUnregisterWindowInfosListener;
const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(getLock());
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index b8de919..99952aa 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -93,7 +93,7 @@
const PointerCoords& c = spotCoords[spotIdToIndex[id]];
ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id,
c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y),
- c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), displayId);
+ c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId);
}
#endif
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index adfa91e..a1bb5b3 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -160,9 +160,11 @@
: PointerController(
policy, looper, spriteController,
/*enabled=*/true,
- [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) {
+ [®isteredListener](const sp<android::gui::WindowInfosListener>& listener)
+ -> std::vector<gui::DisplayInfo> {
// Register listener
registeredListener = listener;
+ return {};
},
[®isteredListener](const sp<android::gui::WindowInfosListener>& listener) {
// Unregister listener
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 687feef..691aa77 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -196,8 +196,8 @@
* Manifest.permission#MEDIA_CONTENT_CONTROL MEDIA_CONTENT_CONTROL} permission.
* @hide
*/
- // TODO (b/311711420): Deprecate once #getInstance(Context, Looper, String, UserHandle)
- // reaches public SDK.
+ // TODO (b/311711420): Deprecate once #getInstance(Context, String, UserHandle) reaches public
+ // SDK.
@SystemApi
@RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
@Nullable
@@ -206,7 +206,7 @@
// Capturing the IAE here to not break nullability.
try {
return findOrCreateProxyInstanceForCallingUser(
- context, Looper.getMainLooper(), clientPackageName, context.getUser());
+ context, clientPackageName, context.getUser());
} catch (IllegalArgumentException ex) {
Log.e(TAG, "Package " + clientPackageName + " not found. Ignoring.");
return null;
@@ -217,8 +217,6 @@
* Returns a proxy MediaRouter2 instance that allows you to control the routing of an app
* specified by {@code clientPackageName} and {@code user}.
*
- * <p>You can specify any {@link Looper} of choice on which internal state updates will run.
- *
* <p>Proxy MediaRouter2 instances operate differently than regular MediaRouter2 instances:
*
* <ul>
@@ -237,7 +235,6 @@
* </ul>
*
* @param context The {@link Context} of the caller.
- * @param looper The {@link Looper} on which to process internal state changes.
* @param clientPackageName The package name of the app you want to control the routing of.
* @param user The {@link UserHandle} of the user running the app for which to get the proxy
* router instance. Must match {@link Process#myUserHandle()} if the caller doesn't hold
@@ -255,10 +252,9 @@
@NonNull
public static MediaRouter2 getInstance(
@NonNull Context context,
- @NonNull Looper looper,
@NonNull String clientPackageName,
@NonNull UserHandle user) {
- return findOrCreateProxyInstanceForCallingUser(context, looper, clientPackageName, user);
+ return findOrCreateProxyInstanceForCallingUser(context, clientPackageName, user);
}
/**
@@ -270,9 +266,8 @@
*/
@NonNull
private static MediaRouter2 findOrCreateProxyInstanceForCallingUser(
- Context context, Looper looper, String clientPackageName, UserHandle user) {
+ Context context, String clientPackageName, UserHandle user) {
Objects.requireNonNull(context, "context must not be null");
- Objects.requireNonNull(looper, "looper must not be null");
Objects.requireNonNull(user, "user must not be null");
if (TextUtils.isEmpty(clientPackageName)) {
@@ -284,7 +279,8 @@
synchronized (sSystemRouterLock) {
MediaRouter2 instance = sAppToProxyRouterMap.get(key);
if (instance == null) {
- instance = new MediaRouter2(context, looper, clientPackageName, user);
+ instance =
+ new MediaRouter2(context, Looper.getMainLooper(), clientPackageName, user);
// Register proxy router after instantiation to avoid race condition.
((ProxyMediaRouter2Impl) instance.mImpl).registerProxyRouter();
sAppToProxyRouterMap.put(key, instance);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index caddd8a..2b31bfe 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -33,6 +33,7 @@
import android.media.AudioFormat.Encoding;
import android.media.AudioPresentation;
import android.media.PlaybackParams;
+import android.media.tv.ad.TvAdManager;
import android.media.tv.interactive.TvInteractiveAppManager;
import android.net.Uri;
import android.os.Binder;
@@ -2744,6 +2745,7 @@
private int mVideoHeight;
private TvInteractiveAppManager.Session mIAppSession;
+ private TvAdManager.Session mAdSession;
private boolean mIAppNotificationEnabled = false;
private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
@@ -2764,6 +2766,14 @@
this.mIAppSession = iAppSession;
}
+ public TvAdManager.Session getAdSession() {
+ return mAdSession;
+ }
+
+ public void setAdSession(TvAdManager.Session adSession) {
+ this.mAdSession = adSession;
+ }
+
/**
* Releases this session.
*/
diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl
index a747e49..9620065 100644
--- a/media/java/android/media/tv/ad/ITvAdManager.aidl
+++ b/media/java/android/media/tv/ad/ITvAdManager.aidl
@@ -16,9 +16,13 @@
package android.media.tv.ad;
+import android.graphics.Rect;
+import android.media.tv.TvTrackInfo;
import android.media.tv.ad.ITvAdClient;
import android.media.tv.ad.ITvAdManagerCallback;
import android.media.tv.ad.TvAdServiceInfo;
+import android.net.Uri;
+import android.os.Bundle;
import android.view.Surface;
/**
@@ -31,10 +35,27 @@
in ITvAdClient client, in String serviceId, in String type, int seq, int userId);
void releaseSession(in IBinder sessionToken, int userId);
void startAdService(in IBinder sessionToken, int userId);
+ void stopAdService(in IBinder sessionToken, int userId);
+ void resetAdService(in IBinder sessionToken, int userId);
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
int userId);
+ void sendCurrentVideoBounds(in IBinder sessionToken, in Rect bounds, int userId);
+ void sendCurrentChannelUri(in IBinder sessionToken, in Uri channelUri, int userId);
+ void sendTrackInfoList(in IBinder sessionToken, in List<TvTrackInfo> tracks, int userId);
+ void sendCurrentTvInputId(in IBinder sessionToken, in String inputId, int userId);
+ void sendSigningResult(in IBinder sessionToken, in String signingId, in byte[] result,
+ int userId);
+
+ void notifyError(in IBinder sessionToken, in String errMsg, in Bundle params, int userId);
+ void notifyTvMessage(in IBinder sessionToken, in int type, in Bundle data, int userId);
+
void registerCallback(in ITvAdManagerCallback callback, int userId);
void unregisterCallback(in ITvAdManagerCallback callback, int userId);
+
+ void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
+ int userId);
+ void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId);
+ void removeMediaView(in IBinder sessionToken, int userId);
}
diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl
index 751257c..69afb17 100644
--- a/media/java/android/media/tv/ad/ITvAdSession.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSession.aidl
@@ -16,6 +16,10 @@
package android.media.tv.ad;
+import android.graphics.Rect;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.Bundle;
import android.view.Surface;
/**
@@ -25,6 +29,21 @@
oneway interface ITvAdSession {
void release();
void startAdService();
+ void stopAdService();
+ void resetAdService();
void setSurface(in Surface surface);
void dispatchSurfaceChanged(int format, int width, int height);
+
+ void sendCurrentVideoBounds(in Rect bounds);
+ void sendCurrentChannelUri(in Uri channelUri);
+ void sendTrackInfoList(in List<TvTrackInfo> tracks);
+ void sendCurrentTvInputId(in String inputId);
+ void sendSigningResult(in String signingId, in byte[] result);
+
+ void notifyError(in String errMsg, in Bundle params);
+ void notifyTvMessage(int type, in Bundle data);
+
+ void createMediaView(in IBinder windowToken, in Rect frame);
+ void relayoutMediaView(in Rect frame);
+ void removeMediaView();
}
diff --git a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
index 4df2783..251351d 100644
--- a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
+++ b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
@@ -16,7 +16,14 @@
package android.media.tv.ad;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
+import android.graphics.Rect;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -29,6 +36,8 @@
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import java.util.List;
+
/**
* Implements the internal ITvAdSession interface.
* @hide
@@ -43,6 +52,19 @@
private static final int DO_RELEASE = 1;
private static final int DO_SET_SURFACE = 2;
private static final int DO_DISPATCH_SURFACE_CHANGED = 3;
+ private static final int DO_CREATE_MEDIA_VIEW = 4;
+ private static final int DO_RELAYOUT_MEDIA_VIEW = 5;
+ private static final int DO_REMOVE_MEDIA_VIEW = 6;
+ private static final int DO_START_AD_SERVICE = 7;
+ private static final int DO_STOP_AD_SERVICE = 8;
+ private static final int DO_RESET_AD_SERVICE = 9;
+ private static final int DO_SEND_CURRENT_VIDEO_BOUNDS = 10;
+ private static final int DO_SEND_CURRENT_CHANNEL_URI = 11;
+ private static final int DO_SEND_TRACK_INFO_LIST = 12;
+ private static final int DO_SEND_CURRENT_TV_INPUT_ID = 13;
+ private static final int DO_SEND_SIGNING_RESULT = 14;
+ private static final int DO_NOTIFY_ERROR = 15;
+ private static final int DO_NOTIFY_TV_MESSAGE = 16;
private final HandlerCaller mCaller;
private TvAdService.Session mSessionImpl;
@@ -61,6 +83,7 @@
@Override
public void release() {
+ mSessionImpl.scheduleMediaViewCleanup();
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
}
@@ -97,6 +120,66 @@
args.recycle();
break;
}
+ case DO_CREATE_MEDIA_VIEW: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.createMediaView((IBinder) args.arg1, (Rect) args.arg2);
+ args.recycle();
+ break;
+ }
+ case DO_RELAYOUT_MEDIA_VIEW: {
+ mSessionImpl.relayoutMediaView((Rect) msg.obj);
+ break;
+ }
+ case DO_REMOVE_MEDIA_VIEW: {
+ mSessionImpl.removeMediaView(true);
+ break;
+ }
+ case DO_START_AD_SERVICE: {
+ mSessionImpl.startAdService();
+ break;
+ }
+ case DO_STOP_AD_SERVICE: {
+ mSessionImpl.stopAdService();
+ break;
+ }
+ case DO_RESET_AD_SERVICE: {
+ mSessionImpl.resetAdService();
+ break;
+ }
+ case DO_SEND_CURRENT_VIDEO_BOUNDS: {
+ mSessionImpl.sendCurrentVideoBounds((Rect) msg.obj);
+ break;
+ }
+ case DO_SEND_CURRENT_CHANNEL_URI: {
+ mSessionImpl.sendCurrentChannelUri((Uri) msg.obj);
+ break;
+ }
+ case DO_SEND_TRACK_INFO_LIST: {
+ mSessionImpl.sendTrackInfoList((List<TvTrackInfo>) msg.obj);
+ break;
+ }
+ case DO_SEND_CURRENT_TV_INPUT_ID: {
+ mSessionImpl.sendCurrentTvInputId((String) msg.obj);
+ break;
+ }
+ case DO_SEND_SIGNING_RESULT: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.sendSigningResult((String) args.arg1, (byte[]) args.arg2);
+ args.recycle();
+ break;
+ }
+ case DO_NOTIFY_ERROR: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.notifyError((String) args.arg1, (Bundle) args.arg2);
+ args.recycle();
+ break;
+ }
+ case DO_NOTIFY_TV_MESSAGE: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.notifyTvMessage((Integer) args.arg1, (Bundle) args.arg2);
+ args.recycle();
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -115,7 +198,17 @@
@Override
public void startAdService() throws RemoteException {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_START_AD_SERVICE));
+ }
+ @Override
+ public void stopAdService() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_STOP_AD_SERVICE));
+ }
+
+ @Override
+ public void resetAdService() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RESET_AD_SERVICE));
}
@Override
@@ -129,6 +222,64 @@
mCaller.obtainMessageIIII(DO_DISPATCH_SURFACE_CHANGED, format, width, height, 0));
}
+ @Override
+ public void sendCurrentVideoBounds(@Nullable Rect bounds) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageO(DO_SEND_CURRENT_VIDEO_BOUNDS, bounds));
+ }
+
+ @Override
+ public void sendCurrentChannelUri(@Nullable Uri channelUri) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageO(DO_SEND_CURRENT_CHANNEL_URI, channelUri));
+ }
+
+ @Override
+ public void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageO(DO_SEND_TRACK_INFO_LIST, tracks));
+ }
+
+ @Override
+ public void sendCurrentTvInputId(@Nullable String inputId) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageO(DO_SEND_CURRENT_TV_INPUT_ID, inputId));
+ }
+
+ @Override
+ public void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_SEND_SIGNING_RESULT, signingId, result));
+ }
+
+ @Override
+ public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_NOTIFY_ERROR, errMsg, params));
+ }
+
+ @Override
+ public void notifyTvMessage(int type, Bundle data) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data));
+ }
+
+ @Override
+ public void createMediaView(IBinder windowToken, Rect frame) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_CREATE_MEDIA_VIEW, windowToken, frame));
+ }
+
+ @Override
+ public void relayoutMediaView(Rect frame) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_MEDIA_VIEW, frame));
+ }
+
+ @Override
+ public void removeMediaView() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_MEDIA_VIEW));
+ }
+
private final class TvAdEventReceiver extends InputEventReceiver {
TvAdEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 9c75051..4dce72f 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -21,8 +21,12 @@
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
+import android.graphics.Rect;
import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
import android.media.tv.flags.Flags;
+import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -35,6 +39,7 @@
import android.view.InputEvent;
import android.view.InputEventSender;
import android.view.Surface;
+import android.view.View;
import com.android.internal.util.Preconditions;
@@ -250,6 +255,14 @@
mSessionCallbackRecordMap = sessionCallbackRecordMap;
}
+ public TvInputManager.Session getInputSession() {
+ return mInputSession;
+ }
+
+ public void setInputSession(TvInputManager.Session inputSession) {
+ mInputSession = inputSession;
+ }
+
/**
* Releases this session.
*/
@@ -286,6 +299,67 @@
}
/**
+ * Creates a media view. Once the media view is created, {@link #relayoutMediaView}
+ * should be called whenever the layout of its containing view is changed.
+ * {@link #removeMediaView()} should be called to remove the media view.
+ * Since a session can have only one media view, this method should be called only once
+ * or it can be called again after calling {@link #removeMediaView()}.
+ *
+ * @param view A view for AD service.
+ * @param frame A position of the media view.
+ * @throws IllegalStateException if {@code view} is not attached to a window.
+ */
+ void createMediaView(@NonNull View view, @NonNull Rect frame) {
+ Preconditions.checkNotNull(view);
+ Preconditions.checkNotNull(frame);
+ if (view.getWindowToken() == null) {
+ throw new IllegalStateException("view must be attached to a window");
+ }
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.createMediaView(mToken, view.getWindowToken(), frame, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Relayouts the current media view.
+ *
+ * @param frame A new position of the media view.
+ */
+ void relayoutMediaView(@NonNull Rect frame) {
+ Preconditions.checkNotNull(frame);
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.relayoutMediaView(mToken, frame, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes the current media view.
+ */
+ void removeMediaView() {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.removeMediaView(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Notifies of any structural changes (format or size) of the surface passed in
* {@link #setSurface}.
*
@@ -348,6 +422,117 @@
}
}
+ void stopAdService() {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.stopAdService(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void resetAdService() {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.resetAdService(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void sendCurrentVideoBounds(@NonNull Rect bounds) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.sendCurrentVideoBounds(mToken, bounds, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void sendCurrentChannelUri(@Nullable Uri channelUri) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.sendCurrentChannelUri(mToken, channelUri, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.sendTrackInfoList(mToken, tracks, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void sendCurrentTvInputId(@Nullable String inputId) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.sendCurrentTvInputId(mToken, inputId, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.sendSigningResult(mToken, signingId, result, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyError(mToken, errMsg, params, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notifies AD service session when a new TV message is received.
+ */
+ public void notifyTvMessage(int type, Bundle data) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyTvMessage(mToken, type, data, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private final class InputEventHandler extends Handler {
public static final int MSG_SEND_INPUT_EVENT = 1;
public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 6995703..5d81837 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -20,19 +20,28 @@
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.Px;
import android.annotation.SdkConstant;
import android.annotation.SuppressLint;
+import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
+import android.view.Gravity;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -42,6 +51,7 @@
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
+import android.widget.FrameLayout;
import com.android.internal.os.SomeArgs;
@@ -56,6 +66,8 @@
private static final boolean DEBUG = false;
private static final String TAG = "TvAdService";
+ private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000;
+
/**
* Name under which a TvAdService component publishes information about itself. This meta-data
* must reference an XML resource containing an
@@ -151,7 +163,14 @@
private final Context mContext;
final Handler mHandler;
private final WindowManager mWindowManager;
+ private WindowManager.LayoutParams mWindowParams;
private Surface mSurface;
+ private FrameLayout mMediaViewContainer;
+ private View mMediaView;
+ private MediaViewCleanUpTask mMediaViewCleanUpTask;
+ private boolean mMediaViewEnabled;
+ private IBinder mWindowToken;
+ private Rect mMediaFrame;
/**
@@ -166,6 +185,48 @@
}
/**
+ * Enables or disables the media view.
+ *
+ * <p>By default, the media view is disabled. Must be called explicitly after the
+ * session is created to enable the media view.
+ *
+ * <p>The TV AD service can disable its media view when needed.
+ *
+ * @param enable {@code true} if you want to enable the media view. {@code false}
+ * otherwise.
+ * @hide
+ */
+ @CallSuper
+ public void setMediaViewEnabled(final boolean enable) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (enable == mMediaViewEnabled) {
+ return;
+ }
+ mMediaViewEnabled = enable;
+ if (enable) {
+ if (mWindowToken != null) {
+ createMediaView(mWindowToken, mMediaFrame);
+ }
+ } else {
+ removeMediaView(false);
+ }
+ }
+ });
+ }
+
+ /**
+ * Returns {@code true} if media view is enabled, {@code false} otherwise.
+ *
+ * @see #setMediaViewEnabled(boolean)
+ * @hide
+ */
+ public boolean isMediaViewEnabled() {
+ return mMediaViewEnabled;
+ }
+
+ /**
* Releases TvAdService session.
*/
public abstract void onRelease();
@@ -180,18 +241,44 @@
mSessionCallback = null;
mPendingActions.clear();
}
+ // Removes the media view lastly so that any hanging on the main thread can be handled
+ // in {@link #scheduleMediaViewCleanup}.
+ removeMediaView(true);
}
/**
* Starts TvAdService session.
+ * @hide
*/
public void onStartAdService() {
}
+ /**
+ * Stops TvAdService session.
+ * @hide
+ */
+ public void onStopAdService() {
+ }
+
+ /**
+ * Resets TvAdService session.
+ * @hide
+ */
+ public void onResetAdService() {
+ }
+
void startAdService() {
onStartAdService();
}
+ void stopAdService() {
+ onStopAdService();
+ }
+
+ void resetAdService() {
+ onResetAdService();
+ }
+
@Override
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
return false;
@@ -307,6 +394,109 @@
}
/**
+ * Receives current video bounds.
+ *
+ * @param bounds the rectangle area for rendering the current video.
+ * @hide
+ */
+ public void onCurrentVideoBounds(@NonNull Rect bounds) {
+ }
+
+ /**
+ * Receives current channel URI.
+ * @hide
+ */
+ public void onCurrentChannelUri(@Nullable Uri channelUri) {
+ }
+
+ /**
+ * Receives track list.
+ * @hide
+ */
+ public void onTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
+ }
+
+ /**
+ * Receives current TV input ID.
+ * @hide
+ */
+ public void onCurrentTvInputId(@Nullable String inputId) {
+ }
+
+ /**
+ * Receives signing result.
+ *
+ * @param signingId the ID to identify the request. It's the same as the corresponding ID in
+ * {@link Session#requestSigning(String, String, String, byte[])}
+ * @param result the signed result.
+ *
+ * @see #requestSigning(String, String, String, byte[])
+ * @hide
+ */
+ public void onSigningResult(@NonNull String signingId, @NonNull byte[] result) {
+ }
+
+ /**
+ * Called when the application sends information of an error.
+ *
+ * @param errMsg the message of the error.
+ * @param params additional parameters of the error. For example, the signingId of {@link
+ * TvAdView.TvAdCallback#onRequestSigning(String, String, String, String, byte[])}
+ * can be included to identify the related signing request, and the method name
+ * "onRequestSigning" can also be added to the params.
+ *
+ * @see TvAdView#ERROR_KEY_METHOD_NAME
+ * @hide
+ */
+ public void onError(@NonNull String errMsg, @NonNull Bundle params) {
+ }
+
+ /**
+ * Called when a TV message is received
+ *
+ * @param type The type of message received, such as
+ * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
+ * @param data The raw data of the message. The bundle keys are:
+ * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+ * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+ * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+ * how to parse this data.
+ * @hide
+ */
+ public void onTvMessage(@TvInputManager.TvMessageType int type,
+ @NonNull Bundle data) {
+ }
+
+ /**
+ * Called when the size of the media view is changed by the application.
+ *
+ * <p>This is always called at least once when the session is created regardless of whether
+ * the media view is enabled or not. The media view container size is the same as the
+ * containing {@link TvAdView}. Note that the size of the underlying surface can
+ * be different if the surface was changed by calling {@link #layoutSurface}.
+ *
+ * @param width The width of the media view, in pixels.
+ * @param height The height of the media view, in pixels.
+ * @hide
+ */
+ public void onMediaViewSizeChanged(@Px int width, @Px int height) {
+ }
+
+ /**
+ * Called when the application requests to create a media view. Each session
+ * implementation can override this method and return its own view.
+ *
+ * @return a view attached to the media window. {@code null} if no media view is created.
+ * @hide
+ */
+ @Nullable
+ public View onCreateMediaView() {
+ return null;
+ }
+
+ /**
* Takes care of dispatching incoming input events and tells whether the event was handled.
*/
int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
@@ -373,6 +563,37 @@
onSurfaceChanged(format, width, height);
}
+ void sendCurrentVideoBounds(@NonNull Rect bounds) {
+ onCurrentVideoBounds(bounds);
+ }
+
+ void sendCurrentChannelUri(@Nullable Uri channelUri) {
+ onCurrentChannelUri(channelUri);
+ }
+
+ void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
+ onTrackInfoList(tracks);
+ }
+
+ void sendCurrentTvInputId(@Nullable String inputId) {
+ onCurrentTvInputId(inputId);
+ }
+
+ void sendSigningResult(String signingId, byte[] result) {
+ onSigningResult(signingId, result);
+ }
+
+ void notifyError(String errMsg, Bundle params) {
+ onError(errMsg, params);
+ }
+
+ void notifyTvMessage(int type, Bundle data) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyTvMessage (type=" + type + ", data= " + data + ")");
+ }
+ onTvMessage(type, data);
+ }
+
private void executeOrPostRunnableOnMainThread(Runnable action) {
synchronized (mLock) {
if (mSessionCallback == null) {
@@ -388,6 +609,137 @@
}
}
}
+
+ /**
+ * Creates a media view. This calls {@link #onCreateMediaView} to get a view to attach
+ * to the media window.
+ *
+ * @param windowToken A window token of the application.
+ * @param frame A position of the media view.
+ */
+ void createMediaView(IBinder windowToken, Rect frame) {
+ if (mMediaViewContainer != null) {
+ removeMediaView(false);
+ }
+ if (DEBUG) Log.d(TAG, "create media view(" + frame + ")");
+ mWindowToken = windowToken;
+ mMediaFrame = frame;
+ onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+ if (!mMediaViewEnabled) {
+ return;
+ }
+ mMediaView = onCreateMediaView();
+ if (mMediaView == null) {
+ return;
+ }
+ if (mMediaViewCleanUpTask != null) {
+ mMediaViewCleanUpTask.cancel(true);
+ mMediaViewCleanUpTask = null;
+ }
+ // Creates a container view to check hanging on the media view detaching.
+ // Adding/removing the media view to/from the container make the view attach/detach
+ // logic run on the main thread.
+ mMediaViewContainer = new FrameLayout(mContext.getApplicationContext());
+ mMediaViewContainer.addView(mMediaView);
+
+ int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+ // We make the overlay view non-focusable and non-touchable so that
+ // the application that owns the window token can decide whether to consume or
+ // dispatch the input events.
+ int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ if (ActivityManager.isHighEndGfx()) {
+ flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ }
+ mWindowParams = new WindowManager.LayoutParams(
+ frame.right - frame.left, frame.bottom - frame.top,
+ frame.left, frame.top, type, flags, PixelFormat.TRANSPARENT);
+ mWindowParams.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ mWindowParams.gravity = Gravity.START | Gravity.TOP;
+ mWindowParams.token = windowToken;
+ mWindowManager.addView(mMediaViewContainer, mWindowParams);
+ }
+
+ /**
+ * Relayouts the current media view.
+ *
+ * @param frame A new position of the media view.
+ */
+ void relayoutMediaView(Rect frame) {
+ if (DEBUG) Log.d(TAG, "relayoutMediaView(" + frame + ")");
+ if (mMediaFrame == null || mMediaFrame.width() != frame.width()
+ || mMediaFrame.height() != frame.height()) {
+ // Note: relayoutMediaView is called whenever TvAdView's layout is
+ // changed regardless of setMediaViewEnabled.
+ onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+ }
+ mMediaFrame = frame;
+ if (!mMediaViewEnabled || mMediaViewContainer == null) {
+ return;
+ }
+ mWindowParams.x = frame.left;
+ mWindowParams.y = frame.top;
+ mWindowParams.width = frame.right - frame.left;
+ mWindowParams.height = frame.bottom - frame.top;
+ mWindowManager.updateViewLayout(mMediaViewContainer, mWindowParams);
+ }
+
+ /**
+ * Removes the current media view.
+ */
+ void removeMediaView(boolean clearWindowToken) {
+ if (DEBUG) Log.d(TAG, "removeMediaView(" + mMediaViewContainer + ")");
+ if (clearWindowToken) {
+ mWindowToken = null;
+ mMediaFrame = null;
+ }
+ if (mMediaViewContainer != null) {
+ // Removes the media view from the view hierarchy in advance so that it can be
+ // cleaned up in the {@link MediaViewCleanUpTask} if the remove process is
+ // hanging.
+ mMediaViewContainer.removeView(mMediaView);
+ mMediaView = null;
+ mWindowManager.removeView(mMediaViewContainer);
+ mMediaViewContainer = null;
+ mWindowParams = null;
+ }
+ }
+
+ /**
+ * Schedules a task which checks whether the media view is detached and kills the process
+ * if it is not. Note that this method is expected to be called in a non-main thread.
+ */
+ void scheduleMediaViewCleanup() {
+ View mediaViewParent = mMediaViewContainer;
+ if (mediaViewParent != null) {
+ mMediaViewCleanUpTask = new MediaViewCleanUpTask();
+ mMediaViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
+ mediaViewParent);
+ }
+ }
+ }
+
+ private static final class MediaViewCleanUpTask extends AsyncTask<View, Void, Void> {
+ @Override
+ protected Void doInBackground(View... views) {
+ View mediaViewParent = views[0];
+ try {
+ Thread.sleep(DETACH_MEDIA_VIEW_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ return null;
+ }
+ if (isCancelled()) {
+ return null;
+ }
+ if (mediaViewParent.isAttachedToWindow()) {
+ Log.e(TAG, "Time out on releasing media view. Killing "
+ + mediaViewParent.getContext().getPackageName());
+ android.os.Process.killProcess(Process.myPid());
+ }
+ return null;
+ }
}
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index 5e67fe9..ec23b7c 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -16,12 +16,20 @@
package android.media.tv.ad;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
+import android.media.tv.TvView;
+import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
@@ -32,6 +40,9 @@
import android.view.View;
import android.view.ViewGroup;
+import java.util.List;
+import java.util.concurrent.Executor;
+
/**
* Displays contents of TV AD services.
* @hide
@@ -40,11 +51,22 @@
private static final String TAG = "TvAdView";
private static final boolean DEBUG = false;
+ /**
+ * The name of the method where the error happened, if applicable. For example, if there is an
+ * error during signing, the request name is "onRequestSigning".
+ * @see #notifyError(String, Bundle)
+ * @hide
+ */
+ public static final String ERROR_KEY_METHOD_NAME = "method_name";
+
private final TvAdManager mTvAdManager;
private final Handler mHandler = new Handler();
+ private final Object mCallbackLock = new Object();
private TvAdManager.Session mSession;
private MySessionCallback mSessionCallback;
+ private TvAdCallback mCallback;
+ private Executor mCallbackExecutor;
private final AttributeSet mAttrs;
private final int mDefStyleAttr;
@@ -64,6 +86,9 @@
private int mSurfaceViewTop;
private int mSurfaceViewBottom;
+ private boolean mMediaViewCreated;
+ private Rect mMediaViewFrame;
+
private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@@ -121,6 +146,51 @@
mTvAdManager = (TvAdManager) getContext().getSystemService(Context.TV_AD_SERVICE);
}
+ /**
+ * Sets the TvAdView to receive events from TvInputService. This method links the session of
+ * TvAdManager to TvInputManager session, so the TvAdService can get the TvInputService events.
+ *
+ * @param tvView the TvView to be linked to this TvAdView via linking of Sessions. {@code null}
+ * to unlink the TvView.
+ * @return {@code true} if it's linked successfully; {@code false} otherwise.
+ * @hide
+ */
+ public boolean setTvView(@Nullable TvView tvView) {
+ if (tvView == null) {
+ return unsetTvView();
+ }
+ TvInputManager.Session inputSession = tvView.getInputSession();
+ if (inputSession == null || mSession == null) {
+ return false;
+ }
+ mSession.setInputSession(inputSession);
+ inputSession.setAdSession(mSession);
+ return true;
+ }
+
+ private boolean unsetTvView() {
+ if (mSession == null || mSession.getInputSession() == null) {
+ return false;
+ }
+ mSession.getInputSession().setAdSession(null);
+ mSession.setInputSession(null);
+ return true;
+ }
+
+ /** @hide */
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ createSessionMediaView();
+ }
+
+ /** @hide */
+ @Override
+ public void onDetachedFromWindow() {
+ removeSessionMediaView();
+ super.onDetachedFromWindow();
+ }
+
@Override
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (DEBUG) {
@@ -150,6 +220,11 @@
public void onVisibilityChanged(@NonNull View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
mSurfaceView.setVisibility(visibility);
+ if (visibility == View.VISIBLE) {
+ createSessionMediaView();
+ } else {
+ removeSessionMediaView();
+ }
}
private void resetSurfaceView() {
@@ -162,6 +237,7 @@
@Override
protected void updateSurface() {
super.updateSurface();
+ relayoutSessionMediaView();
}};
// The surface view's content should be treated as secure all the time.
mSurfaceView.setSecure(true);
@@ -174,6 +250,69 @@
addView(mSurfaceView);
}
+ /**
+ * Resets this TvAdView to release its resources.
+ *
+ * <p>It can be reused by call {@link #prepareAdService(String, String)}.
+ * @hide
+ */
+ public void reset() {
+ if (DEBUG) Log.d(TAG, "reset()");
+ resetInternal();
+ }
+
+ private void resetInternal() {
+ mSessionCallback = null;
+ if (mSession != null) {
+ setSessionSurface(null);
+ removeSessionMediaView();
+ mUseRequestedSurfaceLayout = false;
+ mSession.release();
+ mSession = null;
+ resetSurfaceView();
+ }
+ }
+
+ private void createSessionMediaView() {
+ // TODO: handle z-order
+ if (mSession == null || !isAttachedToWindow() || mMediaViewCreated) {
+ return;
+ }
+ mMediaViewFrame = getViewFrameOnScreen();
+ mSession.createMediaView(this, mMediaViewFrame);
+ mMediaViewCreated = true;
+ }
+
+ private void removeSessionMediaView() {
+ if (mSession == null || !mMediaViewCreated) {
+ return;
+ }
+ mSession.removeMediaView();
+ mMediaViewCreated = false;
+ mMediaViewFrame = null;
+ }
+
+ private void relayoutSessionMediaView() {
+ if (mSession == null || !isAttachedToWindow() || !mMediaViewCreated) {
+ return;
+ }
+ Rect viewFrame = getViewFrameOnScreen();
+ if (viewFrame.equals(mMediaViewFrame)) {
+ return;
+ }
+ mSession.relayoutMediaView(viewFrame);
+ mMediaViewFrame = viewFrame;
+ }
+
+ private Rect getViewFrameOnScreen() {
+ Rect frame = new Rect();
+ getGlobalVisibleRect(frame);
+ RectF frameF = new RectF(frame);
+ getMatrix().mapRect(frameF);
+ frameF.round(frame);
+ return frame;
+ }
+
private void setSessionSurface(Surface surface) {
if (mSession == null) {
return;
@@ -185,7 +324,7 @@
if (mSession == null) {
return;
}
- //mSession.dispatchSurfaceChanged(format, width, height);
+ mSession.dispatchSurfaceChanged(format, width, height);
}
/**
@@ -205,16 +344,196 @@
/**
* Starts the AD service.
+ * @hide
*/
public void startAdService() {
if (DEBUG) {
- Log.d(TAG, "start");
+ Log.d(TAG, "startAdService");
}
if (mSession != null) {
mSession.startAdService();
}
}
+ /**
+ * Stops the AD service.
+ */
+ public void stopAdService() {
+ if (DEBUG) {
+ Log.d(TAG, "stopAdService");
+ }
+ if (mSession != null) {
+ mSession.stopAdService();
+ }
+ }
+
+ /**
+ * Resets the AD service.
+ *
+ * <p>This releases the resources of the corresponding {@link TvAdService.Session}.
+ */
+ public void resetAdService() {
+ if (DEBUG) {
+ Log.d(TAG, "resetAdService");
+ }
+ if (mSession != null) {
+ mSession.resetAdService();
+ }
+ }
+
+ /**
+ * Sends current video bounds to related TV AD service.
+ *
+ * @param bounds the rectangle area for rendering the current video.
+ */
+ public void sendCurrentVideoBounds(@NonNull Rect bounds) {
+ if (DEBUG) {
+ Log.d(TAG, "sendCurrentVideoBounds");
+ }
+ if (mSession != null) {
+ mSession.sendCurrentVideoBounds(bounds);
+ }
+ }
+
+ /**
+ * Sends current channel URI to related TV AD service.
+ *
+ * @param channelUri The current channel URI; {@code null} if there is no currently tuned
+ * channel.
+ */
+ public void sendCurrentChannelUri(@Nullable Uri channelUri) {
+ if (DEBUG) {
+ Log.d(TAG, "sendCurrentChannelUri");
+ }
+ if (mSession != null) {
+ mSession.sendCurrentChannelUri(channelUri);
+ }
+ }
+
+ /**
+ * Sends track info list to related TV AD service.
+ */
+ public void sendTrackInfoList(@Nullable List<TvTrackInfo> tracks) {
+ if (DEBUG) {
+ Log.d(TAG, "sendTrackInfoList");
+ }
+ if (mSession != null) {
+ mSession.sendTrackInfoList(tracks);
+ }
+ }
+
+ /**
+ * Sends current TV input ID to related TV AD service.
+ *
+ * @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is
+ * tuned.
+ * @see android.media.tv.TvInputInfo
+ */
+ public void sendCurrentTvInputId(@Nullable String inputId) {
+ if (DEBUG) {
+ Log.d(TAG, "sendCurrentTvInputId");
+ }
+ if (mSession != null) {
+ mSession.sendCurrentTvInputId(inputId);
+ }
+ }
+
+ /**
+ * Sends signing result to related TV AD service.
+ *
+ * <p>This is used when the corresponding server of the ADs requires signing during handshaking,
+ * and the AD service doesn't have the built-in private key. The private key is provided by the
+ * content providers and pre-built in the related app, such as TV app.
+ *
+ * @param signingId the ID to identify the request. It's the same as the corresponding ID in
+ * {@link TvAdService.Session#requestSigning(String, String, String, byte[])}
+ * @param result the signed result.
+ * @hide
+ */
+ public void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
+ if (DEBUG) {
+ Log.d(TAG, "sendSigningResult");
+ }
+ if (mSession != null) {
+ mSession.sendSigningResult(signingId, result);
+ }
+ }
+
+ /**
+ * Notifies the corresponding {@link TvAdService} when there is an error.
+ *
+ * @param errMsg the message of the error.
+ * @param params additional parameters of the error. For example, the signingId of {@link
+ * TvAdView.TvAdCallback#onRequestSigning(String, String, String, String, byte[])} can be
+ * included to identify the related signing request, and the method name "onRequestSigning"
+ * can also be added to the params.
+ *
+ * @see #ERROR_KEY_METHOD_NAME
+ */
+ public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyError msg=" + errMsg + "; params=" + params);
+ }
+ if (mSession != null) {
+ mSession.notifyError(errMsg, params);
+ }
+ }
+
+ /**
+ * This is called to notify the corresponding TV AD service when a new TV message is received.
+ *
+ * @param type The type of message received, such as
+ * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
+ * @param data The raw data of the message. The bundle keys are:
+ * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+ * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+ * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+ * how to parse this data.
+ */
+ public void notifyTvMessage(@NonNull @TvInputManager.TvMessageType int type,
+ @NonNull Bundle data) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyTvMessage type=" + type
+ + "; data=" + data);
+ }
+ if (mSession != null) {
+ mSession.notifyTvMessage(type, data);
+ }
+ }
+
+ /**
+ * Sets the callback to be invoked when an event is dispatched to this TvAdView.
+ *
+ * @param callback the callback to receive events. MUST NOT be {@code null}.
+ *
+ * @see #clearCallback()
+ * @hide
+ */
+ public void setCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull TvAdCallback callback) {
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, callback);
+ synchronized (mCallbackLock) {
+ mCallbackExecutor = executor;
+ mCallback = callback;
+ }
+ }
+
+ /**
+ * Clears the callback.
+ *
+ * @see #setCallback(Executor, TvAdCallback)
+ * @hide
+ */
+ public void clearCallback() {
+ synchronized (mCallbackLock) {
+ mCallback = null;
+ mCallbackExecutor = null;
+ }
+ }
+
private class MySessionCallback extends TvAdManager.SessionCallback {
final String mServiceId;
@@ -246,6 +565,7 @@
dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
}
}
+ createSessionMediaView();
} else {
// Failed to create
// Todo: forward error to Tv App
@@ -262,6 +582,8 @@
Log.w(TAG, "onSessionReleased - session not created");
return;
}
+ mMediaViewCreated = false;
+ mMediaViewFrame = null;
mSessionCallback = null;
mSession = null;
}
@@ -285,4 +607,11 @@
requestLayout();
}
}
+
+ /**
+ * Callback used to receive various status updates on the {@link TvAdView}.
+ * @hide
+ */
+ public abstract static class TvAdCallback {
+ }
}
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index abe4a3d..c572944 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -228,11 +228,6 @@
}
int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNanos) {
- if (actualDurationNanos <= 0) {
- ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__);
- return EINVAL;
- }
-
WorkDuration workDuration(0, actualDurationNanos, actualDurationNanos, 0);
return reportActualWorkDurationInternal(&workDuration);
@@ -320,23 +315,6 @@
int APerformanceHintSession::reportActualWorkDuration(AWorkDuration* aWorkDuration) {
WorkDuration* workDuration = static_cast<WorkDuration*>(aWorkDuration);
- if (workDuration->workPeriodStartTimestampNanos <= 0) {
- ALOGE("%s: workPeriodStartTimestampNanos must be positive", __FUNCTION__);
- return EINVAL;
- }
- if (workDuration->actualTotalDurationNanos <= 0) {
- ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__);
- return EINVAL;
- }
- if (workDuration->actualCpuDurationNanos <= 0) {
- ALOGE("%s: cpuDurationNanos must be positive", __FUNCTION__);
- return EINVAL;
- }
- if (workDuration->actualGpuDurationNanos < 0) {
- ALOGE("%s: gpuDurationNanos must be non negative", __FUNCTION__);
- return EINVAL;
- }
-
return reportActualWorkDurationInternal(workDuration);
}
@@ -428,62 +406,87 @@
return APerformanceHintManager::getInstance();
}
+#define VALIDATE_PTR(ptr) \
+ LOG_ALWAYS_FATAL_IF(ptr == nullptr, "%s: " #ptr " is nullptr", __FUNCTION__);
+
+#define VALIDATE_INT(value, cmp) \
+ if (!(value cmp)) { \
+ ALOGE("%s: Invalid value. Check failed: (" #value " " #cmp ") with value: %" PRIi64, \
+ __FUNCTION__, value); \
+ return EINVAL; \
+ }
+
+#define WARN_INT(value, cmp) \
+ if (!(value cmp)) { \
+ ALOGE("%s: Invalid value. Check failed: (" #value " " #cmp ") with value: %" PRIi64, \
+ __FUNCTION__, value); \
+ }
+
APerformanceHintSession* APerformanceHint_createSession(APerformanceHintManager* manager,
const int32_t* threadIds, size_t size,
int64_t initialTargetWorkDurationNanos) {
+ VALIDATE_PTR(manager)
+ VALIDATE_PTR(threadIds)
return manager->createSession(threadIds, size, initialTargetWorkDurationNanos);
}
int64_t APerformanceHint_getPreferredUpdateRateNanos(APerformanceHintManager* manager) {
+ VALIDATE_PTR(manager)
return manager->getPreferredRateNanos();
}
int APerformanceHint_updateTargetWorkDuration(APerformanceHintSession* session,
int64_t targetDurationNanos) {
+ VALIDATE_PTR(session)
return session->updateTargetWorkDuration(targetDurationNanos);
}
int APerformanceHint_reportActualWorkDuration(APerformanceHintSession* session,
int64_t actualDurationNanos) {
+ VALIDATE_PTR(session)
+ VALIDATE_INT(actualDurationNanos, > 0)
return session->reportActualWorkDuration(actualDurationNanos);
}
void APerformanceHint_closeSession(APerformanceHintSession* session) {
+ VALIDATE_PTR(session)
delete session;
}
int APerformanceHint_sendHint(void* session, SessionHint hint) {
+ VALIDATE_PTR(session)
return reinterpret_cast<APerformanceHintSession*>(session)->sendHint(hint);
}
int APerformanceHint_setThreads(APerformanceHintSession* session, const pid_t* threadIds,
size_t size) {
- if (session == nullptr) {
- return EINVAL;
- }
+ VALIDATE_PTR(session)
+ VALIDATE_PTR(threadIds)
return session->setThreads(threadIds, size);
}
int APerformanceHint_getThreadIds(void* aPerformanceHintSession, int32_t* const threadIds,
size_t* const size) {
- if (aPerformanceHintSession == nullptr) {
- return EINVAL;
- }
+ VALIDATE_PTR(aPerformanceHintSession)
return static_cast<APerformanceHintSession*>(aPerformanceHintSession)
->getThreadIds(threadIds, size);
}
int APerformanceHint_setPreferPowerEfficiency(APerformanceHintSession* session, bool enabled) {
+ VALIDATE_PTR(session)
return session->setPreferPowerEfficiency(enabled);
}
int APerformanceHint_reportActualWorkDuration2(APerformanceHintSession* session,
- AWorkDuration* workDuration) {
- if (session == nullptr || workDuration == nullptr) {
- ALOGE("Invalid value: (session %p, workDuration %p)", session, workDuration);
- return EINVAL;
- }
- return session->reportActualWorkDuration(workDuration);
+ AWorkDuration* workDurationPtr) {
+ VALIDATE_PTR(session)
+ VALIDATE_PTR(workDurationPtr)
+ WorkDuration& workDuration = *static_cast<WorkDuration*>(workDurationPtr);
+ VALIDATE_INT(workDuration.workPeriodStartTimestampNanos, > 0)
+ VALIDATE_INT(workDuration.actualTotalDurationNanos, > 0)
+ VALIDATE_INT(workDuration.actualCpuDurationNanos, > 0)
+ VALIDATE_INT(workDuration.actualGpuDurationNanos, >= 0)
+ return session->reportActualWorkDuration(workDurationPtr);
}
AWorkDuration* AWorkDuration_create() {
@@ -492,46 +495,36 @@
}
void AWorkDuration_release(AWorkDuration* aWorkDuration) {
- if (aWorkDuration == nullptr) {
- ALOGE("%s: aWorkDuration is nullptr", __FUNCTION__);
- }
+ VALIDATE_PTR(aWorkDuration)
delete aWorkDuration;
}
void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* aWorkDuration,
int64_t workPeriodStartTimestampNanos) {
- if (aWorkDuration == nullptr || workPeriodStartTimestampNanos <= 0) {
- ALOGE("%s: Invalid value. (AWorkDuration: %p, workPeriodStartTimestampNanos: %" PRIi64 ")",
- __FUNCTION__, aWorkDuration, workPeriodStartTimestampNanos);
- }
+ VALIDATE_PTR(aWorkDuration)
+ WARN_INT(workPeriodStartTimestampNanos, > 0)
static_cast<WorkDuration*>(aWorkDuration)->workPeriodStartTimestampNanos =
workPeriodStartTimestampNanos;
}
void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* aWorkDuration,
int64_t actualTotalDurationNanos) {
- if (aWorkDuration == nullptr || actualTotalDurationNanos <= 0) {
- ALOGE("%s: Invalid value. (AWorkDuration: %p, actualTotalDurationNanos: %" PRIi64 ")",
- __FUNCTION__, aWorkDuration, actualTotalDurationNanos);
- }
+ VALIDATE_PTR(aWorkDuration)
+ WARN_INT(actualTotalDurationNanos, > 0)
static_cast<WorkDuration*>(aWorkDuration)->actualTotalDurationNanos = actualTotalDurationNanos;
}
void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* aWorkDuration,
int64_t actualCpuDurationNanos) {
- if (aWorkDuration == nullptr || actualCpuDurationNanos <= 0) {
- ALOGE("%s: Invalid value. (AWorkDuration: %p, actualCpuDurationNanos: %" PRIi64 ")",
- __FUNCTION__, aWorkDuration, actualCpuDurationNanos);
- }
+ VALIDATE_PTR(aWorkDuration)
+ WARN_INT(actualCpuDurationNanos, > 0)
static_cast<WorkDuration*>(aWorkDuration)->actualCpuDurationNanos = actualCpuDurationNanos;
}
void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* aWorkDuration,
int64_t actualGpuDurationNanos) {
- if (aWorkDuration == nullptr || actualGpuDurationNanos < 0) {
- ALOGE("%s: Invalid value. (AWorkDuration: %p, actualGpuDurationNanos: %" PRIi64 ")",
- __FUNCTION__, aWorkDuration, actualGpuDurationNanos);
- }
+ VALIDATE_PTR(aWorkDuration)
+ WARN_INT(actualGpuDurationNanos, >= 0)
static_cast<WorkDuration*>(aWorkDuration)->actualGpuDurationNanos = actualGpuDurationNanos;
}
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index dc2a625..3524f8c 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -45,7 +45,7 @@
package android.nfc.cardemulation {
public final class CardEmulation {
- method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public android.nfc.cardemulation.ApduServiceInfo getPreferredPaymentService();
+ method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static android.content.ComponentName getPreferredPaymentService(@NonNull android.content.Context);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int);
}
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 0943392..9d38e4c 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -16,6 +16,7 @@
package android.nfc.cardemulation;
+import android.Manifest;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -23,6 +24,7 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
+import android.annotation.UserHandleAware;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.content.ComponentName;
@@ -1138,31 +1140,28 @@
}
/**
- * Returns the {@link Settings.Secure#NFC_PAYMENT_DEFAULT_COMPONENT} for the given user.
+ * Returns the value of {@link Settings.Secure#NFC_PAYMENT_DEFAULT_COMPONENT}.
+ *
+ * @param context A context
+ * @return A ComponentName for the setting value, or null.
*
* @hide
*/
@SystemApi
+ @UserHandleAware
@RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @SuppressWarnings("AndroidFrameworkClientSidePermissionCheck")
@FlaggedApi(android.permission.flags.Flags.FLAG_WALLET_ROLE_ENABLED)
@Nullable
- public ApduServiceInfo getPreferredPaymentService() {
- try {
- return sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return null;
- }
- try {
- return sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return null;
- }
- }
- }
+ public static ComponentName getPreferredPaymentService(@NonNull Context context) {
+ context.checkCallingOrSelfPermission(Manifest.permission.NFC_PREFERRED_PAYMENT_INFO);
+ String defaultPaymentComponent = Settings.Secure.getString(context.getContentResolver(),
+ Constants.SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT);
+ if (defaultPaymentComponent == null) {
+ return null;
+ }
+
+ return ComponentName.unflattenFromString(defaultPaymentComponent);
+ }
}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index c2cb757..bd56aae 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -16,6 +16,7 @@
static_libs: [
"androidx.localbroadcastmanager_localbroadcastmanager",
"androidx.room_room-runtime",
+ "androidx.sqlite_sqlite",
"zxing-core",
"guava",
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
index 84fea15..d92a863 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
@@ -35,8 +35,8 @@
/**
* Scope for the children of [MoreOptionsAction].
*/
-interface MoreOptionsScope {
- fun dismiss()
+abstract class MoreOptionsScope {
+ abstract fun dismiss()
@Composable
fun MenuItem(text: String, enabled: Boolean = true, onClick: () -> Unit) {
@@ -60,7 +60,7 @@
val onDismiss = { expanded = false }
DropdownMenu(expanded = expanded, onDismissRequest = onDismiss) {
val moreOptionsScope = remember(this) {
- object : MoreOptionsScope {
+ object : MoreOptionsScope() {
override fun dismiss() {
onDismiss()
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
index 983284c..2ccf323 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
@@ -130,7 +130,7 @@
}
private fun setContent(restrictions: Restrictions) {
- val fakeMoreOptionsScope = object : MoreOptionsScope {
+ val fakeMoreOptionsScope = object : MoreOptionsScope() {
override fun dismiss() {}
}
composeTestRule.setContent {
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
index 5d520ce..7e2d0af 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
@@ -21,6 +21,8 @@
import android.content.res.Configuration;
import android.content.res.Resources;
+import androidx.annotation.NonNull;
+
/**
* A class for applying config changes and determing if doing so resulting in any "interesting"
* changes.
@@ -48,8 +50,15 @@
*/
@SuppressLint("NewApi")
public boolean applyNewConfig(Resources res) {
+ return applyNewConfig(res.getConfiguration());
+ }
+
+ /**
+ * Applies the given config change and returns whether an "interesting" change happened.
+ */
+ public boolean applyNewConfig(@NonNull Configuration configuration) {
int configChanges = mLastConfiguration.updateFrom(
- Configuration.generateDelta(mLastConfiguration, res.getConfiguration()));
+ Configuration.generateDelta(mLastConfiguration, configuration));
return (configChanges & (mFlags)) != 0;
}
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index cc63996..d384542 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -343,6 +343,7 @@
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_GLASSES" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
<uses-permission android:name="android.permission.USE_COMPANION_TRANSPORTS" />
+ <uses-permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE" />
<uses-permission android:name="android.permission.MANAGE_APPOPS" />
<uses-permission android:name="android.permission.WATCH_APPOPS" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index d61ae7e..80656e9 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -361,6 +361,7 @@
"androidx.test.ext.junit",
"androidx.test.ext.truth",
"kotlin-test",
+ "SystemUICustomizationTestUtils",
],
libs: [
"android.test.runner",
@@ -439,6 +440,7 @@
"androidx.test.ext.junit",
"inline-mockito-robolectric-prebuilt",
"platform-parametric-runner-lib",
+ "SystemUICustomizationTestUtils",
],
libs: [
"android.test.runner",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 168e6e0..54ab5d1 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -899,6 +899,14 @@
android:exported="true"
/>
+ <activity
+ android:name=".volume.panel.ui.activity.VolumePanelActivity"
+ android:label="@string/sound_settings"
+ android:excludeFromRecents="true"
+ android:exported="false"
+ android:launchMode="singleInstance"
+ android:theme="@style/Theme.VolumePanelActivity" />
+
<activity android:name=".wallet.ui.WalletActivity"
android:label="@string/wallet_title"
android:theme="@style/Wallet.Theme"
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index 2052e2c..3c32594 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -17,6 +17,7 @@
package com.android.systemui.compose
+import android.app.Dialog
import android.content.Context
import android.view.View
import android.view.WindowInsets
@@ -26,11 +27,14 @@
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
+import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
@@ -60,6 +64,14 @@
throwComposeUnavailableError()
}
+ override fun setVolumePanelActivityContent(
+ activity: ComponentActivity,
+ viewModel: VolumePanelViewModel,
+ onDismissAnimationFinished: () -> Unit,
+ ) {
+ throwComposeUnavailableError()
+ }
+
override fun createFooterActionsView(
context: Context,
viewModel: FooterActionsViewModel,
@@ -78,6 +90,13 @@
throwComposeUnavailableError()
}
+ override fun createStickyKeysDialog(
+ dialogFactory: SystemUIDialogFactory,
+ viewModel: StickyKeysIndicatorViewModel
+ ): Dialog {
+ throwComposeUnavailableError()
+ }
+
override fun createCommunalView(
context: Context,
viewModel: BaseCommunalViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
similarity index 77%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
index 22a74d2..c8dae76 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.volume.panel.component.bottombar
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import dagger.Module
+
+@Module interface BottomBarModule
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index b607d59..afb860e 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -16,6 +16,7 @@
package com.android.systemui.compose
+import android.app.Dialog
import android.content.Context
import android.graphics.Point
import android.view.View
@@ -38,6 +39,8 @@
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
+import com.android.systemui.keyboard.stickykeys.ui.view.StickyKeysIndicator
+import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
import com.android.systemui.people.ui.compose.PeopleScreen
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -47,6 +50,10 @@
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.scene.ui.composable.SceneContainer
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.statusbar.phone.create
+import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -86,6 +93,19 @@
}
}
+ override fun setVolumePanelActivityContent(
+ activity: ComponentActivity,
+ viewModel: VolumePanelViewModel,
+ onDismissAnimationFinished: () -> Unit,
+ ) {
+ activity.setContent {
+ VolumePanelRoot(
+ viewModel = viewModel,
+ onDismissAnimationFinished = onDismissAnimationFinished,
+ )
+ }
+ }
+
override fun createFooterActionsView(
context: Context,
viewModel: FooterActionsViewModel,
@@ -120,6 +140,13 @@
}
}
+ override fun createStickyKeysDialog(
+ dialogFactory: SystemUIDialogFactory,
+ viewModel: StickyKeysIndicatorViewModel
+ ): Dialog {
+ return dialogFactory.create { StickyKeysIndicator(viewModel) }
+ }
+
override fun createCommunalView(
context: Context,
viewModel: BaseCommunalViewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index c073b79b..a22fecf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -64,12 +64,11 @@
transitions = sceneTransitions,
)
- // Don't show hub mode UI if keyguard is not present. This is important since we're in the
- // shade, which can be opened from many locations.
- val isKeyguardShowing by viewModel.isKeyguardVisible.collectAsState(initial = false)
+ // Don't show hub mode UI if communal is not available. Communal is only available if it has
+ // been enabled via settings and either keyguard is showing, or, the device is currently
+ // dreaming.
val isCommunalAvailable by viewModel.isCommunalAvailable.collectAsState()
-
- if (!isKeyguardShowing || !isCommunalAvailable) {
+ if (!isCommunalAvailable) {
return
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
new file mode 100644
index 0000000..68e57b5
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.keyboard.stickykeys.ui.view
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+
+@Composable
+fun StickyKeysIndicator(viewModel: StickyKeysIndicatorViewModel) {
+ val stickyKeys by viewModel.indicatorContent.collectAsState(emptyMap())
+ StickyKeysIndicator(stickyKeys)
+}
+
+@Composable
+fun StickyKeysIndicator(stickyKeys: Map<ModifierKey, Locked>, modifier: Modifier = Modifier) {
+ Surface(
+ color = MaterialTheme.colorScheme.surface,
+ shape = MaterialTheme.shapes.medium,
+ modifier = modifier
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.padding(16.dp)
+ ) {
+ stickyKeys.forEach { (key, isLocked) ->
+ key(key) {
+ Text(
+ text = key.text,
+ fontWeight = if (isLocked.locked) FontWeight.Bold else FontWeight.Normal
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index 9778e53..c027c49 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -16,17 +16,16 @@
package com.android.systemui.qs.ui.composable
-import android.view.ContextThemeWrapper
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
@@ -53,14 +52,6 @@
}
}
-@Composable
-private fun QuickSettingsTheme(content: @Composable () -> Unit) {
- val context = LocalContext.current
- val themedContext =
- remember(context) { ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) }
- CompositionLocalProvider(LocalContext provides themedContext) { content() }
-}
-
private fun SceneScope.stateForQuickSettingsContent(): QSSceneAdapter.State {
return when (val transitionState = layoutState.transitionState) {
is TransitionState.Idle -> {
@@ -115,6 +106,7 @@
modifier: Modifier = Modifier,
) {
val qsView by qsSceneAdapter.qsView.collectAsState(null)
+ val isCustomizing by qsSceneAdapter.isCustomizing.collectAsState()
QuickSettingsTheme {
val context = LocalContext.current
@@ -124,14 +116,27 @@
}
}
qsView?.let { view ->
- AndroidView(
- modifier = modifier.fillMaxSize().background(colorAttr(R.attr.underSurface)),
- factory = { _ ->
- qsSceneAdapter.setState(state)
- view
- },
- update = { qsSceneAdapter.setState(state) }
- )
+ Box(
+ modifier =
+ modifier
+ .fillMaxWidth()
+ .then(
+ if (isCustomizing) {
+ Modifier.fillMaxHeight()
+ } else {
+ Modifier.wrapContentHeight()
+ }
+ )
+ ) {
+ AndroidView(
+ modifier = Modifier.fillMaxWidth().background(colorAttr(R.attr.underSurface)),
+ factory = { _ ->
+ qsSceneAdapter.setState(state)
+ view
+ },
+ update = { qsSceneAdapter.setState(state) }
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index d8c7290..bbfe0fd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -24,31 +24,44 @@
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
+import androidx.compose.foundation.clipScrollableContainer
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.TransitionState
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
+import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.scene.ui.composable.toTransitionSceneKey
import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.Shade
@@ -105,57 +118,120 @@
) {
// TODO(b/280887232): implement the real UI.
Box(modifier = modifier.fillMaxSize()) {
- Box(modifier = Modifier.fillMaxSize()) {
- val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
- val collapsedHeaderHeight =
- with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
- Spacer(
- modifier =
- Modifier.element(Shade.Elements.ScrimBackground)
- .fillMaxSize()
- .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
- )
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier =
- Modifier.fillMaxSize().padding(start = 16.dp, end = 16.dp, bottom = 48.dp)
- ) {
- when (LocalWindowSizeClass.current.widthSizeClass) {
- WindowWidthSizeClass.Compact ->
- AnimatedVisibility(
- visible = !isCustomizing,
- enter =
- expandVertically(
- animationSpec = tween(1000),
- initialHeight = { collapsedHeaderHeight },
- ) + fadeIn(tween(1000)),
- exit =
- shrinkVertically(
- animationSpec = tween(1000),
- targetHeight = { collapsedHeaderHeight },
- shrinkTowards = Alignment.Top,
- ) + fadeOut(tween(1000)),
- ) {
- ExpandedShadeHeader(
+ val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
+ val collapsedHeaderHeight =
+ with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val footerActionsViewModel =
+ remember(lifecycleOwner, viewModel) {
+ viewModel.getFooterActionsViewModel(lifecycleOwner)
+ }
+ val scrollState = rememberScrollState()
+ // When animating into the scene, we don't want it to be able to scroll, as it could mess
+ // up with the expansion animation.
+ val isScrollable =
+ when (val state = layoutState.transitionState) {
+ is TransitionState.Idle -> true
+ is TransitionState.Transition -> {
+ state.fromScene == SceneKey.QuickSettings.toTransitionSceneKey()
+ }
+ }
+
+ LaunchedEffect(isCustomizing, scrollState) {
+ if (isCustomizing) {
+ scrollState.scrollTo(0)
+ }
+ }
+
+ // This is the background for the whole scene, as the elements don't necessarily provide
+ // a background that extends to the edges.
+ Spacer(
+ modifier =
+ Modifier.element(Shade.Elements.ScrimBackground)
+ .fillMaxSize()
+ .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
+ )
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier =
+ Modifier.fillMaxSize()
+ // bottom should be tied to insets
+ .padding(bottom = 16.dp)
+ ) {
+ Box(modifier = Modifier.fillMaxSize().weight(1f)) {
+ val shadeHeaderAndQuickSettingsModifier =
+ if (isCustomizing) {
+ Modifier.fillMaxHeight().align(Alignment.TopCenter)
+ } else {
+ Modifier.verticalNestedScrollToScene()
+ .verticalScroll(
+ scrollState,
+ enabled = isScrollable,
+ )
+ .clipScrollableContainer(Orientation.Horizontal)
+ .fillMaxWidth()
+ .wrapContentHeight(unbounded = true)
+ .align(Alignment.TopCenter)
+ }
+
+ Column(
+ modifier = shadeHeaderAndQuickSettingsModifier,
+ ) {
+ when (LocalWindowSizeClass.current.widthSizeClass) {
+ WindowWidthSizeClass.Compact ->
+ AnimatedVisibility(
+ visible = !isCustomizing,
+ enter =
+ expandVertically(
+ animationSpec = tween(100),
+ initialHeight = { collapsedHeaderHeight },
+ ) + fadeIn(tween(100)),
+ exit =
+ shrinkVertically(
+ animationSpec = tween(100),
+ targetHeight = { collapsedHeaderHeight },
+ shrinkTowards = Alignment.Top,
+ ) + fadeOut(tween(100)),
+ ) {
+ ExpandedShadeHeader(
+ viewModel = viewModel.shadeHeaderViewModel,
+ createTintedIconManager = createTintedIconManager,
+ createBatteryMeterViewController =
+ createBatteryMeterViewController,
+ statusBarIconController = statusBarIconController,
+ modifier = Modifier.padding(horizontal = 16.dp),
+ )
+ }
+ else ->
+ CollapsedShadeHeader(
viewModel = viewModel.shadeHeaderViewModel,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
+ modifier = Modifier.padding(horizontal = 16.dp),
)
- }
- else ->
- CollapsedShadeHeader(
- viewModel = viewModel.shadeHeaderViewModel,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
- )
+ }
+ Spacer(modifier = Modifier.height(16.dp))
+ // This view has its own horizontal padding
+ QuickSettings(
+ modifier = Modifier.sysuiResTag("expanded_qs_scroll_view"),
+ viewModel.qsSceneAdapter,
+ )
}
- Spacer(modifier = Modifier.height(16.dp))
- QuickSettings(
- modifier = Modifier.fillMaxHeight(),
- viewModel.qsSceneAdapter,
- )
+ }
+ AnimatedVisibility(
+ visible = !isCustomizing,
+ modifier = Modifier.align(Alignment.CenterHorizontally).fillMaxWidth()
+ ) {
+ QuickSettingsTheme {
+ // This view has its own horizontal padding
+ // TODO(b/321716470) This should use a lifecycle tied to the scene.
+ FooterActions(
+ viewModel = footerActionsViewModel,
+ qsVisibilityLifecycleOwner = lifecycleOwner,
+ modifier = Modifier.element(QuickSettings.Elements.FooterActions)
+ )
+ }
}
}
HeadsUpNotificationSpace(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsTheme.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsTheme.kt
new file mode 100644
index 0000000..87b6f95b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsTheme.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.qs.ui.composable
+
+import android.view.ContextThemeWrapper
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import com.android.systemui.res.R
+
+@Composable
+fun QuickSettingsTheme(content: @Composable () -> Unit) {
+ val context = LocalContext.current
+ val themedContext =
+ remember(context) { ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) }
+ CompositionLocalProvider(LocalContext provides themedContext) { content() }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
new file mode 100644
index 0000000..43d5453
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.bottombar
+
+import com.android.systemui.volume.panel.component.bottombar.ui.BottomBarComponent
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.domain.AlwaysAvailableCriteria
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface BottomBarModule {
+
+ @Binds
+ @IntoMap
+ @StringKey(VolumePanelComponents.BOTTOM_BAR)
+ fun bindMediaVolumeSliderComponent(component: BottomBarComponent): VolumePanelUiComponent
+
+ @Binds
+ @IntoMap
+ @StringKey(VolumePanelComponents.BOTTOM_BAR)
+ fun bindComponentAvailabilityCriteria(
+ defaultCriteria: AlwaysAvailableCriteria
+ ): ComponentAvailabilityCriteria
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
new file mode 100644
index 0000000..03c07f7
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.volume.panel.component.bottombar.ui
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformButton
+import com.android.compose.PlatformOutlinedButton
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.bottombar.ui.viewmodel.BottomBarViewModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
+import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
+import javax.inject.Inject
+
+@VolumePanelScope
+class BottomBarComponent
+@Inject
+constructor(
+ private val viewModel: BottomBarViewModel,
+) : ComposeVolumePanelUiComponent {
+
+ @Composable
+ override fun VolumePanelComposeScope.Content(modifier: Modifier) {
+ Row(
+ modifier = modifier.height(48.dp).fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ PlatformOutlinedButton(onClick = viewModel::onSettingsClicked) {
+ Text(text = stringResource(R.string.volume_panel_dialog_settings_button))
+ }
+ PlatformButton(onClick = viewModel::onDoneClicked) {
+ Text(text = stringResource(R.string.inline_done_button))
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/ComposeVolumePanelUiComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/ComposeVolumePanelUiComponent.kt
new file mode 100644
index 0000000..e1834ee
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/ComposeVolumePanelUiComponent.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+
+/**
+ * Compose implementation of [VolumePanelUiComponent]. Each new UI component should implement this
+ * interface.
+ */
+interface ComposeVolumePanelUiComponent : VolumePanelUiComponent {
+
+ @Composable fun VolumePanelComposeScope.Content(modifier: Modifier)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
new file mode 100644
index 0000000..dcd22fe
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.volume.panel.ui.composable
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
+
+@Composable
+fun VolumePanelComposeScope.VerticalVolumePanelContent(
+ components: List<ComponentState>,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(20.dp),
+ ) {
+ for (component in components) {
+ AnimatedVisibility(component.isVisible) {
+ with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
new file mode 100644
index 0000000..c70c6b1
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.volume.panel.ui.composable
+
+import android.content.res.Configuration.Orientation
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
+
+class VolumePanelComposeScope(private val state: VolumePanelState) {
+
+ /**
+ * Layout orientation of the panel. It doesn't necessarily aligns with the device orientation,
+ * because in some cases we want to show bigger version of a portrait orientation when the
+ * device is in landscape.
+ */
+ @Orientation
+ val orientation: Int
+ get() = state.orientation
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
new file mode 100644
index 0000000..3487184
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.volume.panel.ui.composable
+
+import android.content.res.Configuration
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.MutableTransitionState
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.dimensionResource
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+
+@Composable
+fun VolumePanelRoot(
+ viewModel: VolumePanelViewModel,
+ onDismissAnimationFinished: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ PlatformTheme(isSystemInDarkTheme()) {
+ val state: VolumePanelState by viewModel.volumePanelState.collectAsState()
+ val components by viewModel.componentsLayout.collectAsState(null)
+
+ val transitionState =
+ remember { MutableTransitionState(false) }.apply { targetState = state.isVisible }
+
+ LaunchedEffect(transitionState.targetState, transitionState.isIdle) {
+ if (!transitionState.targetState && transitionState.isIdle) {
+ onDismissAnimationFinished()
+ }
+ }
+
+ Column(
+ modifier =
+ modifier
+ .fillMaxSize()
+ .statusBarsPadding()
+ .clickable(onClick = { viewModel.dismissPanel() }),
+ verticalArrangement = Arrangement.Bottom,
+ ) {
+ AnimatedVisibility(
+ visibleState = transitionState,
+ enter = slideInVertically { it },
+ exit = slideOutVertically { it },
+ ) {
+ val radius = dimensionResource(R.dimen.volume_panel_corner_radius)
+ Surface(
+ shape = RoundedCornerShape(topStart = radius, topEnd = radius),
+ color = MaterialTheme.colorScheme.surfaceBright,
+ ) {
+ Column {
+ components?.let { componentsState ->
+ with(VolumePanelComposeScope(state)) { Components(componentsState) }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun VolumePanelComposeScope.Components(state: ComponentsLayout) {
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ VerticalVolumePanelContent(
+ components = state.contentComponents,
+ modifier = Modifier.padding(dimensionResource(R.dimen.volume_panel_content_padding)),
+ )
+ } else {
+ TODO("Add landscape layout")
+ }
+
+ val horizontalPadding = dimensionResource(R.dimen.volume_panel_bottom_bar_horizontal_padding)
+ if (state.bottomBarComponent.isVisible) {
+ with(state.bottomBarComponent.component as ComposeVolumePanelUiComponent) {
+ Content(
+ Modifier.navigationBarsPadding()
+ .padding(
+ start = horizontalPadding,
+ end = horizontalPadding,
+ bottom = dimensionResource(R.dimen.volume_panel_bottom_bar_bottom_padding),
+ )
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp
index 1d18496..81b5bd4 100644
--- a/packages/SystemUI/customization/Android.bp
+++ b/packages/SystemUI/customization/Android.bp
@@ -34,15 +34,19 @@
"PluginCoreLib",
"SystemUIPluginLib",
"SystemUIUnfoldLib",
- "androidx.dynamicanimation_dynamicanimation",
+ "kotlinx_coroutines",
+ "dagger2",
+ "jsr330",
+ ],
+ libs: [
+ // Keep android-specific libraries as libs instead of static_libs, so that they don't break
+ // things when included as transitive dependencies in robolectric targets.
"androidx.concurrent_concurrent-futures",
+ "androidx.dynamicanimation_dynamicanimation",
"androidx.lifecycle_lifecycle-runtime-ktx",
"androidx.lifecycle_lifecycle-viewmodel-ktx",
"androidx.recyclerview_recyclerview",
"kotlinx_coroutines_android",
- "kotlinx_coroutines",
- "dagger2",
- "jsr330",
],
resource_dirs: [
"res",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
index 8d6d052..a862112 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
@@ -37,6 +37,8 @@
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -112,6 +114,7 @@
windowManager,
displayStateInteractor,
Optional.of(fingerprintInteractiveToAuthProvider),
+ kosmos.biometricSettingsRepository,
kosmos.keyguardTransitionInteractor,
SideFpsLogger(logcatLogBuffer("SfpsLogger"))
)
@@ -420,6 +423,7 @@
@Test
fun isProlongedTouchRequiredForAuthentication_dependsOnSettingsToggle() =
testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
val isEnabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication)
setupFingerprint(FingerprintSensorType.POWER_BUTTON)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 86279ef..1b7117f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -124,6 +124,7 @@
keyguardRepository.setIsEncryptedOrLockdown(false)
userRepository.setSelectedUserInfo(mainUser)
+ keyguardRepository.setKeyguardShowing(true)
runCurrent()
assertThat(isAvailable).isTrue()
@@ -150,12 +151,27 @@
keyguardRepository.setIsEncryptedOrLockdown(false)
userRepository.setSelectedUserInfo(secondaryUser)
+ keyguardRepository.setKeyguardShowing(true)
runCurrent()
assertThat(isAvailable).isFalse()
}
@Test
+ fun isCommunalAvailable_whenDreaming_true() =
+ testScope.runTest {
+ val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+ assertThat(isAvailable).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ userRepository.setSelectedUserInfo(mainUser)
+ keyguardRepository.setDreaming(true)
+ runCurrent()
+
+ assertThat(isAvailable).isTrue()
+ }
+
+ @Test
fun updateAppWidgetHostActive_uponStorageUnlockAsMainUser_true() =
testScope.runTest {
collectLastValue(underTest.isCommunalAvailable)
@@ -163,6 +179,7 @@
keyguardRepository.setIsEncryptedOrLockdown(false)
userRepository.setSelectedUserInfo(mainUser)
+ keyguardRepository.setKeyguardShowing(true)
runCurrent()
assertThat(widgetRepository.isHostActive()).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
new file mode 100644
index 0000000..74c1970
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.dreams.touch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CommunalTouchHandlerTest extends SysuiTestCase {
+ @Mock
+ CentralSurfaces mCentralSurfaces;
+ @Mock
+ NotificationShadeWindowController mNotificationShadeWindowController;
+ @Mock
+ DreamTouchHandler.TouchSession mTouchSession;
+ CommunalTouchHandler mTouchHandler;
+
+ private static final int INITIATION_WIDTH = 20;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTouchHandler = new CommunalTouchHandler(
+ Optional.of(mCentralSurfaces),
+ mNotificationShadeWindowController,
+ INITIATION_WIDTH);
+ }
+
+ @Test
+ public void testSessionStartForcesShadeOpen() {
+ mTouchHandler.onSessionStart(mTouchSession);
+ verify(mNotificationShadeWindowController).setForcePluginOpen(true, mTouchHandler);
+ }
+
+ @Test
+ public void testEventPropagation() {
+ final MotionEvent motionEvent = Mockito.mock(MotionEvent.class);
+
+ final ArgumentCaptor<InputChannelCompat.InputEventListener>
+ inputEventListenerArgumentCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+
+ mTouchHandler.onSessionStart(mTouchSession);
+ verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture());
+ inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent);
+ verify(mCentralSurfaces).handleDreamTouch(motionEvent);
+ }
+
+ @Test
+ public void testTouchPilferingOnScroll() {
+ final MotionEvent motionEvent1 = Mockito.mock(MotionEvent.class);
+ final MotionEvent motionEvent2 = Mockito.mock(MotionEvent.class);
+
+ final ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerArgumentCaptor =
+ ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+
+ mTouchHandler.onSessionStart(mTouchSession);
+ verify(mTouchSession).registerGestureListener(gestureListenerArgumentCaptor.capture());
+
+ assertThat(gestureListenerArgumentCaptor.getValue()
+ .onScroll(motionEvent1, motionEvent2, 1, 1))
+ .isTrue();
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt
new file mode 100644
index 0000000..ea766f8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.haptics.slider
+
+import android.widget.SeekBar
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.fakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class SeekableSliderHapticPluginTest : SysuiTestCase() {
+
+ private val kosmos = Kosmos()
+
+ @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule()
+ @Mock private lateinit var vibratorHelper: VibratorHelper
+ private val seekBar = SeekBar(mContext)
+ private lateinit var plugin: SeekableSliderHapticPlugin
+
+ @Before
+ fun setup() {
+ whenever(vibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(intArrayOf(0))
+ }
+
+ @Test
+ fun start_beginsTrackingSlider() = runOnStartedPlugin { assertThat(plugin.isTracking).isTrue() }
+
+ @Test
+ fun stop_stopsTrackingSlider() = runOnStartedPlugin {
+ // WHEN called to stop
+ plugin.stop()
+
+ // THEN stops tracking
+ assertThat(plugin.isTracking).isFalse()
+ }
+
+ @Test
+ fun start_afterStop_startsTheTrackingAgain() = runOnStartedPlugin {
+ // WHEN the plugin is restarted
+ plugin.stop()
+ plugin.start()
+
+ // THEN the tracking begins again
+ assertThat(plugin.isTracking).isTrue()
+ }
+
+ @Test
+ fun onKeyDown_startsWaiting() = runOnStartedPlugin {
+ // WHEN a keyDown event is recorded
+ plugin.onKeyDown()
+
+ // THEN the timer starts waiting
+ assertThat(plugin.isKeyUpTimerWaiting).isTrue()
+ }
+
+ @Test
+ fun keyUpWaitComplete_triggersOnArrowUp() = runOnStartedPlugin {
+ // GIVEN an onKeyDown that starts the wait and a program progress change that advances the
+ // slider state to ARROW_HANDLE_MOVED_ONCE
+ plugin.onKeyDown()
+ plugin.onProgressChanged(seekBar, 50, false)
+ testScheduler.runCurrent()
+ assertThat(plugin.trackerState).isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the key-up wait completes after the timeout plus a small buffer
+ advanceTimeBy(KEY_UP_TIMEOUT + 10L)
+
+ // THEN the onArrowUp event is delivered causing the slider tracker to move to IDLE
+ assertThat(plugin.trackerState).isEqualTo(SliderState.IDLE)
+ assertThat(plugin.isKeyUpTimerWaiting).isFalse()
+ }
+
+ @Test
+ fun onKeyDown_whileWaiting_restartsWait() = runOnStartedPlugin {
+ // GIVEN an onKeyDown that starts the wait and a program progress change that advances the
+ // slider state to ARROW_HANDLE_MOVED_ONCE
+ plugin.onKeyDown()
+ plugin.onProgressChanged(seekBar, 50, false)
+ testScheduler.runCurrent()
+ assertThat(plugin.trackerState).isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN half the timeout period has elapsed and a new keyDown event occurs
+ advanceTimeBy(KEY_UP_TIMEOUT / 2)
+ plugin.onKeyDown()
+
+ // AFTER advancing by a period of time that should have complete the original wait
+ advanceTimeBy(KEY_UP_TIMEOUT / 2 + 10L)
+
+ // THEN the timer is still waiting and the slider tracker remains on ARROW_HANDLE_MOVED_ONCE
+ assertThat(plugin.isKeyUpTimerWaiting).isTrue()
+ assertThat(plugin.trackerState).isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE)
+ }
+
+ private fun runOnStartedPlugin(test: suspend TestScope.() -> Unit) =
+ with(kosmos) {
+ testScope.runTest {
+ createPlugin(this, UnconfinedTestDispatcher(testScheduler))
+ // GIVEN that the plugin is started
+ plugin.start()
+
+ // THEN run the test
+ test()
+ }
+ }
+
+ private fun createPlugin(scope: CoroutineScope, dispatcher: CoroutineDispatcher) {
+ plugin =
+ SeekableSliderHapticPlugin(
+ vibratorHelper,
+ kosmos.fakeSystemClock,
+ dispatcher,
+ scope,
+ )
+ }
+
+ companion object {
+ private const val KEY_UP_TIMEOUT = 100L
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
new file mode 100644
index 0000000..2fe4ef78
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
@@ -0,0 +1,189 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import android.content.applicationContext
+import android.hardware.biometrics.BiometricFingerprintConstants
+import android.os.PowerManager
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.sideFpsSensorInteractor
+import com.android.systemui.biometrics.fakeFingerprintInteractiveToAuthProvider
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+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.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.dozeServiceHost
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidJUnit4::class)
+class SideFpsProgressBarViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private lateinit var underTest: SideFpsProgressBarViewModel
+ private val testScope = kosmos.testScope
+ private lateinit var mTestableLooper: TestableLooper
+
+ @Before
+ fun setup() {
+ mTestableLooper = TestableLooper.get(this)
+ allowTestableLooperAsMainThread()
+ }
+
+ private suspend fun setupRestToUnlockEnabled() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_REST_TO_UNLOCK)
+ overrideResource(R.bool.config_restToUnlockSupported, true)
+ kosmos.fakeFingerprintPropertyRepository.setProperties(
+ 1,
+ SensorStrength.STRONG,
+ FingerprintSensorType.POWER_BUTTON,
+ mutableMapOf(Pair("sensor", mock()))
+ )
+ kosmos.fakeFingerprintInteractiveToAuthProvider.enabledForCurrentUser.value = true
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 0.0f,
+ transitionState = TransitionState.STARTED
+ )
+ )
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 1.0f,
+ transitionState = TransitionState.FINISHED
+ )
+ )
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ }
+
+ @Test
+ fun whenConfigDisabled_featureIsDisabled() =
+ testScope.runTest {
+ overrideResource(R.bool.config_restToUnlockSupported, false)
+ underTest = createViewModel()
+ val enabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication)
+
+ assertThat(enabled).isFalse()
+ }
+
+ @Test
+ fun whenConfigEnabledSensorIsPowerButtonAndSettingsToggleIsEnabled_featureIsEnabled() =
+ testScope.runTest {
+ overrideResource(R.bool.config_restToUnlockSupported, true)
+ underTest = createViewModel()
+ val enabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication)
+
+ assertThat(enabled).isFalse()
+ kosmos.fakeFingerprintPropertyRepository.setProperties(
+ 1,
+ SensorStrength.STRONG,
+ FingerprintSensorType.POWER_BUTTON,
+ mutableMapOf(Pair("sensor", mock()))
+ )
+ assertThat(enabled).isFalse()
+
+ kosmos.fakeFingerprintInteractiveToAuthProvider.enabledForCurrentUser.value = true
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+
+ runCurrent()
+ assertThat(enabled).isTrue()
+ }
+
+ @Test
+ fun whenFingerprintAcquiredStartsWhenNotDozing_wakesUpDevice() =
+ testScope.runTest {
+ setupRestToUnlockEnabled()
+ underTest = createViewModel()
+
+ kosmos.fakeKeyguardRepository.setIsDozing(false)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ AcquiredFingerprintAuthenticationStatus(
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+ )
+ )
+
+ runCurrent()
+
+ assertThat(kosmos.fakePowerRepository.lastWakeReason)
+ .isEqualTo(PowerManager.WAKE_REASON_BIOMETRIC)
+ }
+
+ @Test
+ fun whenFingerprintAcquiredStartsWhenDozing_pulsesAod() =
+ testScope.runTest {
+ setupRestToUnlockEnabled()
+ underTest = createViewModel()
+
+ kosmos.fakeKeyguardRepository.setIsDozing(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ AcquiredFingerprintAuthenticationStatus(
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+ )
+ )
+
+ runCurrent()
+
+ verify(kosmos.dozeServiceHost).fireSideFpsAcquisitionStarted()
+ }
+
+ private fun createViewModel() =
+ SideFpsProgressBarViewModel(
+ kosmos.applicationContext,
+ kosmos.deviceEntryFingerprintAuthInteractor,
+ kosmos.sideFpsSensorInteractor,
+ kosmos.dozeServiceHost,
+ kosmos.keyguardInteractor,
+ kosmos.displayStateInteractor,
+ kosmos.testDispatcher,
+ kosmos.applicationCoroutineScope,
+ kosmos.powerInteractor,
+ )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index d9b1ea1..cae20d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -16,12 +16,16 @@
package com.android.systemui.qs.ui.adapter
+import android.content.res.Configuration
import android.os.Bundle
+import android.view.Surface
import android.view.View
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.QSImpl
import com.android.systemui.qs.dagger.QSComponent
@@ -34,6 +38,7 @@
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import java.util.Locale
import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -81,11 +86,17 @@
.also { components.add(it) }
}
}
+ private val configuration = Configuration(context.resources.configuration)
+
+ private val fakeConfigurationRepository =
+ FakeConfigurationRepository().apply { onConfigurationChange(configuration) }
+ private val configurationInteractor = ConfigurationInteractor(fakeConfigurationRepository)
private val mockAsyncLayoutInflater =
mock<AsyncLayoutInflater>() {
whenever(inflate(anyInt(), nullable(), any())).then { invocation ->
val mockView = mock<View>()
+ whenever(mockView.context).thenReturn(context)
invocation
.getArgument<AsyncLayoutInflater.OnInflateFinishedListener>(2)
.onInflateFinished(
@@ -102,6 +113,7 @@
qsImplProvider,
testDispatcher,
testScope.backgroundScope,
+ configurationInteractor,
{ mockAsyncLayoutInflater },
)
@@ -297,6 +309,9 @@
@Test
fun reinflation_previousStateDestroyed() =
testScope.runTest {
+ // Run all flows... In particular, initial configuration propagation that could cause
+ // QSImpl to re-inflate.
+ runCurrent()
val qsImpl by collectLastValue(underTest.qsImpl)
underTest.inflate(context)
@@ -322,4 +337,81 @@
bundleArgCaptor.value,
)
}
+
+ @Test
+ fun changeInLocale_reinflation() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+
+ val oldQsImpl = qsImpl!!
+
+ val newLocale =
+ if (configuration.locales[0] == Locale("en-US")) {
+ Locale("es-UY")
+ } else {
+ Locale("en-US")
+ }
+ configuration.setLocale(newLocale)
+ fakeConfigurationRepository.onConfigurationChange(configuration)
+ runCurrent()
+
+ assertThat(oldQsImpl).isNotSameInstanceAs(qsImpl!!)
+ }
+
+ @Test
+ fun changeInFontSize_reinflation() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+
+ val oldQsImpl = qsImpl!!
+
+ configuration.fontScale *= 2
+ fakeConfigurationRepository.onConfigurationChange(configuration)
+ runCurrent()
+
+ assertThat(oldQsImpl).isNotSameInstanceAs(qsImpl!!)
+ }
+
+ @Test
+ fun changeInAssetPath_reinflation() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+
+ val oldQsImpl = qsImpl!!
+
+ configuration.assetsSeq += 1
+ fakeConfigurationRepository.onConfigurationChange(configuration)
+ runCurrent()
+
+ assertThat(oldQsImpl).isNotSameInstanceAs(qsImpl!!)
+ }
+
+ @Test
+ fun otherChangesInConfiguration_noReinflation_configurationChangeDispatched() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+
+ val oldQsImpl = qsImpl!!
+ configuration.densityDpi *= 2
+ configuration.windowConfiguration.maxBounds.scale(2f)
+ configuration.windowConfiguration.rotation = Surface.ROTATION_270
+ fakeConfigurationRepository.onConfigurationChange(configuration)
+ runCurrent()
+
+ assertThat(oldQsImpl).isSameInstanceAs(qsImpl!!)
+ verify(qsImpl!!).onConfigurationChanged(configuration)
+ verify(qsImpl!!.view).dispatchConfigurationChanged(configuration)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index d7a7941..42200a3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -23,6 +23,8 @@
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Direction
@@ -39,12 +41,16 @@
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -56,6 +62,12 @@
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() }
+ private val footerActionsViewModel = mock<FooterActionsViewModel>()
+ private val footerActionsViewModelFactory =
+ mock<FooterActionsViewModel.Factory> {
+ whenever(create(any())).thenReturn(footerActionsViewModel)
+ }
+ private val footerActionsController = mock<FooterActionsController>()
private var mobileIconsViewModel: MobileIconsViewModel =
MobileIconsViewModel(
@@ -94,6 +106,8 @@
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
+ footerActionsViewModelFactory,
+ footerActionsController,
)
}
@@ -125,4 +139,12 @@
)
)
}
+
+ @Test
+ fun gettingViewModelInitializesControllerOnlyOnce() {
+ underTest.getFooterActionsViewModel(mock())
+ underTest.getFooterActionsViewModel(mock())
+
+ verify(footerActionsController, times(1)).init()
+ }
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
index 3d9645a..b1736b1 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@@ -227,5 +227,10 @@
void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkBeforeSwitch);
// requires version 2
void onShowCsdWarning(@AudioManager.CsdWarning int csdWarning, int durationMs);
+
+ /**
+ * Callback function for when the volume changed due to a physical key press.
+ */
+ void onVolumeChangedFromKey();
}
}
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 16eba22..1365a11 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -17,6 +17,7 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/internet_connectivity_dialog"
android:layout_width="@dimen/large_dialog_width"
@@ -386,9 +387,8 @@
</LinearLayout>
</LinearLayout>
- <LinearLayout
+ <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/button_layout"
- android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
@@ -398,53 +398,58 @@
android:clickable="false"
android:focusable="false">
- <LinearLayout
+ <Button
+ android:id="@+id/apm_button"
+ style="@style/Widget.Dialog.Button.BorderButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="start|center_vertical"
- android:orientation="horizontal">
- <Button
- android:id="@+id/apm_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/turn_off_airplane_mode"
- android:ellipsize="end"
- android:maxLines="1"
- style="@style/Widget.Dialog.Button.BorderButton"
- android:clickable="true"
- android:focusable="true"/>
+ android:layout_marginEnd="10dp"
+ android:clickable="true"
+ android:ellipsize="end"
+ android:focusable="true"
+ android:maxLines="1"
+ android:text="@string/turn_off_airplane_mode"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/share_wifi_button"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
- <Button
- android:id="@+id/share_wifi_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/share_wifi_button_text"
- style="?android:attr/buttonBarNeutralButtonStyle"
- android:maxLines="1"
- android:ellipsize="end"
- android:clickable="true"
- android:focusable="true"
- android:visibility="gone"/>
- </LinearLayout>
-
- <LinearLayout
+ <Button
+ android:id="@+id/share_wifi_button"
+ style="?android:attr/buttonBarNeutralButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="16dp"
- android:layout_gravity="end|center_vertical">
- <Button
- android:id="@+id/done_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/inline_done_button"
- style="@style/Widget.Dialog.Button"
- android:maxLines="1"
- android:ellipsize="end"
- android:clickable="true"
- android:focusable="true"/>
- </LinearLayout>
- </LinearLayout>
+ android:layout_marginEnd="10dp"
+ android:clickable="true"
+ android:ellipsize="end"
+ android:focusable="true"
+ android:maxLines="1"
+ android:visibility="gone"
+ app:layout_constraintHorizontal_bias="0"
+ android:text="@string/share_wifi_button_text"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/done_button"
+ app:layout_constraintStart_toEndOf="@id/apm_button"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <Button
+ android:id="@+id/done_button"
+ style="@style/Widget.Dialog.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:ellipsize="end"
+ android:focusable="true"
+ android:maxLines="1"
+ android:text="@string/inline_done_button"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e8201ecb..4209c1f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1732,6 +1732,8 @@
<dimen name="communal_right_edge_swipe_region_width">16dp</dimen>
<!-- Height of area at top of communal hub where swipes should open the notification shade -->
<dimen name="communal_top_edge_swipe_region_height">32dp</dimen>
+ <!-- Height of area at bottom of communal hub where swipes should open the bouncer -->
+ <dimen name="communal_bottom_edge_swipe_region_height">32dp</dimen>
<dimen name="drag_and_drop_icon_size">70dp</dimen>
@@ -1803,6 +1805,9 @@
<dimen name="dream_overlay_complication_smartspace_padding">24dp</dimen>
<dimen name="dream_overlay_complication_smartspace_max_width">408dp</dimen>
+ <!-- The width of the swipe target to initiate opening communal hub over dreams. -->
+ <dimen name="communal_gesture_initiation_width">48dp</dimen>
+
<!-- The position of the end guide, which dream overlay complications can align their start with
if their end is aligned with the parent end. Represented as the percentage over from the
start of the parent container. -->
@@ -1936,5 +1941,9 @@
<dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
<!-- UDFPS view attributes -->
- <dimen name="udfps_icon_size">6mm</dimen>
+ <!-- UDFPS icon size in microns/um -->
+ <dimen name="udfps_icon_size" format="float">6000</dimen>
+ <!-- Microns/ums (1000 um = 1mm) per pixel for the given device. If unspecified, UI that
+ relies on this value will not be sized correctly. -->
+ <item name="pixel_pitch" format="float" type="dimen">-1</item>
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index ec4c7d5..8ec5ccd 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -227,6 +227,7 @@
<item type="id" name="ambient_indication_container" />
<item type="id" name="status_view_media_container" />
<item type="id" name="smart_space_barrier_bottom" />
+ <item type="id" name="weather_clock_date_and_icons_barrier_bottom" />
<!-- Privacy dialog -->
<item type="id" name="privacy_dialog_close_app_button" />
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 8e5d0da..ecce223 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -37,6 +37,7 @@
import android.app.admin.DevicePolicyManager;
import android.content.Intent;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricRequestConstants;
import android.media.AudioManager;
@@ -390,6 +391,11 @@
mSecurityViewFlipperController.updateConstraints(useSplitBouncer);
}
}
+
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ configureMode();
+ }
};
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index fe96099..3e8c6a7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1644,11 +1644,11 @@
void setAssistantVisible(boolean assistantVisible) {
mAssistantVisible = assistantVisible;
mLogger.logAssistantVisible(mAssistantVisible);
- if (getFaceAuthInteractor() != null) {
- getFaceAuthInteractor().onAssistantTriggeredOnLockScreen();
- }
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
if (mAssistantVisible) {
+ if (getFaceAuthInteractor() != null) {
+ getFaceAuthInteractor().onAssistantTriggeredOnLockScreen();
+ }
requestActiveUnlock(
ActiveUnlockConfig.ActiveUnlockRequestOrigin.ASSISTANT,
"assistant",
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
index 348b54e..c3dc2d4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
@@ -26,20 +26,24 @@
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.isDefaultOrientation
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.SideFpsLogger
import com.android.systemui.res.R
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
+@ExperimentalCoroutinesApi
@SysUISingleton
class SideFpsSensorInteractor
@Inject
@@ -49,6 +53,7 @@
windowManager: WindowManager,
displayStateInteractor: DisplayStateInteractor,
fingerprintInteractiveToAuthProvider: Optional<FingerprintInteractiveToAuthProvider>,
+ biometricSettingsRepository: BiometricSettingsRepository,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val logger: SideFpsLogger,
) {
@@ -84,13 +89,24 @@
.map { it ?: 0L }
.onEach { logger.authDurationChanged(it) }
+ private val isSettingEnabled: Flow<Boolean> =
+ biometricSettingsRepository.isFingerprintEnrolledAndEnabled
+ .flatMapLatest { enabledAndEnrolled ->
+ if (!enabledAndEnrolled || fingerprintInteractiveToAuthProvider.isEmpty) {
+ flowOf(false)
+ } else {
+ fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser
+ }
+ }
+ .onEach { logger.restToUnlockSettingEnabledChanged(it) }
+
val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
- if (fingerprintInteractiveToAuthProvider.isEmpty || !isProlongedTouchEnabledForDevice) {
+ if (!isProlongedTouchEnabledForDevice) {
flowOf(false)
} else {
combine(
isAvailable,
- fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser
+ isSettingEnabled,
) { sfpsAvailable, isSettingEnabled ->
sfpsAvailable && isSettingEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 4fc1b58..a77cc1f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.domain.interactor
import android.content.Context
+import android.util.Log
import android.view.MotionEvent
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
@@ -42,12 +43,23 @@
class UdfpsOverlayInteractor
@Inject
constructor(
- @Application context: Context,
+ @Application private val context: Context,
private val authController: AuthController,
private val selectedUserInteractor: SelectedUserInteractor,
@Application scope: CoroutineScope
) {
- private val iconSize: Int = context.resources.getDimensionPixelSize(R.dimen.udfps_icon_size)
+ private fun calculateIconSize(): Int {
+ val pixelPitch = context.resources.getFloat(R.dimen.pixel_pitch)
+ if (pixelPitch <= 0) {
+ Log.e(
+ "UdfpsOverlayInteractor",
+ "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device."
+ )
+ }
+ return (context.resources.getFloat(R.dimen.udfps_icon_size) / pixelPitch).toInt()
+ }
+
+ private var iconSize: Int = calculateIconSize()
/** Whether a touch is within the under-display fingerprint sensor area */
fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index 1353985..5f6ff82 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -72,6 +72,9 @@
val onAnyConfigurationChange: Flow<Unit> =
repository.onAnyConfigurationChange.onStart { emit(Unit) }
+ /** Emits the new configuration on any configuration change */
+ val configurationValues: Flow<Configuration> = repository.configurationValues
+
/** Emits the current resolution scaling factor */
val scaleForResolution: Flow<Float> = repository.scaleForResolution
}
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 44b0383..4c5871d 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
@@ -68,21 +68,23 @@
private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter
) {
-
/** Whether communal features are enabled. */
val isCommunalEnabled: Boolean
get() = communalRepository.isCommunalEnabled
- /** Whether communal features are enabled and available. */
- val isCommunalAvailable: StateFlow<Boolean> =
+ val isCommunalAvailable =
flowOf(isCommunalEnabled)
.flatMapLatest { enabled ->
if (enabled)
combine(
keyguardInteractor.isEncryptedOrLockdown,
userRepository.selectedUserInfo,
- ) { isEncryptedOrLockdown, selectedUserInfo ->
- !isEncryptedOrLockdown && selectedUserInfo.isMain
+ keyguardInteractor.isKeyguardVisible,
+ keyguardInteractor.isDreaming,
+ ) { isEncryptedOrLockdown, selectedUserInfo, isKeyguardVisible, isDreaming ->
+ !isEncryptedOrLockdown &&
+ selectedUserInfo.isMain &&
+ (isKeyguardVisible || isDreaming)
}
else flowOf(false)
}
@@ -154,8 +156,6 @@
it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Communal
}
- val isKeyguardVisible: Flow<Boolean> = keyguardInteractor.isKeyguardVisible
-
/** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
fun onSceneChanged(newScene: CommunalSceneKey) {
communalRepository.setDesiredScene(newScene)
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 4e5be9b..309c84e 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
@@ -27,12 +27,15 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** Encapsulates business-logic related to communal tutorial state. */
@@ -48,7 +51,7 @@
communalInteractor: CommunalInteractor,
) {
/** An observable for whether the tutorial is available. */
- val isTutorialAvailable: Flow<Boolean> =
+ val isTutorialAvailable: StateFlow<Boolean> =
combine(
communalInteractor.isCommunalAvailable,
keyguardInteractor.isKeyguardVisible,
@@ -58,7 +61,11 @@
isKeyguardVisible &&
tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
}
- .distinctUntilChanged()
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
/**
* A flow of the new tutorial state after transitioning. The new state will be calculated based
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt
index 4dfc371..0120b5c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt
@@ -18,7 +18,6 @@
package com.android.systemui.communal.ui.binder
import android.widget.TextView
-import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -32,16 +31,14 @@
fun bind(
view: TextView,
viewModel: CommunalTutorialIndicatorViewModel,
+ isPreviewMode: Boolean = false,
): DisposableHandle {
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
- viewModel.showIndicator.collect { isVisible ->
- updateView(
- view = view,
- isIndicatorVisible = isVisible,
- )
+ viewModel.showIndicator(isPreviewMode).collect { showIndicator ->
+ view.isVisible = showIndicator
}
}
@@ -51,18 +48,4 @@
return disposableHandle
}
-
- private fun updateView(
- isIndicatorVisible: Boolean,
- view: TextView,
- ) {
- if (!isIndicatorVisible) {
- view.isGone = true
- return
- }
-
- if (!view.isVisible) {
- view.isVisible = true
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
index 027cc96..2d9dd50 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
@@ -120,6 +120,7 @@
ConstraintSet.PARENT_ID,
ConstraintSet.BOTTOM
)
+ setVisibility(tutorialIndicatorId, View.GONE)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index acc7981..1e64d3f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -35,8 +35,6 @@
) {
val isCommunalAvailable: StateFlow<Boolean> = communalInteractor.isCommunalAvailable
- val isKeyguardVisible: Flow<Boolean> = communalInteractor.isKeyguardVisible
-
val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
/** Whether widgets are currently being re-ordered. */
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt
index 274e61a..63a4972 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt
@@ -20,17 +20,30 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
/** View model for communal tutorial indicator on keyguard */
class CommunalTutorialIndicatorViewModel
@Inject
constructor(
- communalTutorialInteractor: CommunalTutorialInteractor,
+ private val communalTutorialInteractor: CommunalTutorialInteractor,
bottomAreaInteractor: KeyguardBottomAreaInteractor,
) {
- /** An observable for whether the tutorial indicator view should be visible. */
- val showIndicator: Flow<Boolean> = communalTutorialInteractor.isTutorialAvailable
+ /**
+ * An observable for whether the tutorial indicator view should be visible.
+ *
+ * @param isPreviewMode Whether for preview keyguard mode in wallpaper settings.
+ */
+ fun showIndicator(isPreviewMode: Boolean): StateFlow<Boolean> {
+ return if (isPreviewMode) {
+ MutableStateFlow(false).asStateFlow()
+ } else {
+ communalTutorialInteractor.isTutorialAvailable
+ }
+ }
/** An observable for the alpha level for the tutorial indicator. */
val alpha: Flow<Float> = bottomAreaInteractor.alpha.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 641064b..947cb02 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -17,6 +17,7 @@
package com.android.systemui.compose
+import android.app.Dialog
import android.content.Context
import android.view.View
import android.view.WindowInsets
@@ -26,11 +27,14 @@
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
+import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
@@ -70,6 +74,12 @@
onEditDone: () -> Unit,
)
+ fun setVolumePanelActivityContent(
+ activity: ComponentActivity,
+ viewModel: VolumePanelViewModel,
+ onDismissAnimationFinished: () -> Unit,
+ )
+
/** Create a [View] to represent [viewModel] on screen. */
fun createFooterActionsView(
context: Context,
@@ -86,6 +96,12 @@
sceneByKey: Map<SceneKey, Scene>,
): View
+ /** Creates sticky key dialog presenting provided [viewModel] */
+ fun createStickyKeysDialog(
+ dialogFactory: SystemUIDialogFactory,
+ viewModel: StickyKeysIndicatorViewModel
+ ): Dialog
+
/** Create a [View] to represent [viewModel] on screen. */
fun createCommunalView(
context: Context,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index db0c3c6..0fd6887 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -18,10 +18,7 @@
import android.content.Context;
import android.hardware.Sensor;
-import android.os.Handler;
-import com.android.systemui.res.R;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.doze.DozeAuthRemover;
import com.android.systemui.doze.DozeBrightnessHostForwarder;
@@ -40,6 +37,7 @@
import com.android.systemui.doze.DozeTriggers;
import com.android.systemui.doze.DozeUi;
import com.android.systemui.doze.DozeWallpaperState;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -75,9 +73,8 @@
@Provides
@DozeScope
- static WakeLock providesDozeWakeLock(DelayedWakeLock.Builder delayedWakeLockBuilder,
- @Main Handler handler) {
- return delayedWakeLockBuilder.setHandler(handler).setTag("Doze").build();
+ static WakeLock providesDozeWakeLock(DelayedWakeLock.Factory delayedWakeLockFactory) {
+ return delayedWakeLockFactory.create("Doze");
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
new file mode 100644
index 0000000..c9b56a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -0,0 +1,87 @@
+/*
+ * 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.dreams.touch;
+
+import static com.android.systemui.dreams.touch.dagger.ShadeModule.COMMUNAL_GESTURE_INITIATION_WIDTH;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/** {@link DreamTouchHandler} responsible for handling touches to open communal hub. **/
+public class CommunalTouchHandler implements DreamTouchHandler {
+ private final int mInitiationWidth;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final Optional<CentralSurfaces> mCentralSurfaces;
+
+ @Inject
+ public CommunalTouchHandler(
+ Optional<CentralSurfaces> centralSurfaces,
+ NotificationShadeWindowController notificationShadeWindowController,
+ @Named(COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth) {
+ mInitiationWidth = initiationWidth;
+ mCentralSurfaces = centralSurfaces;
+ mNotificationShadeWindowController = notificationShadeWindowController;
+ }
+
+ @Override
+ public void onSessionStart(TouchSession session) {
+ mCentralSurfaces.ifPresent(surfaces -> handleSessionStart(surfaces, session));
+ }
+
+ @Override
+ public void getTouchInitiationRegion(Rect bounds, Region region) {
+ final Rect outBounds = new Rect(bounds);
+ outBounds.inset(outBounds.width() - mInitiationWidth, 0, 0, 0);
+ region.op(outBounds, Region.Op.UNION);
+ }
+
+ private void handleSessionStart(CentralSurfaces surfaces, TouchSession session) {
+ // Force the notification shade window open (otherwise the hub won't show while swiping).
+ mNotificationShadeWindowController.setForcePluginOpen(true, this);
+
+ session.registerInputListener(ev -> {
+ surfaces.handleDreamTouch((MotionEvent) ev);
+ if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
+ var unused = session.pop();
+ }
+ });
+
+ session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+ float distanceY) {
+ return true;
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ return true;
+ }
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
index 94fe4bd..0f08d37 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
@@ -18,11 +18,13 @@
import android.content.res.Resources;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.touch.CommunalTouchHandler;
import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.dreams.touch.ShadeTouchHandler;
+import com.android.systemui.res.R;
+import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
@@ -33,7 +35,7 @@
* Dependencies for swipe down to notification over dream.
*/
@Module
-public class ShadeModule {
+public abstract class ShadeModule {
/**
* The height, defined in pixels, of the gesture initiation region at the top of the screen for
* swiping down notifications.
@@ -41,15 +43,22 @@
public static final String NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT =
"notification_shade_gesture_initiation_height";
+ /** Width of swipe gesture edge to show communal hub. */
+ public static final String COMMUNAL_GESTURE_INITIATION_WIDTH =
+ "communal_gesture_initiation_width";
+
/**
* Provides {@link ShadeTouchHandler} to handle notification swipe down over dream.
*/
- @Provides
+ @Binds
@IntoSet
- public static DreamTouchHandler providesNotificationShadeTouchHandler(
- ShadeTouchHandler touchHandler) {
- return touchHandler;
- }
+ public abstract DreamTouchHandler providesNotificationShadeTouchHandler(
+ ShadeTouchHandler touchHandler);
+
+ /** Provides {@link CommunalTouchHandler}. */
+ @Binds
+ @IntoSet
+ public abstract DreamTouchHandler bindCommunalTouchHandler(CommunalTouchHandler touchHandler);
/**
* Provides the height of the gesture area for notification swipe down.
@@ -59,4 +68,13 @@
public static int providesNotificationShadeGestureRegionHeight(@Main Resources resources) {
return resources.getDimensionPixelSize(R.dimen.dream_overlay_status_bar_height);
}
+
+ /**
+ * Provides the width of the gesture area for swiping open communal hub.
+ */
+ @Provides
+ @Named(COMMUNAL_GESTURE_INITIATION_WIDTH)
+ public static int providesCommunalGestureInitiationWidth(@Main Resources resources) {
+ return resources.getDimensionPixelSize(R.dimen.communal_gesture_initiation_width);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 846736c..3f7c152 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -574,9 +574,6 @@
@JvmField
val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog")
- // TODO(b/289573946): Tracking Bug
- @JvmField val PRECOMPUTED_TEXT = releasedFlag("precomputed_text")
-
// TODO(b/302087895): Tracking Bug
@JvmField val CALL_LAYOUT_ASYNC_SET_DATA =
unreleasedFlag("call_layout_async_set_data", teamfood = true)
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
new file mode 100644
index 0000000..58fb6a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.haptics.slider
+
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import android.widget.SeekBar
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/**
+ * A plugin added to a manager of a [android.widget.SeekBar] that adds dynamic haptic feedback.
+ *
+ * A [SeekableSliderEventProducer] is used as the producer of slider events, a
+ * [SliderHapticFeedbackProvider] is used as the listener of slider states to play haptic feedback
+ * depending on the state, and a [SeekableSliderTracker] is used as the state machine handler that
+ * tracks and manipulates the slider state.
+ */
+class SeekableSliderHapticPlugin
+@JvmOverloads
+constructor(
+ vibratorHelper: VibratorHelper,
+ systemClock: SystemClock,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Application private val applicationScope: CoroutineScope,
+ sliderHapticFeedbackConfig: SliderHapticFeedbackConfig = SliderHapticFeedbackConfig(),
+ sliderTrackerConfig: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
+) {
+
+ private val velocityTracker = VelocityTracker.obtain()
+
+ private val sliderEventProducer = SeekableSliderEventProducer()
+
+ private val sliderHapticFeedbackProvider =
+ SliderHapticFeedbackProvider(
+ vibratorHelper,
+ velocityTracker,
+ sliderHapticFeedbackConfig,
+ systemClock,
+ )
+
+ private val sliderTracker =
+ SeekableSliderTracker(
+ sliderHapticFeedbackProvider,
+ sliderEventProducer,
+ mainDispatcher,
+ sliderTrackerConfig,
+ )
+
+ val isTracking: Boolean
+ get() = sliderTracker.isTracking
+
+ val trackerState: SliderState
+ get() = sliderTracker.currentState
+
+ /**
+ * A waiting [Job] for a timer that estimates the key-up event when a key-down event is
+ * received.
+ *
+ * This is useful for the cases where the slider is being operated by an external key, but the
+ * release of the key is not easily accessible (e.g., the volume keys)
+ */
+ private var keyUpJob: Job? = null
+
+ @VisibleForTesting
+ val isKeyUpTimerWaiting: Boolean
+ get() = keyUpJob != null && keyUpJob?.isActive == true
+
+ /**
+ * Start the plugin.
+ *
+ * This starts the tracking of slider states, events and triggering of haptic feedback.
+ */
+ fun start() {
+ if (!isTracking) {
+ sliderTracker.startTracking()
+ }
+ }
+
+ /**
+ * Stop the plugin
+ *
+ * This stops the tracking of slider states, events and triggers of haptic feedback.
+ */
+ fun stop() = sliderTracker.stopTracking()
+
+ /** React to a touch event */
+ fun onTouchEvent(event: MotionEvent?) {
+ when (event?.actionMasked) {
+ MotionEvent.ACTION_UP,
+ MotionEvent.ACTION_CANCEL -> velocityTracker.clear()
+ MotionEvent.ACTION_DOWN,
+ MotionEvent.ACTION_MOVE -> velocityTracker.addMovement(event)
+ }
+ }
+
+ /** onStartTrackingTouch event from the slider's [android.widget.SeekBar] */
+ fun onStartTrackingTouch(seekBar: SeekBar) {
+ if (isTracking) {
+ sliderEventProducer.onStartTrackingTouch(seekBar)
+ }
+ }
+
+ /** onProgressChanged event from the slider's [android.widget.SeekBar] */
+ fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+ if (isTracking) {
+ sliderEventProducer.onProgressChanged(seekBar, progress, fromUser)
+ }
+ }
+
+ /** onStopTrackingTouch event from the slider's [android.widget.SeekBar] */
+ fun onStopTrackingTouch(seekBar: SeekBar) {
+ if (isTracking) {
+ sliderEventProducer.onStopTrackingTouch(seekBar)
+ }
+ }
+
+ /** onArrowUp event recorded */
+ fun onArrowUp() {
+ if (isTracking) {
+ sliderEventProducer.onArrowUp()
+ }
+ }
+
+ /**
+ * An external key was pressed (e.g., a volume key).
+ *
+ * This event is used to estimate the key-up event based on by running a timer as a waiting
+ * coroutine in the [keyUpTimerScope]. A key-up event in a slider corresponds to an onArrowUp
+ * event. Therefore, [onArrowUp] must be called after the timeout.
+ */
+ fun onKeyDown() {
+ if (!isTracking) return
+
+ if (isKeyUpTimerWaiting) {
+ // Cancel the ongoing wait
+ keyUpJob?.cancel()
+ }
+ keyUpJob =
+ applicationScope.launch {
+ delay(KEY_UP_TIMEOUT)
+ onArrowUp()
+ }
+ }
+
+ companion object {
+ const val KEY_UP_TIMEOUT = 100L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
index d078688..26e83a3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
@@ -17,11 +17,14 @@
package com.android.systemui.keyboard
+import com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyboard.backlight.ui.KeyboardBacklightDialogCoordinator
+import com.android.systemui.keyboard.stickykeys.ui.StickyKeysIndicatorCoordinator
+import dagger.Lazy
import javax.inject.Inject
/** A [CoreStartable] that launches components interested in physical keyboard interaction. */
@@ -29,12 +32,16 @@
class PhysicalKeyboardCoreStartable
@Inject
constructor(
- private val keyboardBacklightDialogCoordinator: KeyboardBacklightDialogCoordinator,
+ private val keyboardBacklightDialogCoordinator: Lazy<KeyboardBacklightDialogCoordinator>,
+ private val stickyKeysIndicatorCoordinator: Lazy<StickyKeysIndicatorCoordinator>,
private val featureFlags: FeatureFlags,
) : CoreStartable {
override fun start() {
if (featureFlags.isEnabled(Flags.KEYBOARD_BACKLIGHT_INDICATOR)) {
- keyboardBacklightDialogCoordinator.startListening()
+ keyboardBacklightDialogCoordinator.get().startListening()
+ }
+ if (keyboardA11yStickyKeysFlag()) {
+ stickyKeysIndicatorCoordinator.get().startListening()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
index 37034f6..4ed8120 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
@@ -26,11 +26,20 @@
private const val TAG = "stickyKeys"
class StickyKeysLogger @Inject constructor(@KeyboardLog private val buffer: LogBuffer) {
- fun logNewStickyKeysReceived(linkedHashMap: Map<ModifierKey, Locked>) {
+ fun logNewStickyKeysReceived(stickyKeys: Map<ModifierKey, Locked>) {
buffer.log(
TAG,
LogLevel.VERBOSE,
- { str1 = linkedHashMap.toString() },
+ { str1 = stickyKeys.toString() },
+ { "new sticky keys state received: $str1" }
+ )
+ }
+
+ fun logNewUiState(stickyKeys: Map<ModifierKey, Locked>) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { str1 = stickyKeys.toString() },
{ "new sticky keys state received: $str1" }
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
new file mode 100644
index 0000000..b68551b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.keyboard.stickykeys.ui
+
+import android.app.Dialog
+import android.util.Log
+import android.view.Gravity
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.Window
+import android.view.WindowManager
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
+import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@SysUISingleton
+class StickyKeysIndicatorCoordinator
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val dialogFactory: SystemUIDialogFactory,
+ private val viewModel: StickyKeysIndicatorViewModel,
+ private val stickyKeysLogger: StickyKeysLogger,
+) {
+
+ private var dialog: Dialog? = null
+
+ fun startListening() {
+ // this check needs to be moved to PhysicalKeyboardCoreStartable
+ if (!ComposeFacade.isComposeAvailable()) {
+ Log.e("StickyKeysIndicatorCoordinator", "Compose is required for this UI")
+ return
+ }
+ applicationScope.launch {
+ viewModel.indicatorContent.collect { stickyKeys ->
+ stickyKeysLogger.logNewUiState(stickyKeys)
+ if (stickyKeys.isEmpty()) {
+ dialog?.dismiss()
+ dialog = null
+ } else if (dialog == null) {
+ dialog = ComposeFacade.createStickyKeysDialog(dialogFactory, viewModel).apply {
+ setCanceledOnTouchOutside(false)
+ window?.setAttributes()
+ show()
+ }
+ }
+ }
+ }
+ }
+
+ private fun Window.setAttributes() {
+ setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+ addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
+ clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ setGravity(Gravity.TOP or Gravity.END)
+ attributes = WindowManager.LayoutParams().apply {
+ copyFrom(attributes)
+ width = WRAP_CONTENT
+ title = "StickyKeysIndicator"
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 5cebd96..50caf17 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2507,8 +2507,18 @@
String message = "";
switch (msg.what) {
case SHOW:
- message = "SHOW";
- handleShow((Bundle) msg.obj);
+ // There is a potential race condition when SysUI starts up. CentralSurfaces
+ // must invoke #registerCentralSurfaces on this class before any messages can be
+ // processed. If this happens, repost the message with a small delay and try
+ // again.
+ if (mCentralSurfaces == null) {
+ message = "DELAYING SHOW";
+ Message newMsg = mHandler.obtainMessage(SHOW, (Bundle) msg.obj);
+ mHandler.sendMessageDelayed(newMsg, 100);
+ } else {
+ message = "SHOW";
+ handleShow((Bundle) msg.obj);
+ }
break;
case HIDE:
message = "HIDE";
@@ -2595,8 +2605,18 @@
Trace.endSection();
break;
case SYSTEM_READY:
- message = "SYSTEM_READY";
- handleSystemReady();
+ // There is a potential race condition when SysUI starts up. CentralSurfaces
+ // must invoke #registerCentralSurfaces on this class before any messages can be
+ // processed. If this happens, repost the message with a small delay and try
+ // again.
+ if (mCentralSurfaces == null) {
+ message = "DELAYING SYSTEM_READY";
+ Message newMsg = mHandler.obtainMessage(SYSTEM_READY);
+ mHandler.sendMessageDelayed(newMsg, 100);
+ } else {
+ message = "SYSTEM_READY";
+ handleSystemReady();
+ }
break;
}
Log.d(TAG, "KeyguardViewMediator queue processing message: " + message);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 7b1466c..a97c152 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -17,14 +17,14 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.util.kotlin.Utils.Companion.toQuad
-import com.android.systemui.util.kotlin.Utils.Companion.toQuint
+import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.sample
import com.android.wm.shell.animation.Interpolators
import javax.inject.Inject
@@ -32,7 +32,6 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -46,6 +45,7 @@
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
+ private val communalInteractor: CommunalInteractor,
private val powerInteractor: PowerInteractor,
) :
TransitionInteractor(
@@ -57,12 +57,12 @@
override fun start() {
listenForAlternateBouncerToGone()
- listenForAlternateBouncerToLockscreenAodOrDozing()
+ listenForAlternateBouncerToLockscreenHubAodOrDozing()
listenForAlternateBouncerToPrimaryBouncer()
listenForTransitionToCamera(scope, keyguardInteractor)
}
- private fun listenForAlternateBouncerToLockscreenAodOrDozing() {
+ private fun listenForAlternateBouncerToLockscreenHubAodOrDozing() {
scope.launch {
keyguardInteractor.alternateBouncerShowing
// Add a slight delay, as alternateBouncer and primaryBouncer showing event changes
@@ -70,14 +70,11 @@
// happening prematurely.
.onEach { delay(50) }
.sample(
- combine(
- keyguardInteractor.primaryBouncerShowing,
- startedKeyguardTransitionStep,
- powerInteractor.isAwake,
- keyguardInteractor.isAodAvailable,
- ::toQuad
- ),
- ::toQuint
+ keyguardInteractor.primaryBouncerShowing,
+ startedKeyguardTransitionStep,
+ powerInteractor.isAwake,
+ keyguardInteractor.isAodAvailable,
+ communalInteractor.isIdleOnCommunal
)
.collect {
(
@@ -85,7 +82,8 @@
isPrimaryBouncerShowing,
lastStartedTransitionStep,
isAwake,
- isAodAvailable) ->
+ isAodAvailable,
+ isIdleOnCommunal) ->
if (
!isAlternateBouncerShowing &&
!isPrimaryBouncerShowing &&
@@ -99,7 +97,11 @@
KeyguardState.DOZING
}
} else {
- KeyguardState.LOCKSCREEN
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
}
startTransitionTo(to)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 4df5d50..9d38be9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -18,6 +18,7 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.Flags
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.dagger.SysUISingleton
@@ -39,12 +40,13 @@
@Inject
constructor(
@Background private val scope: CoroutineScope,
+ @Main mainDispatcher: CoroutineDispatcher,
+ @Background bgDispatcher: CoroutineDispatcher,
private val glanceableHubTransitions: GlanceableHubTransitions,
+ private val keyguardInteractor: KeyguardInteractor,
override val transitionRepository: KeyguardTransitionRepository,
transitionInteractor: KeyguardTransitionInteractor,
private val powerInteractor: PowerInteractor,
- @Main mainDispatcher: CoroutineDispatcher,
- @Background bgDispatcher: CoroutineDispatcher,
) :
TransitionInteractor(
fromState = KeyguardState.GLANCEABLE_HUB,
@@ -58,6 +60,10 @@
}
listenForHubToLockscreen()
listenForHubToDozing()
+ listenForHubToPrimaryBouncer()
+ listenForHubToAlternateBouncer()
+ listenForHubToOccluded()
+ listenForHubToGone()
}
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
@@ -75,10 +81,42 @@
glanceableHubTransitions.listenForLockscreenAndHubTransition(
transitionName = "listenForHubToLockscreen",
transitionOwnerName = TAG,
- toScene = CommunalSceneKey.Blank
+ toScene = CommunalSceneKey.Blank,
)
}
+ private fun listenForHubToPrimaryBouncer() {
+ scope.launch("$TAG#listenForHubToPrimaryBouncer") {
+ keyguardInteractor.primaryBouncerShowing
+ .sample(startedKeyguardTransitionStep, ::Pair)
+ .collect { pair ->
+ val (isBouncerShowing, lastStartedTransitionStep) = pair
+ if (
+ isBouncerShowing &&
+ lastStartedTransitionStep.to == KeyguardState.GLANCEABLE_HUB
+ ) {
+ startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+ }
+ }
+ }
+ }
+
+ private fun listenForHubToAlternateBouncer() {
+ scope.launch("$TAG#listenForHubToAlternateBouncer") {
+ keyguardInteractor.alternateBouncerShowing
+ .sample(startedKeyguardTransitionStep, ::Pair)
+ .collect { pair ->
+ val (isAlternateBouncerShowing, lastStartedTransitionStep) = pair
+ if (
+ isAlternateBouncerShowing &&
+ lastStartedTransitionStep.to == KeyguardState.GLANCEABLE_HUB
+ ) {
+ startTransitionTo(KeyguardState.ALTERNATE_BOUNCER)
+ }
+ }
+ }
+ }
+
private fun listenForHubToDozing() {
scope.launch {
powerInteractor.isAsleep.sample(startedKeyguardTransitionStep, ::Pair).collect {
@@ -93,6 +131,29 @@
}
}
+ private fun listenForHubToOccluded() {
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
+ (isOccluded, keyguardState) ->
+ if (isOccluded && keyguardState == fromState) {
+ startTransitionTo(KeyguardState.OCCLUDED)
+ }
+ }
+ }
+ }
+
+ private fun listenForHubToGone() {
+ scope.launch {
+ keyguardInteractor.isKeyguardGoingAway
+ .sample(startedKeyguardTransitionStep, ::Pair)
+ .collect { (isKeyguardGoingAway, lastStartedStep) ->
+ if (isKeyguardGoingAway && lastStartedStep.to == fromState) {
+ startTransitionTo(KeyguardState.GONE)
+ }
+ }
+ }
+ }
+
companion object {
const val TAG = "FromGlanceableHubTransitionInteractor"
val DEFAULT_DURATION = 400.milliseconds
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 742790e..7477624 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -18,6 +18,7 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -25,6 +26,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -45,6 +47,7 @@
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val powerInteractor: PowerInteractor,
+ private val communalInteractor: CommunalInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.GONE,
@@ -56,18 +59,27 @@
override fun start() {
listenForGoneToAodOrDozing()
listenForGoneToDreaming()
- listenForGoneToLockscreen()
+ listenForGoneToLockscreenOrHub()
listenForGoneToDreamingLockscreenHosted()
}
// Primarily for when the user chooses to lock down the device
- private fun listenForGoneToLockscreen() {
+ private fun listenForGoneToLockscreenOrHub() {
scope.launch {
keyguardInteractor.isKeyguardShowing
- .sample(startedKeyguardTransitionStep, ::Pair)
- .collect { (isKeyguardShowing, lastStartedStep) ->
+ .sample(
+ startedKeyguardTransitionStep,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect { (isKeyguardShowing, lastStartedStep, isIdleOnCommunal) ->
if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) {
- startTransitionTo(KeyguardState.LOCKSCREEN)
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 40061f4..efb604d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -18,12 +18,14 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -44,6 +46,7 @@
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val powerInteractor: PowerInteractor,
+ private val communalInteractor: CommunalInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.OCCLUDED,
@@ -53,7 +56,7 @@
) {
override fun start() {
- listenForOccludedToLockscreen()
+ listenForOccludedToLockscreenOrHub()
listenForOccludedToDreaming()
listenForOccludedToAodOrDozing()
listenForOccludedToGone()
@@ -86,18 +89,15 @@
}
}
- private fun listenForOccludedToLockscreen() {
+ private fun listenForOccludedToLockscreenOrHub() {
scope.launch {
keyguardInteractor.isKeyguardOccluded
.sample(
- combine(
- keyguardInteractor.isKeyguardShowing,
- startedKeyguardTransitionStep,
- ::Pair
- ),
- ::toTriple
+ keyguardInteractor.isKeyguardShowing,
+ startedKeyguardTransitionStep,
+ communalInteractor.isIdleOnCommunal,
)
- .collect { (isOccluded, isShowing, lastStartedKeyguardState) ->
+ .collect { (isOccluded, isShowing, lastStartedKeyguardState, isIdleOnCommunal) ->
// Occlusion signals come from the framework, and should interrupt any
// existing transition
if (
@@ -105,7 +105,13 @@
isShowing &&
lastStartedKeyguardState.to == KeyguardState.OCCLUDED
) {
- startTransitionTo(KeyguardState.LOCKSCREEN)
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index c62055f..33b6373 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -18,6 +18,7 @@
import android.animation.ValueAnimator
import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -29,8 +30,8 @@
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
-import com.android.systemui.util.kotlin.Utils.Companion.toQuint
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import com.android.wm.shell.animation.Interpolators
@@ -54,6 +55,7 @@
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
+ private val communalInteractor: CommunalInteractor,
private val flags: FeatureFlags,
private val keyguardSecurityModel: KeyguardSecurityModel,
private val selectedUserInteractor: SelectedUserInteractor,
@@ -69,7 +71,7 @@
override fun start() {
listenForPrimaryBouncerToGone()
listenForPrimaryBouncerToAodOrDozing()
- listenForPrimaryBouncerToLockscreenOrOccluded()
+ listenForPrimaryBouncerToLockscreenHubOrOccluded()
listenForPrimaryBouncerToDreamingLockscreenHosted()
listenForTransitionToCamera(scope, keyguardInteractor)
}
@@ -125,18 +127,15 @@
scope.launch { startTransitionTo(KeyguardState.GONE) }
}
- private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
+ private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
.sample(
- combine(
- powerInteractor.isAwake,
- startedKeyguardTransitionStep,
- keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.isActiveDreamLockscreenHosted,
- ::toQuad
- ),
- ::toQuint
+ powerInteractor.isAwake,
+ startedKeyguardTransitionStep,
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ communalInteractor.isIdleOnCommunal
)
.collect {
(
@@ -144,16 +143,23 @@
isAwake,
lastStartedTransitionStep,
occluded,
- isActiveDreamLockscreenHosted) ->
+ isActiveDreamLockscreenHosted,
+ isIdleOnCommunal) ->
if (
!isBouncerShowing &&
lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
isAwake &&
!isActiveDreamLockscreenHosted
) {
- startTransitionTo(
- if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
- )
+ val toState =
+ if (occluded) {
+ KeyguardState.OCCLUDED
+ } else if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(toState)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
index cb50839..ca66153 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -23,6 +23,7 @@
import com.android.systemui.communal.domain.interactor.CommunalTransitionProgress
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -30,12 +31,15 @@
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.flowOn
class GlanceableHubTransitions
@Inject
constructor(
@Application private val scope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
private val transitionInteractor: KeyguardTransitionInteractor,
private val transitionRepository: KeyguardTransitionRepository,
private val communalInteractor: CommunalInteractor,
@@ -66,7 +70,10 @@
scope.launch("$transitionOwnerName#$transitionName") {
communalInteractor
.transitionProgressToScene(toScene)
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .sample(
+ transitionInteractor.startedKeyguardTransitionStep.flowOn(bgDispatcher),
+ ::Pair
+ )
.collect { pair ->
val (transitionProgress, lastStartedStep) = pair
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 91747e0..36bd905 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -98,7 +98,7 @@
val dozeAmount: Flow<Float> = repository.linearDozeAmount
/** Whether the system is in doze mode. */
- val isDozing: Flow<Boolean> = repository.isDozing
+ val isDozing: StateFlow<Boolean> = repository.isDozing
/** Receive an event for doze time tick */
val dozeTimeTick: Flow<Long> = repository.dozeTimeTick
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 3dd3e07..8d6493f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -24,6 +24,7 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -53,7 +54,8 @@
}
// Apply transition.
- if (prevBluePrint != null && prevBluePrint != blueprint) {
+ if (!keyguardBottomAreaRefactor() && prevBluePrint != null &&
+ prevBluePrint != blueprint) {
TransitionManager.beginDelayedTransition(
constraintLayout,
BaseBlueprintTransition()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 400b8bf..3c3ebdf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -89,6 +89,12 @@
}
}
}
+ launch {
+ if (!migrateClocksToBlueprint()) return@launch
+ viewModel.isAodIconsVisible.collect {
+ applyConstraints(clockSection, keyguardRootView, true)
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index eb3afb7..841bad4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -39,6 +39,7 @@
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.FrameLayout
+import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isInvisible
import com.android.keyguard.ClockEventController
@@ -48,6 +49,8 @@
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder
+import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -133,6 +136,7 @@
private val screenOffAnimationController: ScreenOffAnimationController,
private val shadeInteractor: ShadeInteractor,
private val secureSettings: SecureSettings,
+ private val communalTutorialViewModel: CommunalTutorialIndicatorViewModel,
) {
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
@@ -408,6 +412,8 @@
smartSpaceView?.let {
KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
}
+
+ setupCommunalTutorialIndicator(keyguardRootView)
}
)
}
@@ -601,6 +607,17 @@
}
}
+ private fun setupCommunalTutorialIndicator(keyguardRootView: ConstraintLayout) {
+ keyguardRootView.findViewById<TextView>(R.id.communal_tutorial_indicator)?.let {
+ indicatorView ->
+ CommunalTutorialIndicatorViewBinder.bind(
+ indicatorView,
+ communalTutorialViewModel,
+ isPreviewMode = true,
+ )
+ }
+ }
+
private suspend fun fetchThemeStyleFromSetting(): Style {
val overlayPackageJson =
withContext(backgroundDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index ed7abff..bc6c7cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -57,7 +57,6 @@
private var nicBindingDisposable: DisposableHandle? = null
private val nicId = R.id.aod_notification_icon_container
private lateinit var nic: NotificationIconContainer
- private val smartSpaceBarrier = View.generateViewId()
override fun addViews(constraintLayout: ConstraintLayout) {
if (!KeyguardShadeMigrationNssl.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index b344d3b..a1b3f27 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.view.View
+import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -35,6 +36,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceLayout
import com.android.systemui.res.R
@@ -61,6 +63,7 @@
protected val keyguardClockViewModel: KeyguardClockViewModel,
private val context: Context,
private val splitShadeStateController: SplitShadeStateController,
+ val smartspaceViewModel: KeyguardSmartspaceViewModel,
val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
) : KeyguardSection() {
override fun addViews(constraintLayout: ConstraintLayout) {}
@@ -117,6 +120,35 @@
private fun getLargeClockFace(clock: ClockController): ClockFaceLayout = clock.largeClock.layout
private fun getSmallClockFace(clock: ClockController): ClockFaceLayout = clock.smallClock.layout
+
+ fun constrainWeatherClockDateIconsBarrier(constraints: ConstraintSet) {
+ constraints.apply {
+ if (keyguardClockViewModel.isAodIconsVisible.value) {
+ createBarrier(
+ R.id.weather_clock_date_and_icons_barrier_bottom,
+ Barrier.BOTTOM,
+ 0,
+ *intArrayOf(sharedR.id.bc_smartspace_view, R.id.aod_notification_icon_container)
+ )
+ } else {
+ if (smartspaceViewModel.bcSmartspaceVisibility.value == VISIBLE) {
+ createBarrier(
+ R.id.weather_clock_date_and_icons_barrier_bottom,
+ Barrier.BOTTOM,
+ 0,
+ (sharedR.id.bc_smartspace_view)
+ )
+ } else {
+ createBarrier(
+ R.id.weather_clock_date_and_icons_barrier_bottom,
+ Barrier.BOTTOM,
+ getDimen(ENHANCED_SMARTSPACE_HEIGHT),
+ (R.id.lockscreen_clock_view)
+ )
+ }
+ }
+ }
+ }
open fun applyDefaultConstraints(constraints: ConstraintSet) {
val guideline =
if (keyguardClockViewModel.clockShouldBeCentered.value) PARENT_ID
@@ -173,6 +205,8 @@
}
connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
}
+
+ constrainWeatherClockDateIconsBarrier(constraints)
}
private fun getDimen(name: String): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index f37d9f8..6763e0a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -27,6 +27,7 @@
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.Utils
import javax.inject.Inject
@@ -44,6 +45,7 @@
private val keyguardClockInteractor: KeyguardClockInteractor,
@Application private val applicationScope: CoroutineScope,
private val splitShadeStateController: SplitShadeStateController,
+ notifsKeyguardInteractor: NotificationsKeyguardInteractor,
) {
var burnInLayer: Layer? = null
val useLargeClock: Boolean
@@ -91,6 +93,13 @@
initialValue = false
)
+ val isAodIconsVisible: StateFlow<Boolean> =
+ notifsKeyguardInteractor.areNotificationsFullyHidden.stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false
+ )
+
// Needs to use a non application context to get display cutout.
fun getSmallClockTopMargin(context: Context) =
if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index 693e3b7..ca9c857 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -20,7 +20,7 @@
import android.content.Context
import android.graphics.Point
import androidx.annotation.VisibleForTesting
-import androidx.core.animation.doOnEnd
+import androidx.core.animation.addListener
import com.android.systemui.Flags
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
@@ -30,15 +30,18 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.DozeServiceHost
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -46,13 +49,14 @@
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
+@ExperimentalCoroutinesApi
@SysUISingleton
class SideFpsProgressBarViewModel
@Inject
@@ -63,9 +67,11 @@
// todo (b/317432075) Injecting DozeServiceHost directly instead of using it through
// DozeInteractor as DozeServiceHost already depends on DozeInteractor.
private val dozeServiceHost: DozeServiceHost,
+ private val keyguardInteractor: KeyguardInteractor,
displayStateInteractor: DisplayStateInteractor,
@Main private val mainDispatcher: CoroutineDispatcher,
@Application private val applicationScope: CoroutineScope,
+ private val powerInteractor: PowerInteractor,
) {
private val _progress = MutableStateFlow(0.0f)
private val _visible = MutableStateFlow(false)
@@ -176,48 +182,54 @@
return@collectLatest
}
animatorJob =
- combine(
- sfpsSensorInteractor.authenticationDuration,
- fpAuthRepository.authenticationStatus,
- ::Pair
- )
- .onEach { (authDuration, authStatus) ->
- when (authStatus) {
- is AcquiredFingerprintAuthenticationStatus -> {
- if (authStatus.fingerprintCaptureStarted) {
- _visible.value = true
- dozeServiceHost.fireSideFpsAcquisitionStarted()
- _animator?.cancel()
- _animator =
- ValueAnimator.ofFloat(0.0f, 1.0f)
- .setDuration(authDuration)
- .apply {
- addUpdateListener {
- _progress.value = it.animatedValue as Float
- }
- addListener(
- doOnEnd {
- if (_progress.value == 0.0f) {
- _visible.value = false
- }
+ sfpsSensorInteractor.authenticationDuration
+ .flatMapLatest { authDuration ->
+ _animator?.cancel()
+ fpAuthRepository.authenticationStatus.map { authStatus ->
+ when (authStatus) {
+ is AcquiredFingerprintAuthenticationStatus -> {
+ if (authStatus.fingerprintCaptureStarted) {
+ if (keyguardInteractor.isDozing.value) {
+ dozeServiceHost.fireSideFpsAcquisitionStarted()
+ } else {
+ powerInteractor
+ .wakeUpForSideFingerprintAcquisition()
+ }
+ _animator?.cancel()
+ _animator =
+ ValueAnimator.ofFloat(0.0f, 1.0f)
+ .setDuration(authDuration)
+ .apply {
+ addUpdateListener {
+ _progress.value =
+ it.animatedValue as Float
}
- )
- }
- _animator?.start()
- } else if (authStatus.fingerprintCaptureCompleted) {
- onFingerprintCaptureCompleted()
- } else {
- // Abandoned FP Auth attempt
- _animator?.reverse()
+ addListener(
+ onEnd = {
+ if (_progress.value == 0.0f) {
+ _visible.value = false
+ }
+ },
+ onStart = { _visible.value = true },
+ onCancel = { _visible.value = false }
+ )
+ }
+ _animator?.start()
+ } else if (authStatus.fingerprintCaptureCompleted) {
+ onFingerprintCaptureCompleted()
+ } else {
+ // Abandoned FP Auth attempt
+ _animator?.reverse()
+ }
}
+ is ErrorFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ is FailFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ is SuccessFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ else -> Unit
}
- is ErrorFingerprintAuthenticationStatus ->
- onFingerprintCaptureCompleted()
- is FailFingerprintAuthenticationStatus ->
- onFingerprintCaptureCompleted()
- is SuccessFingerprintAuthenticationStatus ->
- onFingerprintCaptureCompleted()
- else -> Unit
}
}
.flowOn(mainDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
index 171656a..ce64ac1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
@@ -117,4 +117,13 @@
{ "SideFpsSensor auth duration changed: $long1" }
)
}
+
+ fun restToUnlockSettingEnabledChanged(enabled: Boolean) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { bool1 = enabled },
+ { "restToUnlockSettingEnabled: $bool1" }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index d9e3e55..3f8834a 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -119,6 +119,11 @@
}
}
+ /** Wakes up the device for the Side FPS acquisition event. */
+ fun wakeUpForSideFingerprintAcquisition() {
+ repository.wakeUp("SFPS_FP_ACQUISITION_STARTED", PowerManager.WAKE_REASON_BIOMETRIC)
+ }
+
/**
* Called from [KeyguardService] to inform us that the device has started waking up. This is the
* canonical source of wakefulness information for System UI. This method should not be called
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index a3b9254..a2dfc01 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -27,8 +27,11 @@
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
+
import com.android.systemui.Dumpable;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.res.R;
@@ -53,6 +56,7 @@
private QuickStatusBarHeader mHeader;
private float mQsExpansion;
private QSCustomizer mQSCustomizer;
+ private QSPanel mQSPanel;
private NonInterceptingScrollView mQSPanelContainer;
private int mHorizontalMargins;
@@ -72,6 +76,7 @@
protected void onFinishInflate() {
super.onFinishInflate();
mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view);
+ mQSPanel = findViewById(R.id.quick_settings_panel);
mHeader = findViewById(R.id.header);
mQSCustomizer = findViewById(R.id.qs_customize);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
@@ -79,6 +84,13 @@
void setSceneContainerEnabled(boolean enabled) {
mSceneContainerEnabled = enabled;
+ if (enabled) {
+ mQSPanelContainer.removeAllViews();
+ removeView(mQSPanelContainer);
+ LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ addView(mQSPanel, 0, lp);
+ }
}
@Override
@@ -97,20 +109,26 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// QSPanel will show as many rows as it can (up to TileLayout.MAX_ROWS) such that the
// bottom and footer are inside the screen.
- MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams();
-
int availableHeight = View.MeasureSpec.getSize(heightMeasureSpec);
- int maxQs = availableHeight - layoutParams.topMargin - layoutParams.bottomMargin
- - getPaddingBottom();
- int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin
- + layoutParams.rightMargin;
- final int qsPanelWidthSpec = getChildMeasureSpec(widthMeasureSpec, padding,
- layoutParams.width);
- mQSPanelContainer.measure(qsPanelWidthSpec,
- MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST));
- int width = mQSPanelContainer.getMeasuredWidth() + padding;
- super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY));
+
+ if (!mSceneContainerEnabled) {
+ MarginLayoutParams layoutParams =
+ (MarginLayoutParams) mQSPanelContainer.getLayoutParams();
+ int maxQs = availableHeight - layoutParams.topMargin - layoutParams.bottomMargin
+ - getPaddingBottom();
+ int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin
+ + layoutParams.rightMargin;
+ final int qsPanelWidthSpec = getChildMeasureSpec(widthMeasureSpec, padding,
+ layoutParams.width);
+ mQSPanelContainer.measure(qsPanelWidthSpec,
+ MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST));
+ int width = mQSPanelContainer.getMeasuredWidth() + padding;
+ super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY));
+ } else {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
// QSCustomizer will always be the height of the screen, but do this after
// other measuring to avoid changing the height of the QS.
mQSCustomizer.measure(widthMeasureSpec,
@@ -130,12 +148,15 @@
@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
- // Do not measure QSPanel again when doing super.onMeasure.
- // This prevents the pages in PagedTileLayout to be remeasured with a different (incorrect)
- // size to the one used for determining the number of rows and then the number of pages.
- if (child != mQSPanelContainer) {
- super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
- parentHeightMeasureSpec, heightUsed);
+ if (!mSceneContainerEnabled) {
+ // Do not measure QSPanel again when doing super.onMeasure.
+ // This prevents the pages in PagedTileLayout to be remeasured with a different
+ // (incorrect) size to the one used for determining the number of rows and then the
+ // number of pages.
+ if (child != mQSPanelContainer) {
+ super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
+ parentHeightMeasureSpec, heightUsed);
+ }
}
}
@@ -151,6 +172,7 @@
updateClippingPath();
}
+ @Nullable
public NonInterceptingScrollView getQSPanelContainer() {
return mQSPanelContainer;
}
@@ -172,11 +194,19 @@
.getDimensionPixelSize(
R.dimen.large_screen_shade_header_height);
}
- mQSPanelContainer.setPaddingRelative(
- mQSPanelContainer.getPaddingStart(),
- mSceneContainerEnabled ? 0 : topPadding,
- mQSPanelContainer.getPaddingEnd(),
- mQSPanelContainer.getPaddingBottom());
+ if (mQSPanelContainer != null) {
+ mQSPanelContainer.setPaddingRelative(
+ mQSPanelContainer.getPaddingStart(),
+ mSceneContainerEnabled ? 0 : topPadding,
+ mQSPanelContainer.getPaddingEnd(),
+ mQSPanelContainer.getPaddingBottom());
+ } else {
+ mQSPanel.setPaddingRelative(
+ mQSPanel.getPaddingStart(),
+ mSceneContainerEnabled ? 0 : topPadding,
+ mQSPanel.getPaddingEnd(),
+ mQSPanel.getPaddingBottom());
+ }
int horizontalMargins = getResources().getDimensionPixelSize(R.dimen.qs_horizontal_margin);
int horizontalPadding = getResources().getDimensionPixelSize(
@@ -220,7 +250,9 @@
public void setExpansion(float expansion) {
mQsExpansion = expansion;
- mQSPanelContainer.setScrollingEnabled(expansion > 0f);
+ if (mQSPanelContainer != null) {
+ mQSPanelContainer.setScrollingEnabled(expansion > 0f);
+ }
updateExpansion();
}
@@ -239,7 +271,7 @@
lp.rightMargin = mHorizontalMargins;
lp.leftMargin = mHorizontalMargins;
}
- if (view == mQSPanelContainer) {
+ if (view == mQSPanelContainer || view == mQSPanel) {
// QS panel lays out some of its content full width
qsPanelController.setContentMargins(mContentHorizontalPadding,
mContentHorizontalPadding);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index 7b001c7..ffbc560 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -81,6 +81,9 @@
public void onInit() {
mQuickStatusBarHeaderController.init();
mView.setSceneContainerEnabled(mSceneContainerEnabled);
+ if (mSceneContainerEnabled && mQsPanelController != null) {
+ mQSPanelContainer.setOnTouchListener(null);
+ }
}
public void setListening(boolean listening) {
@@ -91,13 +94,17 @@
protected void onViewAttached() {
mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
mConfigurationController.addCallback(mConfigurationListener);
- mQSPanelContainer.setOnTouchListener(mContainerTouchHandler);
+ if (!mSceneContainerEnabled && mQSPanelContainer != null) {
+ mQSPanelContainer.setOnTouchListener(mContainerTouchHandler);
+ }
}
@Override
protected void onViewDetached() {
mConfigurationController.removeCallback(mConfigurationListener);
- mQSPanelContainer.setOnTouchListener(null);
+ if (mQSPanelContainer != null) {
+ mQSPanelContainer.setOnTouchListener(null);
+ }
}
public QSContainerImpl getView() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 7f91fd2..290821e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -61,6 +61,7 @@
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
@@ -171,8 +172,11 @@
private CommandQueue mCommandQueue;
private View mRootView;
+ @Nullable
private View mFooterActionsView;
+ private final SceneContainerFlags mSceneContainerFlags;
+
@Inject
public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue,
@@ -185,7 +189,8 @@
FooterActionsViewModel.Factory footerActionsViewModelFactory,
FooterActionsViewBinder footerActionsViewBinder,
LargeScreenShadeInterpolator largeScreenShadeInterpolator,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ SceneContainerFlags sceneContainerFlags) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
mQsMediaHost = qsMediaHost;
mQqsMediaHost = qqsMediaHost;
@@ -201,6 +206,7 @@
mFooterActionsViewModelFactory = footerActionsViewModelFactory;
mFooterActionsViewBinder = footerActionsViewBinder;
mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
+ mSceneContainerFlags = sceneContainerFlags;
}
/**
@@ -216,10 +222,17 @@
mQSPanelController.init();
mQuickQSPanelController.init();
- mQSFooterActionsViewModel = mFooterActionsViewModelFactory
- .create(mListeningAndVisibilityLifecycleOwner);
- bindFooterActionsView(mRootView);
- mFooterActionsController.init();
+ if (!mSceneContainerFlags.isEnabled()) {
+ mQSFooterActionsViewModel = mFooterActionsViewModelFactory
+ .create(mListeningAndVisibilityLifecycleOwner);
+ bindFooterActionsView(mRootView);
+ mFooterActionsController.init();
+ } else {
+ View footerView = mRootView.findViewById(R.id.qs_footer_actions);
+ if (footerView != null) {
+ ((ViewGroup) footerView.getParent()).removeView(footerView);
+ }
+ }
mQSPanelScrollView = mRootView.findViewById(R.id.expanded_qs_scroll_view);
mQSPanelScrollView.addOnLayoutChangeListener(
@@ -234,6 +247,7 @@
mScrollListener.onQsPanelScrollChanged(scrollY);
}
});
+ mQSPanelScrollView.setScrollingEnabled(!mSceneContainerFlags.isEnabled());
mHeader = mRootView.findViewById(R.id.header);
mFooter = qsComponent.getQSFooter();
@@ -481,7 +495,9 @@
boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing
|| mHeaderAnimating || mShowCollapsedOnKeyguard);
mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
- mFooterActionsView.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
+ if (mFooterActionsView != null) {
+ mFooterActionsView.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
+ }
mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
|| (mQsExpanded && !mStackScrollerOverscrolling));
mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE);
@@ -622,8 +638,13 @@
@Override
public int getHeightDiff() {
- return mQSPanelScrollView.getBottom() - mHeader.getBottom()
- + mHeader.getPaddingBottom();
+ if (mSceneContainerFlags.isEnabled()) {
+ return mQSPanelController.getViewBottom() - mHeader.getBottom()
+ + mHeader.getPaddingBottom();
+ } else {
+ return mQSPanelScrollView.getBottom() - mHeader.getBottom()
+ + mHeader.getPaddingBottom();
+ }
}
@Override
@@ -678,25 +699,29 @@
mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
float footerActionsExpansion =
onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion;
- mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion,
- mInSplitShade);
+ if (mQSFooterActionsViewModel != null) {
+ mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion,
+ mInSplitShade);
+ }
mQSPanelController.setRevealExpansion(expansion);
mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
- float qsScrollViewTranslation =
- onKeyguard && !mShowCollapsedOnKeyguard ? panelTranslationY : 0;
- mQSPanelScrollView.setTranslationY(qsScrollViewTranslation);
+ if (!mSceneContainerFlags.isEnabled()) {
+ float qsScrollViewTranslation =
+ onKeyguard && !mShowCollapsedOnKeyguard ? panelTranslationY : 0;
+ mQSPanelScrollView.setTranslationY(qsScrollViewTranslation);
- if (fullyCollapsed) {
- mQSPanelScrollView.setScrollY(0);
- }
+ if (fullyCollapsed) {
+ mQSPanelScrollView.setScrollY(0);
+ }
- if (!fullyExpanded) {
- // Set bounds on the QS panel so it doesn't run over the header when animating.
- mQsBounds.top = (int) -mQSPanelScrollView.getTranslationY();
- mQsBounds.right = mQSPanelScrollView.getWidth();
- mQsBounds.bottom = mQSPanelScrollView.getHeight();
+ if (!fullyExpanded) {
+ // Set bounds on the QS panel so it doesn't run over the header when animating.
+ mQsBounds.top = (int) -mQSPanelScrollView.getTranslationY();
+ mQsBounds.right = mQSPanelScrollView.getWidth();
+ mQsBounds.bottom = mQSPanelScrollView.getHeight();
+ }
}
updateQsBounds();
@@ -786,15 +811,17 @@
mQsBounds.set(-sideMargin, 0, mQSPanelScrollView.getWidth() + sideMargin,
mQSPanelScrollView.getHeight());
}
- mQSPanelScrollView.setClipBounds(mQsBounds);
+ if (!mSceneContainerFlags.isEnabled()) {
+ mQSPanelScrollView.setClipBounds(mQsBounds);
- mQSPanelScrollView.getLocationOnScreen(mLocationTemp);
- int left = mLocationTemp[0];
- int top = mLocationTemp[1];
- mQsMediaHost.getCurrentClipping().set(left, top,
- left + getView().getMeasuredWidth(),
- top + mQSPanelScrollView.getMeasuredHeight()
- - mQSPanelController.getPaddingBottom());
+ mQSPanelScrollView.getLocationOnScreen(mLocationTemp);
+ int left = mLocationTemp[0];
+ int top = mLocationTemp[1];
+ mQsMediaHost.getCurrentClipping().set(left, top,
+ left + getView().getMeasuredWidth(),
+ top + mQSPanelScrollView.getMeasuredHeight()
+ - mQSPanelController.getPaddingBottom());
+ }
}
private void updateMediaPositions() {
@@ -867,9 +894,15 @@
// The customize state changed, so our height changed.
mContainer.updateExpansion();
boolean customizing = isCustomizing();
- mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
+ if (mSceneContainerFlags.isEnabled()) {
+ mQSPanelController.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
+ } else {
+ mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
+ }
mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
- mFooterActionsView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
+ if (mFooterActionsView != null) {
+ mFooterActionsView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
+ }
mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
// Let the panel know the position changed and it needs to update where notifications
// and whatnot are.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 51b94dd..7a7ee59 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -387,7 +387,7 @@
setPaddingRelative(getPaddingStart(),
mSceneContainerEnabled ? 0 : paddingTop,
getPaddingEnd(),
- paddingBottom);
+ mSceneContainerEnabled ? 0 : paddingBottom);
}
void addOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index ef58a60..c3f5086 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -278,5 +278,9 @@
public int getPaddingBottom() {
return mView.getPaddingBottom();
}
+
+ int getViewBottom() {
+ return mView.getBottom();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index ce840ee..0d43396 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -17,10 +17,13 @@
package com.android.systemui.qs.ui.adapter
import android.content.Context
+import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import com.android.settingslib.applications.InterestingConfigChanges
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -58,7 +61,7 @@
/**
* Inflate an instance of [QSImpl] for this context. Once inflated, it will be available in
- * [qsView]
+ * [qsView]. Re-inflations due to configuration changes will use the last used [context].
*/
suspend fun inflate(context: Context)
@@ -90,6 +93,7 @@
private val qsImplProvider: Provider<QSImpl>,
@Main private val mainDispatcher: CoroutineDispatcher,
@Application applicationScope: CoroutineScope,
+ private val configurationInteractor: ConfigurationInteractor,
private val asyncLayoutInflaterFactory: (Context) -> AsyncLayoutInflater,
) : QSContainerController, QSSceneAdapter {
@@ -99,7 +103,15 @@
qsImplProvider: Provider<QSImpl>,
@Main dispatcher: CoroutineDispatcher,
@Application scope: CoroutineScope,
- ) : this(qsSceneComponentFactory, qsImplProvider, dispatcher, scope, ::AsyncLayoutInflater)
+ configurationInteractor: ConfigurationInteractor,
+ ) : this(
+ qsSceneComponentFactory,
+ qsImplProvider,
+ dispatcher,
+ scope,
+ configurationInteractor,
+ ::AsyncLayoutInflater,
+ )
private val state = MutableStateFlow<QSSceneAdapter.State>(QSSceneAdapter.State.CLOSED)
private val _isCustomizing: MutableStateFlow<Boolean> = MutableStateFlow(false)
@@ -109,14 +121,36 @@
val qsImpl = _qsImpl.asStateFlow()
override val qsView: Flow<View> = _qsImpl.map { it?.view }.filterNotNull()
+ // Same config changes as in FragmentHostManager
+ private val interestingChanges =
+ InterestingConfigChanges(
+ ActivityInfo.CONFIG_FONT_SCALE or
+ ActivityInfo.CONFIG_LOCALE or
+ ActivityInfo.CONFIG_ASSETS_PATHS
+ )
+
init {
applicationScope.launch {
- state.sample(_isCustomizing, ::Pair).collect { (state, customizing) ->
- _qsImpl.value?.apply {
- if (state != QSSceneAdapter.State.QS && customizing) {
- this@apply.closeCustomizerImmediately()
+ launch {
+ state.sample(_isCustomizing, ::Pair).collect { (state, customizing) ->
+ _qsImpl.value?.apply {
+ if (state != QSSceneAdapter.State.QS && customizing) {
+ this@apply.closeCustomizerImmediately()
+ }
+ applyState(state)
}
- applyState(state)
+ }
+ }
+ launch {
+ configurationInteractor.configurationValues.collect { config ->
+ if (interestingChanges.applyNewConfig(config)) {
+ // Assumption: The context is always the same and with the same theme.
+ // If colors change they will be reflected as attributes in the theme.
+ qsImpl.value?.view?.let { inflate(it.context) }
+ } else {
+ qsImpl.value?.onConfigurationChanged(config)
+ qsImpl.value?.view?.dispatchConfigurationChanged(config)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index e5e1e84..8a900ece 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -16,7 +16,10 @@
package com.android.systemui.qs.ui.viewmodel
+import androidx.lifecycle.LifecycleOwner
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
@@ -24,6 +27,7 @@
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlinx.coroutines.flow.map
@@ -35,6 +39,8 @@
val shadeHeaderViewModel: ShadeHeaderViewModel,
val qsSceneAdapter: QSSceneAdapter,
val notifications: NotificationsPlaceholderViewModel,
+ private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
+ private val footerActionsController: FooterActionsController,
) {
val destinationScenes =
qsSceneAdapter.isCustomizing.map { customizing ->
@@ -47,4 +53,13 @@
)
}
}
+
+ private val footerActionsControllerInitialized = AtomicBoolean(false)
+
+ fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
+ if (footerActionsControllerInitialized.compareAndSet(false, true)) {
+ footerActionsController.init()
+ }
+ return footerActionsViewModelFactory.create(lifecycleOwner)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index c96651c..995059d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -313,7 +313,7 @@
}
applicationScope.launch {
- keyguardInteractor.isDozing.distinctUntilChanged().collect { isDozing ->
+ keyguardInteractor.isDozing.collect { isDozing ->
falsingCollector.setShowingAod(isDozing)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 3362ebc..44cb0d6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -66,6 +66,13 @@
private var topEdgeSwipeRegionWidth: Int = 0
/**
+ * The height of the area in which a bottom edge swipe while the hub is open will not intercept
+ * touches, in pixels. This allows the bottom edge swipe to instead open the bouncer. Read from
+ * resources when [initView] is called.
+ */
+ private var bottomEdgeSwipeRegionWidth: Int = 0
+
+ /**
* True if we are currently tracking a gesture for opening the hub that started in the edge
* swipe region.
*/
@@ -132,6 +139,10 @@
communalContainerView.resources.getDimensionPixelSize(
R.dimen.communal_top_edge_swipe_region_height
)
+ bottomEdgeSwipeRegionWidth =
+ communalContainerView.resources.getDimensionPixelSize(
+ R.dimen.communal_bottom_edge_swipe_region_height
+ )
collectFlow(
communalContainerView,
@@ -179,11 +190,11 @@
if (hubShowing && isDown) {
val y = ev.rawY
val topSwipe: Boolean = y <= topEdgeSwipeRegionWidth
+ val bottomSwipe = y >= communalContainerView.height - bottomEdgeSwipeRegionWidth
- // TODO(b/315207481): allow bottom edge swipes to open the bouncer
- if (topSwipe) {
- // Don't intercept touches at the top edge so that swipes can open the notification
- // shade.
+ if (topSwipe || bottomSwipe) {
+ // Don't intercept touches at the top/bottom edge so that swipes can open the
+ // notification shade and bouncer.
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 8c852cd..863bb36 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -272,6 +272,14 @@
return result;
}
+ /**
+ * Handle a touch event while dreaming by forwarding the event to the content view.
+ * @param event The event to forward.
+ */
+ public void handleDreamTouch(MotionEvent event) {
+ mView.dispatchTouchEvent(event);
+ }
+
/** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
public void setupExpandedStatusBar() {
mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 909cff37..e598242 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -542,7 +542,19 @@
new ShortcutKeyGroupMultiMappingInfo(
context.getString(R.string.group_system_access_google_assistant),
Arrays.asList(
- Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON)))
+ Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))),
+ /* Lock screen: Meta + L */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_lock_screen),
+ Arrays.asList(
+ Pair.create(KeyEvent.KEYCODE_L, KeyEvent.META_META_ON))),
+ /* Pull up Notes app for quick memo: Meta + Ctrl + N */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_quick_memo),
+ Arrays.asList(
+ Pair.create(
+ KeyEvent.KEYCODE_N,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON)))
);
for (ShortcutKeyGroupMultiMappingInfo info : infoList) {
systemGroup.addItem(info.getShortcutMultiMappingInfo());
@@ -584,11 +596,17 @@
new ArrayList<>());
// System multitasking shortcuts:
+ // Enter Split screen with current app to RHS: Meta + Ctrl + Right arrow
+ // Enter Split screen with current app to LHS: Meta + Ctrl + Left arrow
// Switch from Split screen to full screen: Meta + Ctrl + Up arrow
String[] shortcutLabels = {
+ context.getString(R.string.system_multitasking_rhs),
+ context.getString(R.string.system_multitasking_lhs),
context.getString(R.string.system_multitasking_full_screen),
};
int[] keyCodes = {
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_UP,
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index 3a59978..46ddba4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -65,9 +65,7 @@
CallLayoutSetDataAsyncFactory callLayoutSetDataAsyncFactory
) {
final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
- if (featureFlags.isEnabled(Flags.PRECOMPUTED_TEXT)) {
- replacementFactories.add(precomputedTextViewFactory);
- }
+ replacementFactories.add(precomputedTextViewFactory);
if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
replacementFactories.add(bigPictureLayoutInflaterFactory);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 90cba40..4019436 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -25,6 +25,7 @@
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.UserHandle;
+import android.view.MotionEvent;
import android.view.RemoteAnimationAdapter;
import android.view.View;
import android.window.RemoteTransition;
@@ -277,6 +278,13 @@
void awakenDreams();
+ /**
+ * Handle a touch event while dreaming when the touch was initiated within a prescribed
+ * swipeable area. This method is provided for cases where swiping in certain areas of a dream
+ * should be handled by CentralSurfaces instead (e.g. swiping communal hub open).
+ */
+ void handleDreamTouch(MotionEvent event);
+
boolean isBouncerShowing();
boolean isBouncerShowingScrimmed();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 7dc4b96..60dfaa7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone
import android.content.Intent
+import android.view.MotionEvent
import androidx.lifecycle.LifecycleRegistry
import com.android.keyguard.AuthKeyguardMessageArea
import com.android.systemui.animation.ActivityLaunchAnimator
@@ -78,6 +79,7 @@
override fun updateScrimController() {}
override fun shouldIgnoreTouch() = false
override fun isDeviceInteractive() = false
+ override fun handleDreamTouch(event: MotionEvent?) {}
override fun awakenDreams() {}
override fun isBouncerShowing() = false
override fun isBouncerShowingScrimmed() = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 6e3aabf..266c19c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -80,6 +80,7 @@
import android.view.Display;
import android.view.IRemoteAnimationRunner;
import android.view.IWindowManager;
+import android.view.MotionEvent;
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.WindowInsets;
@@ -2903,6 +2904,11 @@
};
@Override
+ public void handleDreamTouch(MotionEvent event) {
+ getNotificationShadeWindowViewController().handleDreamTouch(event);
+ }
+
+ @Override
public void awakenDreams() {
mUiBgExecutor.execute(() -> {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 459b368..aabe4a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -300,7 +300,7 @@
DozeParameters dozeParameters,
AlarmManager alarmManager,
KeyguardStateController keyguardStateController,
- DelayedWakeLock.Builder delayedWakeLockBuilder,
+ DelayedWakeLock.Factory delayedWakeLockFactory,
Handler handler,
KeyguardUpdateMonitor keyguardUpdateMonitor,
DockManager dockManager,
@@ -331,7 +331,7 @@
mScreenOffAnimationController = screenOffAnimationController;
mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
"hide_aod_wallpaper", mHandler);
- mWakeLock = delayedWakeLockBuilder.setHandler(mHandler).setTag("Scrims").build();
+ mWakeLock = delayedWakeLockFactory.create("Scrims");
// Scrim alpha is initially set to the value on the resource but might be changed
// to make sure that text on top of it is legible.
mDozeParameters = dozeParameters;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index fe49c07..6b30326 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.content.Context
+import com.android.internal.telephony.flags.Flags
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.graph.SignalDrawable
import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
@@ -32,14 +33,18 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
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
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -79,6 +84,9 @@
/** Whether or not to show the slice attribution */
val showSliceAttribution: StateFlow<Boolean>
+ /** True if this connection is satellite-based */
+ val isNonTerrestrial: StateFlow<Boolean>
+
/**
* Provider name for this network connection. The name can be one of 3 values:
* 1. The default network name, if one is configured
@@ -244,6 +252,13 @@
override val showSliceAttribution: StateFlow<Boolean> =
connectionRepository.hasPrioritizedNetworkCapabilities
+ override val isNonTerrestrial: StateFlow<Boolean> =
+ if (Flags.carrierEnabledSatelliteFlag()) {
+ connectionRepository.isNonTerrestrial
+ } else {
+ MutableStateFlow(false).asStateFlow()
+ }
+
override val isRoaming: StateFlow<Boolean> =
combine(
connectionRepository.carrierNetworkChangeActive,
@@ -313,26 +328,45 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
- override val signalLevelIcon: StateFlow<SignalIconModel> = run {
- val initial =
- SignalIconModel(
- level = shownLevel.value,
- numberOfLevels = numberOfLevels.value,
- showExclamationMark = showExclamationMark.value,
- carrierNetworkChange = carrierNetworkChangeActive.value,
- )
+ private val cellularIcon: Flow<SignalIconModel.Cellular> =
combine(
+ shownLevel,
+ numberOfLevels,
+ showExclamationMark,
+ carrierNetworkChangeActive,
+ ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
+ SignalIconModel.Cellular(
shownLevel,
numberOfLevels,
showExclamationMark,
- carrierNetworkChangeActive,
- ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
- SignalIconModel(
- shownLevel,
- numberOfLevels,
- showExclamationMark,
- carrierNetworkChange,
- )
+ carrierNetworkChange,
+ )
+ }
+
+ private val satelliteIcon: Flow<SignalIconModel.Satellite> =
+ shownLevel.map {
+ SignalIconModel.Satellite(
+ level = it,
+ icon = SatelliteIconModel.fromSignalStrength(it)
+ ?: SatelliteIconModel.fromSignalStrength(0)!!
+ )
+ }
+
+ override val signalLevelIcon: StateFlow<SignalIconModel> = run {
+ val initial =
+ SignalIconModel.Cellular(
+ shownLevel.value,
+ numberOfLevels.value,
+ showExclamationMark.value,
+ carrierNetworkChangeActive.value,
+ )
+ isNonTerrestrial
+ .flatMapLatest { ntn ->
+ if (ntn) {
+ satelliteIcon
+ } else {
+ cellularIcon
+ }
}
.distinctUntilChanged()
.logDiffsForTable(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
index e58f081..d6b8fd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
@@ -17,51 +17,94 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.model
import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
-/** A model that will be consumed by [SignalDrawable] to show the mobile triangle icon. */
-data class SignalIconModel(
- val level: Int,
- val numberOfLevels: Int,
- val showExclamationMark: Boolean,
- val carrierNetworkChange: Boolean,
-) : Diffable<SignalIconModel> {
- // TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes?
+sealed interface SignalIconModel : Diffable<SignalIconModel> {
+ val level: Int
+
override fun logDiffs(prevVal: SignalIconModel, row: TableRowLogger) {
- if (prevVal.level != level) {
+ logPartial(prevVal, row)
+ }
+
+ override fun logFull(row: TableRowLogger) = logFully(row)
+
+ fun logFully(row: TableRowLogger)
+
+ fun logPartial(prevVal: SignalIconModel, row: TableRowLogger)
+
+ /** A model that will be consumed by [SignalDrawable] to show the mobile triangle icon. */
+ data class Cellular(
+ override val level: Int,
+ val numberOfLevels: Int,
+ val showExclamationMark: Boolean,
+ val carrierNetworkChange: Boolean,
+ ) : SignalIconModel {
+ override fun logPartial(prevVal: SignalIconModel, row: TableRowLogger) {
+ if (prevVal !is Cellular) {
+ logFull(row)
+ } else {
+ if (prevVal.level != level) {
+ row.logChange(COL_LEVEL, level)
+ }
+ if (prevVal.numberOfLevels != numberOfLevels) {
+ row.logChange(COL_NUM_LEVELS, numberOfLevels)
+ }
+ if (prevVal.showExclamationMark != showExclamationMark) {
+ row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
+ }
+ if (prevVal.carrierNetworkChange != carrierNetworkChange) {
+ row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange)
+ }
+ }
+ }
+
+ override fun logFully(row: TableRowLogger) {
+ row.logChange(COL_TYPE, "c")
row.logChange(COL_LEVEL, level)
- }
- if (prevVal.numberOfLevels != numberOfLevels) {
row.logChange(COL_NUM_LEVELS, numberOfLevels)
- }
- if (prevVal.showExclamationMark != showExclamationMark) {
row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
- }
- if (prevVal.carrierNetworkChange != carrierNetworkChange) {
row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange)
}
+
+ /** Convert this model to an [Int] consumable by [SignalDrawable]. */
+ fun toSignalDrawableState(): Int =
+ if (carrierNetworkChange) {
+ SignalDrawable.getCarrierChangeState(numberOfLevels)
+ } else {
+ SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
+ }
}
- override fun logFull(row: TableRowLogger) {
- row.logChange(COL_LEVEL, level)
- row.logChange(COL_NUM_LEVELS, numberOfLevels)
- row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
- row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange)
- }
-
- /** Convert this model to an [Int] consumable by [SignalDrawable]. */
- fun toSignalDrawableState(): Int =
- if (carrierNetworkChange) {
- SignalDrawable.getCarrierChangeState(numberOfLevels)
- } else {
- SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
+ /**
+ * For non-terrestrial networks, we can use a resource-backed icon instead of the
+ * [SignalDrawable]-backed version above
+ */
+ data class Satellite(
+ override val level: Int,
+ val icon: Icon.Resource,
+ ) : SignalIconModel {
+ override fun logPartial(prevVal: SignalIconModel, row: TableRowLogger) {
+ if (prevVal !is Satellite) {
+ logFull(row)
+ } else {
+ if (prevVal.level != level) row.logChange(COL_LEVEL, level)
+ }
}
+ override fun logFully(row: TableRowLogger) {
+ row.logChange("numLevels", "HELLO")
+ row.logChange(COL_TYPE, "s")
+ row.logChange(COL_LEVEL, level)
+ }
+ }
+
companion object {
private const val COL_LEVEL = "level"
private const val COL_NUM_LEVELS = "numLevels"
private const val COL_SHOW_EXCLAMATION = "showExclamation"
private const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChange"
+ private const val COL_TYPE = "type"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
index a1a5370..43cb38f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
@@ -59,7 +59,7 @@
str1 = parentView.getIdForLogging()
int1 = subId
int2 = icon.level
- bool1 = icon.showExclamationMark
+ bool1 = if (icon is SignalIconModel.Cellular) icon.showExclamationMark else false
},
{
"Binder[subId=$int1, viewId=$str1] received new signal icon: " +
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 5475528..a0c5618 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -38,6 +38,7 @@
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
@@ -70,7 +71,7 @@
val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type)
val networkTypeContainer = view.requireViewById<FrameLayout>(R.id.mobile_type_container)
val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
- val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
+ val mobileDrawable = SignalDrawable(view.context)
val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming)
val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space)
val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
@@ -138,7 +139,12 @@
viewModel.subscriptionId,
icon,
)
- mobileDrawable.level = icon.toSignalDrawableState()
+ if (icon is SignalIconModel.Cellular) {
+ iconView.setImageDrawable(mobileDrawable)
+ mobileDrawable.level = icon.toSignalDrawableState()
+ } else if (icon is SignalIconModel.Satellite) {
+ IconViewBinder.bind(icon.icon, iconView)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 60c662d..eda5c44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -33,12 +33,15 @@
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.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
/** Common interface for all of the location-based mobile icon view models. */
@@ -71,7 +74,6 @@
* model gets the exact same information, as well as allows us to log that unified state only once
* per icon.
*/
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class MobileIconViewModel(
override val subscriptionId: Int,
@@ -81,6 +83,100 @@
flags: FeatureFlagsClassic,
scope: CoroutineScope,
) : MobileIconViewModelCommon {
+ private val cellProvider by lazy {
+ CellularIconViewModel(
+ subscriptionId,
+ iconInteractor,
+ airplaneModeInteractor,
+ constants,
+ flags,
+ scope,
+ )
+ }
+
+ private val satelliteProvider by lazy {
+ CarrierBasedSatelliteViewModelImpl(
+ subscriptionId,
+ iconInteractor,
+ )
+ }
+
+ /**
+ * Similar to repository switching, this allows us to split up the logic of satellite/cellular
+ * states, since they are different by nature
+ */
+ private val vmProvider: Flow<MobileIconViewModelCommon> =
+ iconInteractor.isNonTerrestrial
+ .mapLatest { nonTerrestrial ->
+ if (nonTerrestrial) {
+ satelliteProvider
+ } else {
+ cellProvider
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), cellProvider)
+
+ override val isVisible: StateFlow<Boolean> =
+ vmProvider
+ .flatMapLatest { it.isVisible }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val icon: Flow<SignalIconModel> = vmProvider.flatMapLatest { it.icon }
+
+ override val contentDescription: Flow<ContentDescription> =
+ vmProvider.flatMapLatest { it.contentDescription }
+
+ override val roaming: Flow<Boolean> = vmProvider.flatMapLatest { it.roaming }
+
+ override val networkTypeIcon: Flow<Icon.Resource?> =
+ vmProvider.flatMapLatest { it.networkTypeIcon }
+
+ override val networkTypeBackground: StateFlow<Icon.Resource?> =
+ vmProvider
+ .flatMapLatest { it.networkTypeBackground }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val activityInVisible: Flow<Boolean> =
+ vmProvider.flatMapLatest { it.activityInVisible }
+
+ override val activityOutVisible: Flow<Boolean> =
+ vmProvider.flatMapLatest { it.activityOutVisible }
+
+ override val activityContainerVisible: Flow<Boolean> =
+ vmProvider.flatMapLatest { it.activityContainerVisible }
+}
+
+/** Representation of this network when it is non-terrestrial (e.g., satellite) */
+private class CarrierBasedSatelliteViewModelImpl(
+ override val subscriptionId: Int,
+ interactor: MobileIconInteractor,
+) : MobileIconViewModelCommon {
+ override val isVisible: StateFlow<Boolean> = MutableStateFlow(true)
+ override val icon: Flow<SignalIconModel> = interactor.signalLevelIcon
+
+ override val contentDescription: Flow<ContentDescription> =
+ MutableStateFlow(ContentDescription.Loaded(""))
+
+ /** These fields are not used for satellite icons currently */
+ override val roaming: Flow<Boolean> = flowOf(false)
+ override val networkTypeIcon: Flow<Icon.Resource?> = flowOf(null)
+ override val networkTypeBackground: StateFlow<Icon.Resource?> = MutableStateFlow(null)
+ override val activityInVisible: Flow<Boolean> = flowOf(false)
+ override val activityOutVisible: Flow<Boolean> = flowOf(false)
+ override val activityContainerVisible: Flow<Boolean> = flowOf(false)
+}
+
+/** Terrestrial (cellular) icon. */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+private class CellularIconViewModel(
+ override val subscriptionId: Int,
+ iconInteractor: MobileIconInteractor,
+ airplaneModeInteractor: AirplaneModeInteractor,
+ constants: ConnectivityConstants,
+ flags: FeatureFlagsClassic,
+ scope: CoroutineScope,
+) : MobileIconViewModelCommon {
override val isVisible: StateFlow<Boolean> =
if (!constants.hasDataCapabilities) {
flowOf(false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
index 6938d66..63566ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
@@ -28,7 +28,7 @@
fun fromConnectionState(
connectionState: SatelliteConnectionState,
signalStrength: Int,
- ): Icon? =
+ ): Icon.Resource? =
when (connectionState) {
// TODO(b/316635648): check if this should be null
SatelliteConnectionState.Unknown,
@@ -41,9 +41,13 @@
SatelliteConnectionState.Connected -> fromSignalStrength(signalStrength)
}
- private fun fromSignalStrength(
+ /**
+ * Satellite icon appropriate for when we are connected. Use [fromConnectionState] for a more
+ * generally correct representation.
+ */
+ fun fromSignalStrength(
signalStrength: Int,
- ): Icon? =
+ ): Icon.Resource? =
// TODO(b/316634365): these need content descriptions
when (signalStrength) {
// No signal
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
index ae58398..352413e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.text.Html
import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -28,6 +29,7 @@
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.SignalIcon
@@ -116,14 +118,31 @@
it.signalLevelIcon,
mobileDataContentName,
) { networkNameModel, signalIcon, dataContentDescription ->
- val secondary =
- mobileDataContentConcat(networkNameModel.name, dataContentDescription)
- InternetTileModel.Active(
- secondaryTitle = secondary,
- icon = SignalIcon(signalIcon.toSignalDrawableState()),
- stateDescription = ContentDescription.Loaded(secondary.toString()),
- contentDescription = ContentDescription.Loaded(internetLabel),
- )
+ when (signalIcon) {
+ is SignalIconModel.Cellular -> {
+ val secondary =
+ mobileDataContentConcat(
+ networkNameModel.name,
+ dataContentDescription
+ )
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ icon = SignalIcon(signalIcon.toSignalDrawableState()),
+ stateDescription = ContentDescription.Loaded(secondary.toString()),
+ contentDescription = ContentDescription.Loaded(internetLabel),
+ )
+ }
+ is SignalIconModel.Satellite -> {
+ val secondary =
+ signalIcon.icon.contentDescription.loadContentDescription(context)
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ iconId = signalIcon.icon.res,
+ stateDescription = ContentDescription.Loaded(secondary),
+ contentDescription = ContentDescription.Loaded(internetLabel),
+ )
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
index 2336a8e..6993c96 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
@@ -28,9 +28,13 @@
fun <A, B, C, D> toQuad(a: A, bcd: Triple<B, C, D>) =
Quad(a, bcd.first, bcd.second, bcd.third)
+ fun <A, B, C, D, E> toQuint(a: A, b: B, c: C, d: D, e: E) = Quint(a, b, c, d, e)
fun <A, B, C, D, E> toQuint(a: A, bcde: Quad<B, C, D, E>) =
Quint(a, bcde.first, bcde.second, bcde.third, bcde.fourth)
+ fun <A, B, C, D, E, F> toSextuple(a: A, bcdef: Quint<B, C, D, E, F>) =
+ Sextuple(a, bcdef.first, bcdef.second, bcdef.third, bcdef.fourth, bcdef.fifth)
+
/**
* Samples the provided flows, emitting a tuple of the original flow's value as well as each
* of the combined flows' values.
@@ -69,6 +73,22 @@
): Flow<Quint<A, B, C, D, E>> {
return this.sample(combine(b, c, d, e, ::Quad), ::toQuint)
}
+
+ /**
+ * Samples the provided flows, emitting a tuple of the original flow's value as well as each
+ * of the combined flows' values.
+ *
+ * Flow<A>.sample(Flow<B>, Flow<C>, Flow<D>, Flow<E>, Flow<F>) -> (A, B, C, D, E, F)
+ */
+ fun <A, B, C, D, E, F> Flow<A>.sample(
+ b: Flow<B>,
+ c: Flow<C>,
+ d: Flow<D>,
+ e: Flow<E>,
+ f: Flow<F>,
+ ): Flow<Sextuple<A, B, C, D, E, F>> {
+ return this.sample(combine(b, c, d, e, f, ::Quint), ::toSextuple)
+ }
}
}
@@ -81,3 +101,12 @@
val fourth: D,
val fifth: E
)
+
+data class Sextuple<A, B, C, D, E, F>(
+ val first: A,
+ val second: B,
+ val third: C,
+ val fourth: D,
+ val fifth: E,
+ val sixth: F,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
index 972895d..039109e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
@@ -19,7 +19,11 @@
import android.content.Context;
import android.os.Handler;
-import javax.inject.Inject;
+import com.android.systemui.dagger.qualifiers.Background;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
/**
* A wake lock that has a built in delay when releasing to give the framebuffer time to update.
@@ -32,9 +36,11 @@
private final Handler mHandler;
private final WakeLock mInner;
- public DelayedWakeLock(Handler h, WakeLock inner) {
- mHandler = h;
- mInner = inner;
+ @AssistedInject
+ public DelayedWakeLock(@Background Handler handler, Context context, WakeLockLogger logger,
+ @Assisted String tag) {
+ mInner = WakeLock.createPartial(context, logger, tag);
+ mHandler = handler;
}
@Override
@@ -58,46 +64,11 @@
}
/**
- * An injectable builder for {@see DelayedWakeLock} that has the context already filled in.
+ * Factory to create the instance of DelayedWakeLock class.
*/
- public static class Builder {
- private final Context mContext;
- private final WakeLockLogger mLogger;
- private String mTag;
- private Handler mHandler;
-
- /**
- * Constructor for DelayedWakeLock.Builder
- */
- @Inject
- public Builder(Context context, WakeLockLogger logger) {
- mContext = context;
- mLogger = logger;
- }
-
- /**
- * Set the tag for the WakeLock.
- */
- public Builder setTag(String tag) {
- mTag = tag;
-
- return this;
- }
-
- /**
- * Set the handler for the DelayedWakeLock.
- */
- public Builder setHandler(Handler handler) {
- mHandler = handler;
-
- return this;
- }
-
- /**
- * Build the DelayedWakeLock.
- */
- public DelayedWakeLock build() {
- return new DelayedWakeLock(mHandler, WakeLock.createPartial(mContext, mLogger, mTag));
- }
+ @AssistedFactory
+ public interface Factory {
+ /** creates the instance of DelayedWakeLock class. */
+ DelayedWakeLock create(String tag);
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 9ee3d22..aee441a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -535,6 +535,7 @@
}
if (changed && fromKey) {
Events.writeEvent(Events.EVENT_KEY, stream, lastAudibleStreamVolume);
+ mCallbacks.onVolumeChangedFromKey();
}
return changed;
}
@@ -1030,6 +1031,18 @@
}
@Override
+ public void onVolumeChangedFromKey() {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onVolumeChangedFromKey();
+ }
+ });
+ }
+ }
+
+ @Override
public void onAccessibilityModeChanged(Boolean showA11yStream) {
boolean show = showA11yStream != null && showA11yStream;
for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 404621d..ce6d740 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -34,6 +34,7 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
+import static com.android.systemui.Flags.hapticVolumeSlider;
import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
@@ -49,7 +50,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -76,7 +76,6 @@
import android.provider.Settings;
import android.provider.Settings.Global;
import android.text.InputFilter;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -117,14 +116,18 @@
import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
import com.android.systemui.Prefs;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
+import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.plugins.VolumeDialogController.StreamState;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -132,6 +135,8 @@
import com.android.systemui.util.AlphaTintDrawableWrapper;
import com.android.systemui.util.RoundedCornerProgressDrawable;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
+import com.android.systemui.volume.ui.navigation.VolumeNavigator;
import dagger.Lazy;
@@ -140,6 +145,9 @@
import java.util.List;
import java.util.function.Consumer;
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.CoroutineScope;
+
/**
* Visual presentation of the volume dialog.
*
@@ -262,9 +270,9 @@
private final Accessibility mAccessibility = new Accessibility();
private final ConfigurationController mConfigurationController;
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
- private final VolumePanelFactory mVolumePanelFactory;
private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
- private final ActivityStarter mActivityStarter;
+ private final VolumePanelNavigationInteractor mVolumePanelNavigationInteractor;
+ private final VolumeNavigator mVolumeNavigator;
private boolean mShowing;
private boolean mShowA11yStream;
private int mActiveStream;
@@ -303,6 +311,10 @@
private int mOrientation;
private final Lazy<SecureSettings> mSecureSettings;
private int mDialogTimeoutMillis;
+ private final CoroutineDispatcher mMainDispatcher;
+ private final CoroutineScope mApplicationScope;
+ private final VibratorHelper mVibratorHelper;
+ private final com.android.systemui.util.time.SystemClock mSystemClock;
public VolumeDialogImpl(
Context context,
@@ -311,19 +323,26 @@
DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,
MediaOutputDialogFactory mediaOutputDialogFactory,
- VolumePanelFactory volumePanelFactory,
- ActivityStarter activityStarter,
InteractionJankMonitor interactionJankMonitor,
+ VolumePanelNavigationInteractor volumePanelNavigationInteractor,
+ VolumeNavigator volumeNavigator,
boolean shouldListenForJank,
CsdWarningDialog.Factory csdWarningDialogFactory,
DevicePostureController devicePostureController,
Looper looper,
DumpManager dumpManager,
- Lazy<SecureSettings> secureSettings) {
+ Lazy<SecureSettings> secureSettings,
+ VibratorHelper vibratorHelper,
+ @Main CoroutineDispatcher mainDispatcher,
+ @Application CoroutineScope applicationScope,
+ com.android.systemui.util.time.SystemClock systemClock) {
mContext =
new ContextThemeWrapper(context, R.style.volume_dialog_theme);
mHandler = new H(looper);
-
+ mMainDispatcher = mainDispatcher;
+ mApplicationScope = applicationScope;
+ mVibratorHelper = vibratorHelper;
+ mSystemClock = systemClock;
mShouldListenForJank = shouldListenForJank;
mController = volumeDialogController;
mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
@@ -332,9 +351,7 @@
mDeviceProvisionedController = deviceProvisionedController;
mConfigurationController = configurationController;
mMediaOutputDialogFactory = mediaOutputDialogFactory;
- mVolumePanelFactory = volumePanelFactory;
mCsdWarningDialogFactory = csdWarningDialogFactory;
- mActivityStarter = activityStarter;
mShowActiveStreamOnly = showActiveStreamOnly();
mHasSeenODICaptionsTooltip =
Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
@@ -349,6 +366,8 @@
mUseBackgroundBlur =
mContext.getResources().getBoolean(R.bool.config_volumeDialogUseBackgroundBlur);
mInteractionJankMonitor = interactionJankMonitor;
+ mVolumePanelNavigationInteractor = volumePanelNavigationInteractor;
+ mVolumeNavigator = volumeNavigator;
mSecureSettings = secureSettings;
mDialogTimeoutMillis = DIALOG_TIMEOUT_MILLIS;
@@ -839,6 +858,7 @@
row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)});
}
row.slider = row.view.findViewById(R.id.volume_row_slider);
+ row.createPlugin(mVibratorHelper, mSystemClock, mMainDispatcher, mApplicationScope);
row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
row.number = row.view.findViewById(R.id.volume_number);
@@ -1187,13 +1207,8 @@
Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
dismissH(DISMISS_REASON_SETTINGS_CLICKED);
mMediaOutputDialogFactory.dismiss();
- if (FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_VOLUME_PANEL_IN_SYSTEMUI)) {
- mVolumePanelFactory.create(true /* aboveStatusBar */, null);
- } else {
- mActivityStarter.startActivity(new Intent(Settings.Panel.ACTION_VOLUME),
- true /* dismissShade */);
- }
+ mVolumeNavigator.openVolumePanel(
+ mVolumePanelNavigationInteractor.getVolumePanelRoute());
});
}
}
@@ -1480,6 +1495,12 @@
mController.getCaptionsComponentState(false);
checkODICaptionsTooltip(false);
updateBackgroundForDrawerClosedAmount();
+ for (int i = 0; i < mRows.size(); i++) {
+ VolumeRow row = mRows.get(i);
+ if (row.slider.getVisibility() == VISIBLE) {
+ row.addHaptics();
+ }
+ }
Trace.endSection();
}
@@ -1532,7 +1553,9 @@
protected void dismissH(int reason) {
Trace.beginSection("VolumeDialogImpl#dismissH");
-
+ for (int i = 0; i < mRows.size(); i++) {
+ mRows.get(i).removeHaptics();
+ }
Log.i(TAG, "mDialog.dismiss() reason: " + Events.DISMISS_REASONS[reason]
+ " from: " + Debug.getCaller());
@@ -2358,6 +2381,14 @@
public void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkForSwitchState) {
updateCaptionsEnabledH(isEnabled, checkForSwitchState);
}
+
+ @Override
+ public void onVolumeChangedFromKey() {
+ VolumeRow activeRow = getActiveRow();
+ if (activeRow.mHapticPlugin != null) {
+ activeRow.mHapticPlugin.onKeyDown();
+ }
+ }
};
@VisibleForTesting void onPostureChanged(int posture) {
@@ -2459,6 +2490,15 @@
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (mRow.ss == null) return;
+ if (getActiveRow().equals(mRow)
+ && mRow.slider.getVisibility() == VISIBLE
+ && mRow.mHapticPlugin != null) {
+ mRow.mHapticPlugin.onProgressChanged(seekBar, progress, fromUser);
+ if (!fromUser) {
+ // Consider a change from program as the volume key being continuously pressed
+ mRow.mHapticPlugin.onKeyDown();
+ }
+ }
if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
+ " onProgressChanged " + progress + " fromUser=" + fromUser);
if (!fromUser) return;
@@ -2485,6 +2525,9 @@
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
if (D.BUG) Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream);
+ if (mRow.mHapticPlugin != null) {
+ mRow.mHapticPlugin.onStartTrackingTouch(seekBar);
+ }
mController.setActiveStream(mRow.stream);
mRow.tracking = true;
}
@@ -2492,6 +2535,9 @@
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (D.BUG) Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream);
+ if (mRow.mHapticPlugin != null) {
+ mRow.mHapticPlugin.onStopTrackingTouch(seekBar);
+ }
mRow.tracking = false;
mRow.userAttempt = SystemClock.uptimeMillis();
final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
@@ -2524,6 +2570,22 @@
}
private static class VolumeRow {
+ private static final SliderHapticFeedbackConfig sSliderHapticFeedbackConfig =
+ new SliderHapticFeedbackConfig(
+ /* velocityInterpolatorFactor= */ 1f,
+ /* progressInterpolatorFactor= */ 1f,
+ /* progressBasedDragMinScale= */ 0f,
+ /* progressBasedDragMaxScale= */ 0.2f,
+ /* additionalVelocityMaxBump= */ 0.15f,
+ /* deltaMillisForDragInterval= */ 0f,
+ /* deltaProgressForDragThreshold= */ 0.015f,
+ /* numberOfLowTicks= */ 5,
+ /* maxVelocityToScale= */ 300f,
+ /* velocityAxis= */ MotionEvent.AXIS_Y,
+ /* upperBookendScale= */ 1f,
+ /* lowerBookendScale= */ 0.05f,
+ /* exponent= */ 1f / 0.89f);
+
private View view;
private TextView header;
private ImageButton icon;
@@ -2544,6 +2606,7 @@
private ObjectAnimator anim; // slider progress animation for non-touch-related updates
private int animTargetProgress;
private int lastAudibleLevel = 1;
+ private SeekableSliderHapticPlugin mHapticPlugin;
void setIcon(int iconRes, Resources.Theme theme) {
if (icon != null) {
@@ -2554,6 +2617,50 @@
sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme));
}
}
+
+ void createPlugin(
+ VibratorHelper vibratorHelper,
+ com.android.systemui.util.time.SystemClock systemClock,
+ CoroutineDispatcher mainDispatcher,
+ CoroutineScope applicationScope) {
+ if (!hapticVolumeSlider() || mHapticPlugin != null) return;
+
+ mHapticPlugin = new SeekableSliderHapticPlugin(
+ vibratorHelper,
+ systemClock,
+ mainDispatcher,
+ applicationScope,
+ sSliderHapticFeedbackConfig);
+ }
+
+
+ @SuppressLint("ClickableViewAccessibility")
+ void addTouchListener() {
+ slider.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ if (mHapticPlugin != null) {
+ mHapticPlugin.onTouchEvent(motionEvent);
+ }
+ return false;
+ }
+ });
+ }
+
+ void addHaptics() {
+ if (mHapticPlugin != null) {
+ addTouchListener();
+ mHapticPlugin.start();
+ }
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ void removeHaptics() {
+ slider.setOnTouchListener(null);
+ if (mHapticPlugin != null) {
+ mHapticPlugin.stop();
+ }
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 497c4cb..b1bfbe0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -16,30 +16,36 @@
package com.android.systemui.volume.dagger;
+import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
import android.os.Looper;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.time.SystemClock;
import com.android.systemui.volume.CsdWarningDialog;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.volume.VolumeDialogComponent;
import com.android.systemui.volume.VolumeDialogImpl;
-import com.android.systemui.volume.VolumePanelFactory;
import com.android.systemui.volume.VolumeUI;
+import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
import com.android.systemui.volume.panel.dagger.VolumePanelComponent;
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory;
+import com.android.systemui.volume.panel.ui.activity.VolumePanelActivity;
+import com.android.systemui.volume.ui.navigation.VolumeNavigator;
import dagger.Binds;
import dagger.Lazy;
@@ -49,6 +55,9 @@
import dagger.multibindings.IntoMap;
import dagger.multibindings.IntoSet;
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.CoroutineScope;
+
/** Dagger Module for code in the volume package. */
@Module(
subcomponents = {
@@ -71,6 +80,12 @@
@Binds
VolumeComponent provideVolumeComponent(VolumeDialogComponent volumeDialogComponent);
+ /** Inject into VolumePanelActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(VolumePanelActivity.class)
+ Activity bindVolumePanelActivity(VolumePanelActivity activity);
+
/** */
@Binds
VolumePanelComponentFactory bindVolumePanelComponentFactory(VolumePanelComponent.Factory impl);
@@ -84,13 +99,17 @@
DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,
MediaOutputDialogFactory mediaOutputDialogFactory,
- VolumePanelFactory volumePanelFactory,
- ActivityStarter activityStarter,
InteractionJankMonitor interactionJankMonitor,
+ VolumePanelNavigationInteractor volumePanelNavigationInteractor,
+ VolumeNavigator volumeNavigator,
CsdWarningDialog.Factory csdFactory,
DevicePostureController devicePostureController,
DumpManager dumpManager,
- Lazy<SecureSettings> secureSettings) {
+ Lazy<SecureSettings> secureSettings,
+ VibratorHelper vibratorHelper,
+ @Main CoroutineDispatcher mainDispatcher,
+ @Application CoroutineScope applicationScope,
+ SystemClock systemClock) {
VolumeDialogImpl impl = new VolumeDialogImpl(
context,
volumeDialogController,
@@ -98,15 +117,19 @@
deviceProvisionedController,
configurationController,
mediaOutputDialogFactory,
- volumePanelFactory,
- activityStarter,
interactionJankMonitor,
+ volumePanelNavigationInteractor,
+ volumeNavigator,
true, /* should listen for jank */
csdFactory,
devicePostureController,
Looper.getMainLooper(),
dumpManager,
- secureSettings);
+ secureSettings,
+ vibratorHelper,
+ mainDispatcher,
+ applicationScope,
+ systemClock);
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
impl.setAutomute(true);
impl.setSilentMode(false);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/VolumePanelNavigationInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/VolumePanelNavigationInteractor.kt
new file mode 100644
index 0000000..d64bb03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/VolumePanelNavigationInteractor.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.domain.interactor
+
+import android.content.Context
+import android.util.FeatureFlagUtils
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.volume.domain.model.VolumePanelRoute
+import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag
+import javax.inject.Inject
+
+/** Provides navigation routes for Volume space. */
+class VolumePanelNavigationInteractor
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val volumePanelFlag: VolumePanelFlag,
+) {
+
+ fun getVolumePanelRoute(): VolumePanelRoute {
+ return when {
+ volumePanelFlag.canUseNewVolumePanel() -> VolumePanelRoute.COMPOSE_VOLUME_PANEL
+ FeatureFlagUtils.isEnabled(
+ context,
+ FeatureFlagUtils.SETTINGS_VOLUME_PANEL_IN_SYSTEMUI
+ ) -> VolumePanelRoute.SYSTEM_UI_VOLUME_PANEL
+ else -> VolumePanelRoute.SETTINGS_VOLUME_PANEL
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/model/VolumePanelRoute.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/src/com/android/systemui/volume/domain/model/VolumePanelRoute.kt
index 22a74d2..c85af15 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/model/VolumePanelRoute.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.volume.domain.model
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+enum class VolumePanelRoute {
+ COMPOSE_VOLUME_PANEL,
+ SETTINGS_VOLUME_PANEL,
+ SYSTEM_UI_VOLUME_PANEL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
new file mode 100644
index 0000000..8ff2837
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.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.systemui.volume.panel.component.bottombar.ui.viewmodel
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import javax.inject.Inject
+
+@VolumePanelScope
+class BottomBarViewModel
+@Inject
+constructor(
+ private val activityStarter: ActivityStarter,
+ private val volumePanelViewModel: VolumePanelViewModel,
+) {
+
+ fun onDoneClicked() {
+ volumePanelViewModel.dismissPanel()
+ }
+
+ fun onSettingsClicked() {
+ volumePanelViewModel.dismissPanel()
+ activityStarter.startActivity(
+ Intent(Settings.ACTION_SOUND_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+ true,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
similarity index 65%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
index 22a74d2..1a4174a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.volume.panel.component.shared.model
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+
+object VolumePanelComponents {
+
+ const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
index 3660ac1..d1d5390 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
@@ -16,8 +16,9 @@
package com.android.systemui.volume.panel.dagger
-import com.android.systemui.volume.panel.VolumePanelComponentKey
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
import dagger.Module
import dagger.multibindings.Multibinds
@@ -28,4 +29,6 @@
interface DefaultMultibindsModule {
@Multibinds fun criteriaMap(): Map<VolumePanelComponentKey, ComponentAvailabilityCriteria>
+
+ @Multibinds fun components(): Map<VolumePanelComponentKey, VolumePanelUiComponent>
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
index 0a057eb..0f19e9f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -16,12 +16,14 @@
package com.android.systemui.volume.panel.dagger
+import com.android.systemui.volume.panel.component.bottombar.BottomBarModule
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.DomainModule
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
import com.android.systemui.volume.panel.ui.UiModule
-import com.android.systemui.volume.panel.ui.viewmodel.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import dagger.BindsInstance
import dagger.Subcomponent
@@ -41,6 +43,7 @@
DomainModule::class,
UiModule::class,
// Components modules
+ BottomBarModule::class,
]
)
interface VolumePanelComponent {
@@ -49,6 +52,8 @@
fun componentsInteractor(): ComponentsInteractor
+ fun componentsFactory(): ComponentsFactory
+
fun componentsLayoutManager(): ComponentsLayoutManager
@Subcomponent.Factory
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
index 7817630..f785eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
@@ -16,10 +16,11 @@
package com.android.systemui.volume.panel.domain
-import com.android.systemui.volume.panel.VolumePanelComponentKey
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractorImpl
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -47,6 +48,10 @@
*/
@Provides
@VolumePanelScope
- fun provideEnabledComponents(): Collection<VolumePanelComponentKey> = setOf()
+ fun provideEnabledComponents(): Collection<VolumePanelComponentKey> {
+ return setOf(
+ VolumePanelComponents.BOTTOM_BAR,
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt
index e5b52ea..5301b00 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt
@@ -16,10 +16,10 @@
package com.android.systemui.volume.panel.domain.interactor
-import com.android.systemui.volume.panel.VolumePanelComponentKey
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import com.android.systemui.volume.panel.domain.model.ComponentModel
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/model/ComponentModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/model/ComponentModel.kt
index 9765713..11a9916 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/model/ComponentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/model/ComponentModel.kt
@@ -16,7 +16,7 @@
package com.android.systemui.volume.panel.domain.model
-import com.android.systemui.volume.panel.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
/**
* Represents a current state of the Volume Panel component.
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt
new file mode 100644
index 0000000..d90a9c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.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.volume.panel.shared.flag
+
+import com.android.systemui.Flags
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.flags.RefactorFlagUtils
+import javax.inject.Inject
+
+/** Provides a flag to check for the new Compose based Volume Panel availability. */
+class VolumePanelFlag @Inject constructor() {
+
+ /**
+ * Returns true when the new Volume Panel is available and false the otherwise. The new panel
+ * can only be available when [ComposeFacade.isComposeAvailable] is true.
+ */
+ fun canUseNewVolumePanel(): Boolean {
+ return ComposeFacade.isComposeAvailable() && Flags.newVolumePanel()
+ }
+
+ fun assertNewVolumePanel() {
+ require(ComposeFacade.isComposeAvailable())
+ RefactorFlagUtils.assertInNewMode(Flags.newVolumePanel(), Flags.FLAG_NEW_VOLUME_PANEL)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelComponentKey.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
rename to packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelComponentKey.kt
index 22a74d2..4644ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelComponentKey.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.volume.panel.shared.model
/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
typealias VolumePanelComponentKey = String
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponent.kt
new file mode 100644
index 0000000..24de41f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponent.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.shared.model
+
+/**
+ * An element of a Volume Panel. This can be a button bar, group of sliders or something else. The
+ * only real implementation is Compose-based and located in `compose/features/`.
+ *
+ * Steps for adding an implementation in SystemUI:
+ * 1) Implement `ComposeVolumePanelUiComponent` in `compose/features/`
+ * 2) Add a module binding `ComposeVolumePanelUiComponent` into a map in compose/facade/enabled
+ * 3) Add an interface with the same name as the 2-step module in compose/facade/disabled to stub it
+ * when the Compose is disabled
+ * 4) Add the module to the VolumePanelComponent
+ */
+interface VolumePanelUiComponent
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
index bfa7ef2..1346c54 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
@@ -16,8 +16,8 @@
package com.android.systemui.volume.panel.ui
-import com.android.systemui.volume.panel.ui.viewmodel.ComponentsLayoutManager
-import com.android.systemui.volume.panel.ui.viewmodel.DefaultComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
import dagger.Binds
import dagger.Module
@@ -25,5 +25,6 @@
@Module
interface UiModule {
- @Binds fun bindSorter(impl: DefaultComponentsLayoutManager): ComponentsLayoutManager
+ @Binds
+ fun bindComponentsLayoutManager(impl: DefaultComponentsLayoutManager): ComponentsLayoutManager
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
new file mode 100644
index 0000000..1b2265b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.activity
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.viewModels
+import androidx.core.view.WindowCompat
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import javax.inject.Inject
+import javax.inject.Provider
+
+class VolumePanelActivity
+@Inject
+constructor(
+ private val volumePanelViewModelFactory: Provider<VolumePanelViewModel.Factory>,
+ private val volumePanelFlag: VolumePanelFlag,
+) : ComponentActivity() {
+
+ private val viewModel: VolumePanelViewModel by
+ viewModels(factoryProducer = { volumePanelViewModelFactory.get() })
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
+ super.onCreate(savedInstanceState)
+
+ volumePanelFlag.assertNewVolumePanel()
+
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+ ComposeFacade.setVolumePanelActivityContent(this, viewModel) { finish() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactory.kt
new file mode 100644
index 0000000..db1c121
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactory.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.composable
+
+import com.android.systemui.volume.panel.dagger.VolumePanelComponent
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import javax.inject.Inject
+import javax.inject.Provider
+
+/** Provides [VolumePanelComponent] implementation for each [VolumePanelComponentKey]. */
+@VolumePanelScope
+class ComponentsFactory
+@Inject
+constructor(
+ private val componentByKey:
+ Map<
+ VolumePanelComponentKey,
+ @JvmSuppressWildcards
+ Provider<@JvmSuppressWildcards VolumePanelUiComponent>
+ >
+) {
+
+ fun createComponent(key: VolumePanelComponentKey): VolumePanelUiComponent {
+ require(componentByKey.containsKey(key)) { "Component for key=$key is not bound." }
+ return componentByKey.getValue(key).get()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentsLayout.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentsLayout.kt
rename to packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
index 5690ac3..25a95d8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentsLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel.ui.model
+package com.android.systemui.volume.panel.ui.layout
+
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
/** Represents components grouping into the layout. */
data class ComponentsLayout(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManager.kt
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentsLayoutManager.kt
rename to packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManager.kt
index f45401a..71ca95c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentsLayoutManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel.ui.viewmodel
+package com.android.systemui.volume.panel.ui.layout
-import com.android.systemui.volume.panel.ui.model.ComponentState
-import com.android.systemui.volume.panel.ui.model.ComponentsLayout
-import com.android.systemui.volume.panel.ui.model.VolumePanelState
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
/**
* Lays out components to [ComponentsLayout], that UI uses to render the Volume Panel.
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
new file mode 100644
index 0000000..ff485c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.layout
+
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
+import javax.inject.Inject
+
+@VolumePanelScope
+class DefaultComponentsLayoutManager @Inject constructor() : ComponentsLayoutManager {
+
+ override fun layout(
+ volumePanelState: VolumePanelState,
+ components: Collection<ComponentState>
+ ): ComponentsLayout {
+ val bottomBarKey = VolumePanelComponents.BOTTOM_BAR
+ return ComponentsLayout(
+ components.filter { it.key != bottomBarKey }.sortedBy { it.key },
+ components.find { it.key == bottomBarKey }
+ ?: error(
+ "VolumePanelComponents.BOTTOM_BAR must be present in the default " +
+ "components layout."
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt
similarity index 71%
rename from packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentState.kt
rename to packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt
index 0a226e2..5f4dbfb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel.ui.model
+package com.android.systemui.volume.panel.ui.viewmodel
-import com.android.systemui.volume.panel.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
/**
- * State of the [VolumePanelComponent].
+ * State of the [VolumePanelComponent]. It has everything the UI layer needs to layout a particular
+ * component.
*
* @property key uniquely identifies this component
* @property component is an inflated component obtained be the View Model
@@ -27,5 +29,6 @@
*/
data class ComponentState(
val key: VolumePanelComponentKey,
+ val component: VolumePanelUiComponent,
val isVisible: Boolean,
)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManager.kt
deleted file mode 100644
index cedfaf3..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManager.kt
+++ /dev/null
@@ -1,37 +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.volume.panel.ui.viewmodel
-
-import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
-import com.android.systemui.volume.panel.ui.model.ComponentState
-import com.android.systemui.volume.panel.ui.model.ComponentsLayout
-import com.android.systemui.volume.panel.ui.model.VolumePanelState
-import javax.inject.Inject
-
-/**
- * Default [ComponentsLayoutManager]. It places [VolumePanelComponents.BOTTOM_BAR] to
- * [ComponentsLayout.bottomBarComponent] and everything else to
- * [ComponentsLayout.contentComponents].
- */
-@VolumePanelScope
-class DefaultComponentsLayoutManager @Inject constructor() : ComponentsLayoutManager {
-
- override fun layout(
- volumePanelState: VolumePanelState,
- components: Collection<ComponentState>
- ): ComponentsLayout = TODO("Unimplemented yet")
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/VolumePanelState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/VolumePanelState.kt
rename to packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt
index 399342f..f67db96 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/VolumePanelState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel.ui.model
+package com.android.systemui.volume.panel.ui.viewmodel
import android.content.res.Configuration
import android.content.res.Configuration.Orientation
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
index dda361a..d87a79e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
@@ -26,9 +26,9 @@
import com.android.systemui.volume.panel.dagger.VolumePanelComponent
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
-import com.android.systemui.volume.panel.ui.model.ComponentState
-import com.android.systemui.volume.panel.ui.model.ComponentsLayout
-import com.android.systemui.volume.panel.ui.model.VolumePanelState
+import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
@@ -38,9 +38,10 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.update
class VolumePanelViewModel(
resources: Resources,
@@ -56,6 +57,9 @@
private val componentsInteractor: ComponentsInteractor
get() = volumePanelComponent.componentsInteractor()
+ private val componentsFactory: ComponentsFactory
+ get() = volumePanelComponent.componentsFactory()
+
private val componentsLayoutManager: ComponentsLayoutManager
get() = volumePanelComponent.componentsLayoutManager()
@@ -63,20 +67,22 @@
val volumePanelState: StateFlow<VolumePanelState> =
combine(
- configurationController.onConfigChanged.distinctUntilChanged(),
+ configurationController.onConfigChanged
+ .onStart { emit(resources.configuration) }
+ .distinctUntilChanged(),
mutablePanelVisibility,
) { configuration, isVisible ->
VolumePanelState(orientation = configuration.orientation, isVisible = isVisible)
}
.stateIn(
- volumePanelComponent.coroutineScope(),
+ scope,
SharingStarted.Eagerly,
VolumePanelState(
orientation = resources.configuration.orientation,
isVisible = mutablePanelVisibility.value,
),
)
- val mComponentsLayout: Flow<ComponentsLayout> =
+ val componentsLayout: Flow<ComponentsLayout> =
combine(
componentsInteractor.components,
volumePanelState,
@@ -85,19 +91,20 @@
components.map { model ->
ComponentState(
model.key,
+ componentsFactory.createComponent(model.key),
model.isAvailable,
)
}
componentsLayoutManager.layout(scope, componentStates)
}
.shareIn(
- volumePanelComponent.coroutineScope(),
+ scope,
SharingStarted.Eagerly,
replay = 1,
)
fun dismissPanel() {
- scope.launch { mutablePanelVisibility.emit(false) }
+ mutablePanelVisibility.update { false }
}
override fun onCleared() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
new file mode 100644
index 0000000..790638c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.ui.navigation
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.volume.VolumePanelFactory
+import com.android.systemui.volume.domain.model.VolumePanelRoute
+import com.android.systemui.volume.panel.ui.activity.VolumePanelActivity
+import javax.inject.Inject
+
+class VolumeNavigator
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val volumePanelFactory: VolumePanelFactory,
+ private val activityStarter: ActivityStarter,
+) {
+
+ fun openVolumePanel(route: VolumePanelRoute) {
+ when (route) {
+ VolumePanelRoute.COMPOSE_VOLUME_PANEL ->
+ activityStarter.startActivityDismissingKeyguard(
+ /* intent = */ Intent(context, VolumePanelActivity::class.java),
+ /* onlyProvisioned = */ false,
+ /* dismissShade= */ true,
+ /* disallowEnterPictureInPictureWhileLaunching = */ true,
+ /* callback= */ null,
+ /* flags= */ 0,
+ /* animationController= */ null,
+ /* userHandle= */ null,
+ )
+ VolumePanelRoute.SETTINGS_VOLUME_PANEL ->
+ activityStarter.startActivity(
+ /* intent= */ Intent(Settings.Panel.ACTION_VOLUME),
+ /* dismissShade= */ true
+ )
+ VolumePanelRoute.SYSTEM_UI_VOLUME_PANEL ->
+ volumePanelFactory.create(aboveStatusBar = true, view = null)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d8eb05a..d06457b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -1720,6 +1720,24 @@
}
@Test
+ public void assistantVisible_sendEventToFaceAuthInteractor() {
+ // WHEN the assistant is visible
+ mKeyguardUpdateMonitor.setAssistantVisible(true);
+
+ // THEN send event to face auth interactor
+ verify(mFaceAuthInteractor).onAssistantTriggeredOnLockScreen();
+ }
+
+ @Test
+ public void assistantNotVisible_doesNotSendEventToFaceAuthInteractor() {
+ // WHEN the assistant is visible
+ mKeyguardUpdateMonitor.setAssistantVisible(false);
+
+ // THEN never send event to face auth interactor
+ verify(mFaceAuthInteractor, never()).onAssistantTriggeredOnLockScreen();
+ }
+
+ @Test
public void fingerprintFailure_requestActiveUnlock_dismissKeyguard() {
// GIVEN shouldTriggerActiveUnlock
bouncerFullyVisible();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 54dbd04..3603c3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -63,14 +63,20 @@
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
import com.android.systemui.log.SideFpsLogger
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+import com.android.systemui.statusbar.phone.dozeServiceHost
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
@@ -107,6 +113,8 @@
@RunWith(JUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class SideFpsOverlayViewBinderTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var activityTaskManager: ActivityTaskManager
@Mock private lateinit var displayManager: DisplayManager
@@ -237,7 +245,8 @@
windowManager,
displayStateInteractor,
Optional.of(fingerprintInteractiveToAuthProvider),
- mock(),
+ kosmos.biometricSettingsRepository,
+ kosmos.keyguardTransitionInteractor,
SideFpsLogger(logcatLogBuffer("SfpsLogger"))
)
@@ -246,10 +255,12 @@
mContext,
mock(),
sfpsSensorInteractor,
- mock(),
+ kosmos.dozeServiceHost,
+ kosmos.keyguardInteractor,
displayStateInteractor,
UnconfinedTestDispatcher(),
testScope.backgroundScope,
+ kosmos.powerInteractor,
)
viewModel =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 1fa60fc..3c43031 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -56,19 +56,27 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.log.SideFpsLogger
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+import com.android.systemui.statusbar.phone.dozeServiceHost
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
@@ -98,6 +106,7 @@
@SmallTest
@RunWith(JUnit4::class)
class SideFpsOverlayViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
@JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var activityTaskManager: ActivityTaskManager
@@ -239,19 +248,22 @@
windowManager,
displayStateInteractor,
Optional.of(fingerprintInteractiveToAuthProvider),
- mock(),
+ kosmos.biometricSettingsRepository,
+ kosmos.keyguardTransitionInteractor,
SideFpsLogger(logcatLogBuffer("SfpsLogger"))
)
sideFpsProgressBarViewModel =
SideFpsProgressBarViewModel(
mContext,
- mock(),
+ kosmos.deviceEntryFingerprintAuthInteractor,
sfpsSensorInteractor,
- mock(),
+ kosmos.dozeServiceHost,
+ kosmos.keyguardInteractor,
displayStateInteractor,
- StandardTestDispatcher(),
+ kosmos.testDispatcher,
testScope.backgroundScope,
+ kosmos.powerInteractor,
)
underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
new file mode 100644
index 0000000..df73cc8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.keyboard.stickykeys.ui
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.keyboard.data.repository.FakeStickyKeysRepository
+import com.android.systemui.keyboard.data.repository.keyboardRepository
+import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
+import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class StickyKeysIndicatorCoordinatorTest : SysuiTestCase() {
+
+ private lateinit var coordinator: StickyKeysIndicatorCoordinator
+ private val testScope = TestScope(StandardTestDispatcher())
+ private val stickyKeysRepository = FakeStickyKeysRepository()
+ private val dialog = mock<ComponentSystemUIDialog>()
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(ComposeFacade.isComposeAvailable())
+ val dialogFactory = mock<SystemUIDialogFactory> {
+ whenever(applicationContext).thenReturn(context)
+ whenever(create(any(), anyInt(), anyBoolean())).thenReturn(dialog)
+ }
+ val keyboardRepository = Kosmos().keyboardRepository
+ val viewModel = StickyKeysIndicatorViewModel(
+ stickyKeysRepository,
+ keyboardRepository,
+ testScope.backgroundScope)
+ coordinator = StickyKeysIndicatorCoordinator(
+ testScope.backgroundScope,
+ dialogFactory,
+ viewModel,
+ mock<StickyKeysLogger>())
+ coordinator.startListening()
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ }
+
+ @Test
+ fun dialogIsShownWhenStickyKeysAreEmitted() {
+ testScope.run {
+ verifyZeroInteractions(dialog)
+
+ stickyKeysRepository.setStickyKeys(linkedMapOf(SHIFT to Locked(true)))
+ runCurrent()
+
+ verify(dialog).show()
+ }
+ }
+
+ @Test
+ fun dialogDisappearsWhenStickyKeysAreEmpty() {
+ testScope.run {
+ verifyZeroInteractions(dialog)
+
+ stickyKeysRepository.setStickyKeys(linkedMapOf(SHIFT to Locked(true)))
+ runCurrent()
+ stickyKeysRepository.setStickyKeys(linkedMapOf())
+ runCurrent()
+
+ verify(dialog).dismiss()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
index d397fc2..8a71368 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -46,6 +47,7 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 754a7fd..8a3a434 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -309,6 +309,28 @@
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testRaceCondition_doNotRegisterCentralSurfacesImmediately() {
+ create(false);
+
+ // GIVEN central surfaces is not registered with KeyguardViewMediator, but a call to enable
+ // keyguard comes in
+ mViewMediator.onSystemReady();
+ mViewMediator.setKeyguardEnabled(true);
+ TestableLooper.get(this).processAllMessages();
+
+ // If this step has been reached, then system ui has not crashed. Now register
+ // CentralSurfaces
+ assertFalse(mViewMediator.isShowingAndNotOccluded());
+ register();
+ TestableLooper.get(this).moveTimeForward(100);
+ TestableLooper.get(this).processAllMessages();
+
+ // THEN keyguard is shown
+ assertTrue(mViewMediator.isShowingAndNotOccluded());
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
public void onLockdown_showKeyguard_evenIfKeyguardIsNotEnabledExternally() {
// GIVEN keyguard is not enabled and isn't showing
mViewMediator.onSystemReady();
@@ -1139,6 +1161,11 @@
}
private void createAndStartViewMediator(boolean orderUnlockAndWake) {
+ create(orderUnlockAndWake);
+ register();
+ }
+
+ private void create(boolean orderUnlockAndWake) {
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_orderUnlockAndWake, orderUnlockAndWake);
@@ -1189,7 +1216,9 @@
mSelectedUserInteractor,
mKeyguardInteractor);
mViewMediator.start();
+ }
+ private void register() {
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index 809947d..6092b6b35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -66,6 +66,7 @@
bgDispatcher = super.testDispatcher,
mainDispatcher = super.testDispatcher,
keyguardInteractor = super.keyguardInteractor,
+ communalInteractor = super.communalInteractor,
flags = FakeFeatureFlags(),
keyguardSecurityModel = mock(),
powerInteractor = PowerInteractorFactory.create().powerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
index 339fd22..a03aed0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
@@ -17,14 +17,18 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import dagger.Lazy
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
open class KeyguardTransitionInteractorTestCase : SysuiTestCase() {
+ private val kosmos = testKosmos()
val testDispatcher = StandardTestDispatcher()
var testScope = TestScope(testDispatcher)
@@ -32,6 +36,7 @@
lateinit var transitionRepository: FakeKeyguardTransitionRepository
lateinit var keyguardInteractor: KeyguardInteractor
+ lateinit var communalInteractor: CommunalInteractor
lateinit var transitionInteractor: KeyguardTransitionInteractor
/**
@@ -51,6 +56,8 @@
keyguardInteractor =
KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor
+ communalInteractor = kosmos.communalInteractor
+
transitionInteractor =
KeyguardTransitionInteractorFactory.create(
repository = transitionRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index dafd9e6..4f3a63d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -156,10 +156,11 @@
val glanceableHubTransitions =
GlanceableHubTransitions(
- testScope,
- transitionInteractor,
- transitionRepository,
- communalInteractor
+ scope = testScope,
+ bgDispatcher = kosmos.testDispatcher,
+ transitionInteractor = transitionInteractor,
+ transitionRepository = transitionRepository,
+ communalInteractor = communalInteractor
)
fromLockscreenTransitionInteractor =
FromLockscreenTransitionInteractor(
@@ -196,6 +197,7 @@
flags = featureFlags,
keyguardSecurityModel = keyguardSecurityModel,
powerInteractor = powerInteractor,
+ communalInteractor = communalInteractor,
selectedUserInteractor = mSelectedUserInteractor,
)
.apply { start() }
@@ -242,6 +244,7 @@
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
powerInteractor = powerInteractor,
+ communalInteractor = communalInteractor,
)
.apply { start() }
@@ -267,6 +270,7 @@
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
powerInteractor = powerInteractor,
+ communalInteractor = communalInteractor,
)
.apply { start() }
@@ -278,6 +282,7 @@
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
+ communalInteractor = communalInteractor,
powerInteractor = powerInteractor,
)
.apply { start() }
@@ -288,6 +293,7 @@
bgDispatcher = kosmos.testDispatcher,
mainDispatcher = kosmos.testDispatcher,
glanceableHubTransitions = glanceableHubTransitions,
+ keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
powerInteractor = powerInteractor,
@@ -902,6 +908,37 @@
}
@Test
+ fun goneToGlanceableHub() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to GONE
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+
+ // GIVEN the device is idle on the glanceable hub
+ val idleTransitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+ )
+ communalInteractor.setTransitionState(idleTransitionState)
+ runCurrent()
+
+ // WHEN the keyguard starts to show
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DOZING should occur
+ assertThat(info.ownerName).isEqualTo(FromGoneTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.GONE)
+ assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun alternateBouncerToPrimaryBouncer() =
testScope.runTest {
// GIVEN a prior transition has run to ALTERNATE_BOUNCER
@@ -1023,6 +1060,45 @@
}
@Test
+ fun alternateBouncerToGlanceableHub() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to ALTERNATE_BOUNCER
+ bouncerRepository.setAlternateVisible(true)
+ runTransitionAndSetWakefulness(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.ALTERNATE_BOUNCER
+ )
+
+ // GIVEN the primary bouncer isn't showing and device not sleeping
+ bouncerRepository.setPrimaryShow(false)
+
+ // GIVEN the device is idle on the glanceable hub
+ val idleTransitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+ )
+ communalInteractor.setTransitionState(idleTransitionState)
+ runCurrent()
+
+ // WHEN the alternateBouncer stops showing
+ bouncerRepository.setAlternateVisible(false)
+ advanceUntilIdle()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to LOCKSCREEN should occur
+ assertThat(info.ownerName)
+ .isEqualTo(FromAlternateBouncerTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
+ assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun primaryBouncerToAod() =
testScope.runTest {
// GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -1085,7 +1161,7 @@
bouncerRepository.setPrimaryShow(true)
runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
- // WHEN the alternateBouncer stops showing
+ // WHEN the primaryBouncer stops showing
bouncerRepository.setPrimaryShow(false)
runCurrent()
@@ -1103,6 +1179,39 @@
}
@Test
+ fun primaryBouncerToGlanceableHub() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to PRIMARY_BOUNCER
+ bouncerRepository.setPrimaryShow(true)
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
+
+ // GIVEN the device is idle on the glanceable hub
+ val idleTransitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+ )
+ communalInteractor.setTransitionState(idleTransitionState)
+ runCurrent()
+
+ // WHEN the primaryBouncer stops showing
+ bouncerRepository.setPrimaryShow(false)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to LOCKSCREEN should occur
+ assertThat(info.ownerName)
+ .isEqualTo(FromPrimaryBouncerTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
+ assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun primaryBouncerToDreamingLockscreenHosted() =
testScope.runTest {
// GIVEN device dreaming with the lockscreen hosted dream and not dozing
@@ -1193,6 +1302,43 @@
}
@Test
+ fun occludedToGlanceableHub() =
+ testScope.runTest {
+ // GIVEN a device on lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // GIVEN the device is idle on the glanceable hub
+ val idleTransitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+ )
+ communalInteractor.setTransitionState(idleTransitionState)
+ runCurrent()
+
+ // GIVEN a prior transition has run to OCCLUDED
+ runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED)
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ // WHEN occlusion ends
+ keyguardRepository.setKeyguardOccluded(false)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to GLANCEABLE_HUB should occur
+ assertThat(info.ownerName).isEqualTo(FromOccludedTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun occludedToAlternateBouncer() =
testScope.runTest {
// GIVEN a prior transition has run to OCCLUDED
@@ -1640,6 +1786,111 @@
coroutineContext.cancelChildren()
}
+ @Test
+ fun glanceableHubToPrimaryBouncer() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to ALTERNATE_BOUNCER
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+
+ // WHEN the primary bouncer shows
+ bouncerRepository.setPrimaryShow(true)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to PRIMARY_BOUNCER should occur
+ assertThat(info.ownerName)
+ .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun glanceableHubToAlternateBouncer() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to ALTERNATE_BOUNCER
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+
+ // WHEN the primary bouncer shows
+ bouncerRepository.setAlternateVisible(true)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to PRIMARY_BOUNCER should occur
+ assertThat(info.ownerName)
+ .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.to).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun glanceableHubToOccluded() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to GLANCEABLE_HUB
+ runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.GLANCEABLE_HUB)
+ runCurrent()
+
+ // GIVEN the device is idle on the glanceable hub
+ val idleTransitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+ )
+ communalInteractor.setTransitionState(idleTransitionState)
+ runCurrent()
+
+ // WHEN the keyguard is occluded
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to OCCLUDED should occur
+ assertThat(info.ownerName)
+ .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun glanceableHubToGone() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to GLANCEABLE_HUB
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+
+ // WHEN keyguard goes away
+ keyguardRepository.setKeyguardGoingAway(true)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DOZING should occur
+ assertThat(info.ownerName)
+ .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.to).isEqualTo(KeyguardState.GONE)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
private fun createKeyguardInteractor(): KeyguardInteractor {
return KeyguardInteractorFactory.create(
featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index 57b5559..acb6ff0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -19,12 +19,15 @@
import android.content.pm.PackageManager
import android.content.res.Resources
+import android.view.View.GONE
+import android.view.View.VISIBLE
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.Utils
@@ -49,8 +52,11 @@
@Mock private lateinit var keyguardClockInteractor: KeyguardClockInteractor
@Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
@Mock private lateinit var splitShadeStateController: SplitShadeStateController
+ @Mock private lateinit var smartspaceViewModel: KeyguardSmartspaceViewModel
@Mock private lateinit var blueprintInteractor: Lazy<KeyguardBlueprintInteractor>
+ private val bcSmartspaceVisibility: MutableStateFlow<Int> = MutableStateFlow(VISIBLE)
private val clockShouldBeCentered: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ private val isAodIconsVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
private lateinit var underTest: ClockSection
@@ -110,6 +116,8 @@
mContext.setMockPackageManager(packageManager)
whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered)
+ whenever(keyguardClockViewModel.isAodIconsVisible).thenReturn(isAodIconsVisible)
+ whenever(smartspaceViewModel.bcSmartspaceVisibility).thenReturn(bcSmartspaceVisibility)
underTest =
ClockSection(
@@ -117,6 +125,7 @@
keyguardClockViewModel,
mContext,
splitShadeStateController,
+ smartspaceViewModel,
blueprintInteractor
)
}
@@ -176,6 +185,40 @@
assetSmallClockTop(cs, expectedSmallClockTopMargin)
}
+ @Test
+ fun testSmartspaceVisible_weatherClockDateAndIconsBarrierBottomBelowBCSmartspace() {
+ isAodIconsVisible.value = false
+ bcSmartspaceVisibility.value = VISIBLE
+ val cs = ConstraintSet()
+ underTest.applyDefaultConstraints(cs)
+ val referencedIds = cs.getReferencedIds(R.id.weather_clock_date_and_icons_barrier_bottom)
+ referencedIds.contentEquals(intArrayOf(com.android.systemui.shared.R.id.bc_smartspace_view))
+ }
+
+ @Test
+ fun testSmartspaceGone_weatherClockDateAndIconsBarrierBottomBelowSmartspaceDateWeather() {
+ isAodIconsVisible.value = false
+ bcSmartspaceVisibility.value = GONE
+ val cs = ConstraintSet()
+ underTest.applyDefaultConstraints(cs)
+ val referencedIds = cs.getReferencedIds(R.id.weather_clock_date_and_icons_barrier_bottom)
+ referencedIds.contentEquals(intArrayOf(R.id.lockscreen_clock_view))
+ }
+
+ @Test
+ fun testHasAodIcons_weatherClockDateAndIconsBarrierBottomBelowSmartspaceDateWeather() {
+ isAodIconsVisible.value = true
+ val cs = ConstraintSet()
+ underTest.applyDefaultConstraints(cs)
+ val referencedIds = cs.getReferencedIds(R.id.weather_clock_date_and_icons_barrier_bottom)
+ referencedIds.contentEquals(
+ intArrayOf(
+ com.android.systemui.shared.R.id.bc_smartspace_view,
+ R.id.aod_notification_icon_container
+ )
+ )
+ }
+
private fun setLargeClock(useLargeClock: Boolean) {
whenever(keyguardClockViewModel.useLargeClock).thenReturn(useLargeClock)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 1b4573d..22a2e93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -34,12 +34,14 @@
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.TestScope
@@ -68,6 +70,8 @@
@Mock private lateinit var clockFaceConfig: ClockFaceConfig
@Mock private lateinit var eventController: ClockEventController
@Mock private lateinit var splitShadeStateController: SplitShadeStateController
+ @Mock private lateinit var notifsKeyguardInteractor: NotificationsKeyguardInteractor
+ @Mock private lateinit var areNotificationsFullyHidden: Flow<Boolean>
@Before
fun setup() {
@@ -90,12 +94,15 @@
scope.backgroundScope
)
keyguardClockInteractor = KeyguardClockInteractor(keyguardClockRepository)
+ whenever(notifsKeyguardInteractor.areNotificationsFullyHidden)
+ .thenReturn(areNotificationsFullyHidden)
underTest =
KeyguardClockViewModel(
keyguardInteractor,
keyguardClockInteractor,
scope.backgroundScope,
splitShadeStateController,
+ notifsKeyguardInteractor
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index c8c134a..563a3fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
@@ -47,6 +48,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.FrameLayout;
import androidx.lifecycle.Lifecycle;
import androidx.test.filters.SmallTest;
@@ -63,6 +65,7 @@
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
@@ -111,7 +114,8 @@
@Mock private FooterActionsViewBinder mFooterActionsViewBinder;
@Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
@Mock private FeatureFlagsClassic mFeatureFlags;
- private View mQsView;
+ @Mock private SceneContainerFlags mSceneContainerFlags;
+ private ViewGroup mQsView;
private final CommandQueue mCommandQueue =
new CommandQueue(mContext, new FakeDisplayTracker(mContext));
@@ -121,6 +125,9 @@
@Before
public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mSceneContainerFlags.isEnabled()).thenReturn(false);
+
mUnderTest = instantiate();
mUnderTest.onComponentCreated(mQsComponent, null);
@@ -487,9 +494,24 @@
verify(mQSAnimator).setOnKeyguard(true);
}
- private QSImpl instantiate() {
- MockitoAnnotations.initMocks(this);
+ @Test
+ public void testSceneContainerFlagsEnabled_FooterActionsRemoved_controllerNotStarted() {
+ when(mSceneContainerFlags.isEnabled()).thenReturn(true);
+ clearInvocations(
+ mFooterActionsViewBinder, mFooterActionsViewModel, mFooterActionsViewModelFactory);
+ QSImpl other = instantiate();
+ other.onComponentCreated(mQsComponent, null);
+
+ assertThat((View) other.getView().findViewById(R.id.qs_footer_actions)).isNull();
+ verifyZeroInteractions(
+ mFooterActionsViewModel,
+ mFooterActionsViewBinder,
+ mFooterActionsViewModelFactory
+ );
+ }
+
+ private QSImpl instantiate() {
setupQsComponent();
setUpViews();
setUpInflater();
@@ -514,7 +536,8 @@
mFooterActionsViewModelFactory,
mFooterActionsViewBinder,
mLargeScreenShadeInterpolator,
- mFeatureFlags);
+ mFeatureFlags,
+ mSceneContainerFlags);
}
private void setUpOther() {
@@ -533,14 +556,23 @@
}
private void setUpViews() {
- mQsView = spy(new View(mContext));
+ mQsView = spy(new FrameLayout(mContext));
when(mQsComponent.getRootView()).thenReturn(mQsView);
- when(mQsView.findViewById(R.id.expanded_qs_scroll_view))
+
+ when(mQSPanelScrollView.findViewById(R.id.expanded_qs_scroll_view))
.thenReturn(mQSPanelScrollView);
- when(mQsView.findViewById(R.id.header)).thenReturn(mHeader);
- when(mQsView.findViewById(android.R.id.edit)).thenReturn(new View(mContext));
- when(mQsView.findViewById(R.id.qs_footer_actions)).thenAnswer(
- invocation -> new FooterActionsViewBinder().create(mContext));
+ mQsView.addView(mQSPanelScrollView);
+
+ when(mHeader.findViewById(R.id.header)).thenReturn(mHeader);
+ mQsView.addView(mHeader);
+
+ View customizer = new View(mContext);
+ customizer.setId(android.R.id.edit);
+ mQsView.addView(customizer);
+
+ View footerActionsView = new FooterActionsViewBinder().create(mContext);
+ footerActionsView.setId(R.id.qs_footer_actions);
+ mQsView.addView(footerActionsView);
}
private void setUpInflater() {
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 c1f5d85..45f6869 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -96,6 +96,10 @@
overrideResource(R.dimen.communal_right_edge_swipe_region_width, RIGHT_SWIPE_REGION_WIDTH)
overrideResource(R.dimen.communal_top_edge_swipe_region_height, TOP_SWIPE_REGION_WIDTH)
+ overrideResource(
+ R.dimen.communal_bottom_edge_swipe_region_height,
+ BOTTOM_SWIPE_REGION_WIDTH
+ )
}
@Test
@@ -180,6 +184,17 @@
}
@Test
+ fun onTouchEvent_bottomSwipeWhenHubOpen_returnsFalse() {
+ // Communal is open.
+ communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+
+ initAndAttachContainerView()
+
+ // Touch event in the bottom swipe reqgion is not intercepted.
+ assertThat(underTest.onTouchEvent(DOWN_IN_BOTTOM_SWIPE_REGION_EVENT)).isFalse()
+ }
+
+ @Test
fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() {
// Communal is open.
communalRepository.setDesiredScene(CommunalSceneKey.Communal)
@@ -227,6 +242,7 @@
private const val CONTAINER_HEIGHT = 100
private const val RIGHT_SWIPE_REGION_WIDTH = 20
private const val TOP_SWIPE_REGION_WIDTH = 20
+ private const val BOTTOM_SWIPE_REGION_WIDTH = 20
private val DOWN_EVENT =
MotionEvent.obtain(
@@ -248,6 +264,8 @@
TOP_SWIPE_REGION_WIDTH.toFloat(),
0
)
+ private val DOWN_IN_BOTTOM_SWIPE_REGION_EVENT =
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, CONTAINER_HEIGHT.toFloat(), 0)
private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
private val UP_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 200e758..461db8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -226,6 +226,7 @@
powerInteractor,
new GlanceableHubTransitions(
mTestScope,
+ mKosmos.getTestDispatcher(),
keyguardTransitionInteractor,
keyguardTransitionRepository,
communalInteractor
@@ -247,6 +248,7 @@
mKosmos.getTestDispatcher(),
mKosmos.getTestDispatcher(),
keyguardInteractor,
+ communalInteractor,
featureFlags,
mKeyguardSecurityModel,
mSelectedUserInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 6fc88ce..3e0a647 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -257,6 +257,7 @@
powerInteractor,
new GlanceableHubTransitions(
mTestScope,
+ mKosmos.getTestDispatcher(),
keyguardTransitionInteractor,
keyguardTransitionRepository,
communalInteractor
@@ -278,6 +279,7 @@
mKosmos.getTestDispatcher(),
mKosmos.getTestDispatcher(),
keyguardInteractor,
+ communalInteractor,
featureFlags,
mock(KeyguardSecurityModel.class),
mSelectedUserInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 13934da..8cb064d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -158,6 +158,7 @@
powerInteractor,
GlanceableHubTransitions(
testScope,
+ testDispatcher,
keyguardTransitionInteractor,
keyguardTransitionRepository,
communalInteractor
@@ -180,6 +181,7 @@
testDispatcher,
testDispatcher,
keyguardInteractor,
+ communalInteractor,
featureFlags,
mock(),
mock(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index d9eaea1..b3a47d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -48,7 +48,6 @@
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
-import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.MathUtils;
@@ -135,7 +134,7 @@
@Mock private AlarmManager mAlarmManager;
@Mock private DozeParameters mDozeParameters;
@Mock private LightBarController mLightBarController;
- @Mock private DelayedWakeLock.Builder mDelayedWakeLockBuilder;
+ @Mock private DelayedWakeLock.Factory mDelayedWakeLockFactory;
@Mock private DelayedWakeLock mWakeLock;
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -262,11 +261,7 @@
}).when(mLightBarController).setScrimState(
any(ScrimState.class), anyFloat(), any(GradientColors.class));
- when(mDelayedWakeLockBuilder.setHandler(any(Handler.class)))
- .thenReturn(mDelayedWakeLockBuilder);
- when(mDelayedWakeLockBuilder.setTag(any(String.class)))
- .thenReturn(mDelayedWakeLockBuilder);
- when(mDelayedWakeLockBuilder.build()).thenReturn(mWakeLock);
+ when(mDelayedWakeLockFactory.create(any(String.class))).thenReturn(mWakeLock);
when(mDockManager.isDocked()).thenReturn(false);
when(mKeyguardTransitionInteractor.transition(any(), any()))
@@ -281,7 +276,7 @@
mDozeParameters,
mAlarmManager,
mKeyguardStateController,
- mDelayedWakeLockBuilder,
+ mDelayedWakeLockFactory,
new FakeHandler(mLooper.getLooper()),
mKeyguardUpdateMonitor,
mDockManager,
@@ -990,7 +985,7 @@
mDozeParameters,
mAlarmManager,
mKeyguardStateController,
- mDelayedWakeLockBuilder,
+ mDelayedWakeLockFactory,
new FakeHandler(mLooper.getLooper()),
mKeyguardUpdateMonitor,
mDockManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 20d5c5d..49953a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+import android.platform.test.annotations.EnableFlags
import android.telephony.CellSignalStrength
import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
@@ -159,10 +160,13 @@
}
@Test
- fun numberOfLevels_comesFromRepo() =
+ fun numberOfLevels_comesFromRepo_whenApplicable() =
testScope.runTest {
var latest: Int? = null
- val job = underTest.signalLevelIcon.onEach { latest = it.numberOfLevels }.launchIn(this)
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = (it as? SignalIconModel.Cellular)?.numberOfLevels }
+ .launchIn(this)
connectionRepository.numberOfLevels.value = 5
assertThat(latest).isEqualTo(5)
@@ -491,14 +495,19 @@
}
@Test
- fun iconId_correctLevel_notCutout() =
+ fun cellBasedIconId_correctLevel_notCutout() =
testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
connectionRepository.isInService.value = true
connectionRepository.primaryLevel.value = 1
connectionRepository.setDataEnabled(false)
+ connectionRepository.isNonTerrestrial.value = false
- var latest: SignalIconModel? = null
- val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
assertThat(latest?.level).isEqualTo(1)
assertThat(latest?.showExclamationMark).isFalse()
@@ -509,6 +518,7 @@
@Test
fun icon_usesLevelFromInteractor() =
testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
connectionRepository.isInService.value = true
var latest: SignalIconModel? = null
@@ -524,10 +534,15 @@
}
@Test
- fun icon_usesNumberOfLevelsFromInteractor() =
+ fun cellBasedIcon_usesNumberOfLevelsFromInteractor() =
testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+ connectionRepository.isNonTerrestrial.value = false
+
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
connectionRepository.numberOfLevels.value = 5
assertThat(latest!!.numberOfLevels).isEqualTo(5)
@@ -539,12 +554,16 @@
}
@Test
- fun icon_defaultDataDisabled_showExclamationTrue() =
+ fun cellBasedIcon_defaultDataDisabled_showExclamationTrue() =
testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
- var latest: SignalIconModel? = null
- val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
assertThat(latest!!.showExclamationMark).isTrue()
@@ -552,12 +571,16 @@
}
@Test
- fun icon_defaultConnectionFailed_showExclamationTrue() =
+ fun cellBasedIcon_defaultConnectionFailed_showExclamationTrue() =
testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
mobileIconsInteractor.isDefaultConnectionFailed.value = true
- var latest: SignalIconModel? = null
- val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
assertThat(latest!!.showExclamationMark).isTrue()
@@ -565,14 +588,18 @@
}
@Test
- fun icon_enabledAndNotFailed_showExclamationFalse() =
+ fun cellBasedIcon_enabledAndNotFailed_showExclamationFalse() =
testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
connectionRepository.isInService.value = true
mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
mobileIconsInteractor.isDefaultConnectionFailed.value = false
- var latest: SignalIconModel? = null
- val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
assertThat(latest!!.showExclamationMark).isFalse()
@@ -580,11 +607,15 @@
}
@Test
- fun icon_usesEmptyState_whenNotInService() =
+ fun cellBasedIcon_usesEmptyState_whenNotInService() =
testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
+ connectionRepository.isNonTerrestrial.value = false
connectionRepository.isInService.value = false
assertThat(latest?.level).isEqualTo(0)
@@ -604,11 +635,15 @@
}
@Test
- fun icon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() =
+ fun cellBasedIcon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() =
testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular? }
+ .launchIn(this)
+ connectionRepository.isNonTerrestrial.value = false
connectionRepository.isInService.value = true
connectionRepository.carrierNetworkChangeActive.value = true
connectionRepository.primaryLevel.value = 1
@@ -626,6 +661,20 @@
job.cancel()
}
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @Test
+ fun satBasedIcon_isUsedWhenNonTerrestrial() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ // Start off using cellular
+ assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java)
+
+ connectionRepository.isNonTerrestrial.value = true
+
+ assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
+ }
+
private fun createInteractor(
overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
index 90a8946..ebec003 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
@@ -32,7 +32,7 @@
@Test
fun drawableFromModel_level0_numLevels4_noExclamation_notCarrierNetworkChange() {
val model =
- SignalIconModel(
+ SignalIconModel.Cellular(
level = 0,
numberOfLevels = 4,
showExclamationMark = false,
@@ -59,7 +59,7 @@
val expected: Int,
) {
fun toSignalIconModel() =
- SignalIconModel(
+ SignalIconModel.Cellular(
level = level,
numberOfLevels = numberOfLevels,
showExclamationMark = showExclamation,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index 889130f..deb9fcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -190,7 +190,7 @@
/** Convenience constructor for these tests */
fun defaultSignal(level: Int = 1): SignalIconModel {
- return SignalIconModel(
+ return SignalIconModel.Cellular(
level,
NUM_LEVELS,
showExclamationMark = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 147efcb..83d0fe8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -55,6 +55,7 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
@@ -709,6 +710,87 @@
.isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null))
}
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @Test
+ fun nonTerrestrial_defaultProperties() =
+ testScope.runTest {
+ repository.isNonTerrestrial.value = true
+
+ val roaming by collectLastValue(underTest.roaming)
+ val networkTypeIcon by collectLastValue(underTest.networkTypeIcon)
+ val networkTypeBackground by collectLastValue(underTest.networkTypeBackground)
+ val activityInVisible by collectLastValue(underTest.activityInVisible)
+ val activityOutVisible by collectLastValue(underTest.activityOutVisible)
+ val activityContainerVisible by collectLastValue(underTest.activityContainerVisible)
+
+ assertThat(roaming).isFalse()
+ assertThat(networkTypeIcon).isNull()
+ assertThat(networkTypeBackground).isNull()
+ assertThat(activityInVisible).isFalse()
+ assertThat(activityOutVisible).isFalse()
+ assertThat(activityContainerVisible).isFalse()
+ }
+
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @Test
+ fun nonTerrestrial_ignoresDefaultProperties() =
+ testScope.runTest {
+ repository.isNonTerrestrial.value = true
+
+ val roaming by collectLastValue(underTest.roaming)
+ val networkTypeIcon by collectLastValue(underTest.networkTypeIcon)
+ val networkTypeBackground by collectLastValue(underTest.networkTypeBackground)
+ val activityInVisible by collectLastValue(underTest.activityInVisible)
+ val activityOutVisible by collectLastValue(underTest.activityOutVisible)
+ val activityContainerVisible by collectLastValue(underTest.activityContainerVisible)
+
+ repository.setAllRoaming(true)
+ repository.setNetworkTypeKey(connectionsRepository.LTE_KEY)
+ // sets the background on cellular
+ repository.hasPrioritizedNetworkCapabilities.value = true
+ repository.dataActivityDirection.value =
+ DataActivityModel(
+ hasActivityIn = true,
+ hasActivityOut = true,
+ )
+
+ assertThat(roaming).isFalse()
+ assertThat(networkTypeIcon).isNull()
+ assertThat(networkTypeBackground).isNull()
+ assertThat(activityInVisible).isFalse()
+ assertThat(activityOutVisible).isFalse()
+ assertThat(activityContainerVisible).isFalse()
+ }
+
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @Test
+ fun nonTerrestrial_usesSatelliteIcon() =
+ testScope.runTest {
+ repository.isNonTerrestrial.value = true
+ repository.setAllLevels(0)
+
+ val latest by
+ collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
+
+ // Level 0 -> no connection
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+ // 1-2 -> 1 bar
+ repository.setAllLevels(1)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ repository.setAllLevels(2)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ // 3-4 -> 2 bars
+ repository.setAllLevels(3)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+ repository.setAllLevels(4)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+ }
+
private fun createAndSetViewModel() {
underTest =
MobileIconViewModel(
@@ -723,24 +805,5 @@
companion object {
private const val SUB_1_ID = 1
- private const val NUM_LEVELS = 4
-
- /** Convenience constructor for these tests */
- fun defaultSignal(level: Int = 1): SignalIconModel {
- return SignalIconModel(
- level,
- NUM_LEVELS,
- showExclamationMark = false,
- carrierNetworkChange = false,
- )
- }
-
- fun emptySignal(): SignalIconModel =
- SignalIconModel(
- level = 0,
- numberOfLevels = NUM_LEVELS,
- showExclamationMark = true,
- carrierNetworkChange = false,
- )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 8c823b2..7a8dce8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -32,6 +32,7 @@
import static org.junit.Assume.assumeNotNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -63,15 +64,16 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.keyguard.TestScopeProvider;
import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -79,6 +81,9 @@
import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
+import com.android.systemui.volume.ui.navigation.VolumeNavigator;
import dagger.Lazy;
@@ -97,6 +102,8 @@
import java.util.Arrays;
import java.util.function.Predicate;
+import kotlinx.coroutines.Dispatchers;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -126,10 +133,6 @@
@Mock
MediaOutputDialogFactory mMediaOutputDialogFactory;
@Mock
- VolumePanelFactory mVolumePanelFactory;
- @Mock
- ActivityStarter mActivityStarter;
- @Mock
InteractionJankMonitor mInteractionJankMonitor;
@Mock
private DumpManager mDumpManager;
@@ -138,6 +141,10 @@
DevicePostureController mPostureController;
@Mock
private Lazy<SecureSettings> mLazySecureSettings;
+ @Mock
+ private VolumePanelNavigationInteractor mVolumePanelNavigationInteractor;
+ @Mock
+ private VolumeNavigator mVolumeNavigator;
private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
new CsdWarningDialog.Factory() {
@@ -146,6 +153,8 @@
return mCsdWarningDialog;
}
};
+ @Mock
+ private VibratorHelper mVibratorHelper;
private int mLongestHideShowAnimationDuration = 250;
private FakeSettings mSecureSettings;
@@ -180,6 +189,8 @@
when(mLazySecureSettings.get()).thenReturn(mSecureSettings);
+ when(mVibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(new int[]{0});
+
mDialog = new VolumeDialogImpl(
getContext(),
mVolumeDialogController,
@@ -187,15 +198,19 @@
mDeviceProvisionedController,
mConfigurationController,
mMediaOutputDialogFactory,
- mVolumePanelFactory,
- mActivityStarter,
mInteractionJankMonitor,
+ mVolumePanelNavigationInteractor,
+ mVolumeNavigator,
false,
mCsdWarningDialogFactory,
mPostureController,
mTestableLooper.getLooper(),
mDumpManager,
- mLazySecureSettings);
+ mLazySecureSettings,
+ mVibratorHelper,
+ Dispatchers.getUnconfined(),
+ TestScopeProvider.getTestScope(),
+ new FakeSystemClock());
mDialog.init(0, null);
State state = createShellState();
mDialog.onStateChangedH(state);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 93d8dcc..98f3ede 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -456,6 +456,7 @@
powerInteractor,
new GlanceableHubTransitions(
mTestScope,
+ mKosmos.getTestDispatcher(),
keyguardTransitionInteractor,
keyguardTransitionRepository,
communalInteractor
@@ -477,6 +478,7 @@
mKosmos.getTestDispatcher(),
mKosmos.getTestDispatcher(),
keyguardInteractor,
+ communalInteractor,
featureFlags,
mock(KeyguardSecurityModel.class),
mSelectedUserInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/android/app/KeyguardManagerKosmos.kt
similarity index 76%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/android/app/KeyguardManagerKosmos.kt
index 22a74d2..e5121d5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/android/app/KeyguardManagerKosmos.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package android.app
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.keyguardManager by Kosmos.Fixture { mock<KeyguardManager>() }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/android/os/HandlerKosmos.kt
similarity index 69%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/android/os/HandlerKosmos.kt
index 22a74d2..4e2683b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/android/os/HandlerKosmos.kt
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package android.os
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.concurrency.mockExecutorHandler
+
+val Kosmos.fakeExecutorHandler by Kosmos.Fixture { mockExecutorHandler(fakeExecutor) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/android/service/dream/DreamManagerKosmos.kt
similarity index 72%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/android/service/dream/DreamManagerKosmos.kt
index 22a74d2..fb51f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/android/service/dream/DreamManagerKosmos.kt
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package android.service.dream
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import android.service.dreams.IDreamManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.dreamManager by Kosmos.Fixture { mock<IDreamManager>() }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
similarity index 69%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
index 22a74d2..2a05598 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package android.view
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.Mockito.mock
+
+val Kosmos.windowManager by Kosmos.Fixture<WindowManager> { mock(WindowManager::class.java) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
similarity index 75%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
index 22a74d2..d9ea5e9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.internal.widget
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.lockPatternUtils by Kosmos.Fixture { mock<LockPatternUtils>() }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/ActivityIntentHelperKosmos.kt
similarity index 74%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/ActivityIntentHelperKosmos.kt
index 22a74d2..7185b7c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/ActivityIntentHelperKosmos.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.activityIntentHelper by Kosmos.Fixture { ActivityIntentHelper(applicationContext) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
similarity index 77%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
index 22a74d2..128f58b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.animation
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.activityLaunchAnimator by Kosmos.Fixture { ActivityLaunchAnimator() }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt
similarity index 75%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt
index 22a74d2..b7d6f3a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.assist
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.assistManager by Kosmos.Fixture { mock<AssistManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FakeFingerprintInteractiveToAuthProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FakeFingerprintInteractiveToAuthProvider.kt
new file mode 100644
index 0000000..8fcb60c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FakeFingerprintInteractiveToAuthProvider.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.systemui.biometrics
+
+import android.hardware.biometrics.common.AuthenticateReason
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeFingerprintInteractiveToAuthProvider : FingerprintInteractiveToAuthProvider {
+ override val enabledForCurrentUser: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ private val userIdToExtension = mutableMapOf<Int, AuthenticateReason.Vendor>()
+ override fun getVendorExtension(userId: Int): AuthenticateReason.Vendor? =
+ userIdToExtension[userId]
+
+ fun setVendorExtension(userId: Int, extension: AuthenticateReason.Vendor) {
+ userIdToExtension[userId] = extension
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProviderKosmos.kt
new file mode 100644
index 0000000..57dc37e3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProviderKosmos.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.biometrics
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fingerprintInteractiveToAuthProvider by
+ Kosmos.Fixture { fakeFingerprintInteractiveToAuthProvider }
+
+val Kosmos.fakeFingerprintInteractiveToAuthProvider by
+ Kosmos.Fixture { FakeFingerprintInteractiveToAuthProvider() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt
index 8702e00..b5515c4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt
@@ -19,4 +19,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.fingerprintPropertyRepository by Fixture { FakeFingerprintPropertyRepository() }
+val Kosmos.fingerprintPropertyRepository by Fixture { fakeFingerprintPropertyRepository }
+
+val Kosmos.fakeFingerprintPropertyRepository by Fixture { FakeFingerprintPropertyRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorKosmos.kt
new file mode 100644
index 0000000..979a49b7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.content.applicationContext
+import android.view.windowManager
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.fingerprintInteractiveToAuthProvider
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.SideFpsLogger
+import java.util.Optional
+import org.mockito.Mockito.mock
+
+val Kosmos.sideFpsSensorInteractor by
+ Kosmos.Fixture {
+ SideFpsSensorInteractor(
+ applicationContext,
+ fingerprintPropertyRepository,
+ windowManager,
+ displayStateInteractor,
+ Optional.of(fingerprintInteractiveToAuthProvider),
+ biometricSettingsRepository,
+ keyguardTransitionInteractor,
+ mock(SideFpsLogger::class.java),
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeStickyKeysRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeStickyKeysRepository.kt
new file mode 100644
index 0000000..68e1457
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeStickyKeysRepository.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.keyboard.data.repository
+
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeStickyKeysRepository : StickyKeysRepository {
+ override val settingEnabled: Flow<Boolean> = MutableStateFlow(true)
+ private val _stickyKeys: MutableStateFlow<LinkedHashMap<ModifierKey, Locked>> =
+ MutableStateFlow(LinkedHashMap())
+
+ override val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>> = _stickyKeys
+
+ fun setStickyKeys(keys: LinkedHashMap<ModifierKey, Locked>) {
+ _stickyKeys.value = keys
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryKosmos.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryKosmos.kt
index 22a74d2..46f7355 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.keyboard.data.repository
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.keyboardRepository by Kosmos.Fixture { FakeKeyboardRepository() }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt
index 45d39b0..cf8f812 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt
@@ -19,4 +19,5 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.biometricSettingsRepository by Fixture { FakeBiometricSettingsRepository() }
+val Kosmos.fakeBiometricSettingsRepository by Fixture { FakeBiometricSettingsRepository() }
+val Kosmos.biometricSettingsRepository by Fixture { fakeBiometricSettingsRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt
index 6437ef3..0d20939 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt
@@ -19,6 +19,10 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.deviceEntryFingerprintAuthRepository by Fixture {
+val Kosmos.fakeDeviceEntryFingerprintAuthRepository by Fixture {
FakeDeviceEntryFingerprintAuthRepository()
}
+
+val Kosmos.deviceEntryFingerprintAuthRepository by Fixture {
+ fakeDeviceEntryFingerprintAuthRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index 97536e2..719686e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.keyguard.keyguardSecurityModel
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
@@ -34,6 +35,7 @@
bgDispatcher = testDispatcher,
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
+ communalInteractor = communalInteractor,
flags = featureFlagsClassic,
keyguardSecurityModel = keyguardSecurityModel,
selectedUserInteractor = selectedUserInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
index ec17c48..55885bf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
@@ -20,11 +20,13 @@
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
val Kosmos.glanceableHubTransitions by
Kosmos.Fixture {
GlanceableHubTransitions(
scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
transitionRepository = keyguardTransitionRepository,
transitionInteractor = keyguardTransitionInteractor,
communalInteractor = communalInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
index 5ca0439..4a85909 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
@@ -20,6 +20,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.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.policy.splitShadeStateController
val Kosmos.keyguardClockViewModel by
@@ -29,5 +30,6 @@
keyguardClockInteractor = keyguardClockInteractor,
applicationScope = applicationCoroutineScope,
splitShadeStateController = splitShadeStateController,
+ notifsKeyguardInteractor = notificationsKeyguardInteractor,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
similarity index 75%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index 22a74d2..7cc5d6b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.shade
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.shadeController by Kosmos.Fixture { mock<ShadeController>() }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
similarity index 74%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
index 22a74d2..1ceab68 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.shade
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.shadeViewController by Kosmos.Fixture { mock<ShadeViewController>() }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt
similarity index 76%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt
index 22a74d2..4dcd220 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.shade.data.repository
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.shadeAnimationRepository by Kosmos.Fixture { ShadeAnimationRepository() }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
similarity index 65%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
index 22a74d2..57b272f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.shade.domain.interactor
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shade.data.repository.shadeAnimationRepository
+
+var Kosmos.shadeAnimationInteractor: ShadeAnimationInteractor by
+ Kosmos.Fixture { ShadeAnimationInteractorEmptyImpl(shadeAnimationRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryKosmos.kt
new file mode 100644
index 0000000..a75d2bc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryKosmos.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.shared.notifications.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
+
+val Kosmos.notificationSettingsRepository by
+ Kosmos.Fixture {
+ NotificationSettingsRepository(
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
+ secureSettingsRepository = secureSettingsRepository,
+ )
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractorKosmos.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractorKosmos.kt
index 22a74d2..17b4603 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractorKosmos.kt
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.shared.notifications.domain.interactor
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
+
+val Kosmos.notificationSettingsInteractor by
+ Kosmos.Fixture { NotificationSettingsInteractor(notificationSettingsRepository) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryKosmos.kt
similarity index 65%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryKosmos.kt
index 22a74d2..552b09e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryKosmos.kt
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.shared.settings.data.repository
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.secureSettingsRepository: SecureSettingsRepository by
+ Kosmos.Fixture { fakeSecureSettingsRepository }
+val Kosmos.fakeSecureSettingsRepository by Kosmos.Fixture { FakeSecureSettingsRepository() }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationClickNotifierKosmos.kt
similarity index 73%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationClickNotifierKosmos.kt
index 22a74d2..7b912ae 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationClickNotifierKosmos.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.statusbar
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.notificationClickNotifier by Kosmos.Fixture { mock<NotificationClickNotifier>() }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationPresenterKosmos.kt
similarity index 73%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationPresenterKosmos.kt
index 22a74d2..8d30049 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationPresenterKosmos.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.statusbar
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.notificationPresenter by Kosmos.Fixture { mock<NotificationPresenter>() }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt
similarity index 71%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt
index 22a74d2..554bdbe 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.statusbar
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.notificationRemoteInputManager by
+ Kosmos.Fixture { mock<NotificationRemoteInputManager>() }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationShadeWindowControllerKosmos.kt
similarity index 71%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationShadeWindowControllerKosmos.kt
index 22a74d2..e8ca3b8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationShadeWindowControllerKosmos.kt
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.statusbar
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.notificationShadeWindowController by
+ Kosmos.Fixture { mock<NotificationShadeWindowController>() }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt
index 22a74d2..c337ac2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.statusbar.notification
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.phone.statusBarNotificationActivityStarter
+
+var Kosmos.notificationActivityStarter: NotificationActivityStarter by
+ Kosmos.Fixture { statusBarNotificationActivityStarter }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.kt
similarity index 68%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.kt
index 22a74d2..c3db34b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.kt
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.statusbar.notification
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.notificationLaunchAnimatorControllerProvider by
+ Kosmos.Fixture { mock<NotificationLaunchAnimatorControllerProvider>() }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreKosmos.kt
similarity index 68%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreKosmos.kt
index 22a74d2..1f45fbb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreKosmos.kt
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.statusbar.notification.collection
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.notifLiveDataStore: NotifLiveDataStore by
+ Kosmos.Fixture { NotifLiveDataStoreImpl(fakeExecutor) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt
similarity index 69%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt
index 22a74d2..358d251 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.statusbar.notification.collection.coordinator
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.visualStabilityCoordinator by Kosmos.Fixture { mock<VisualStabilityCoordinator>() }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProviderKosmos.kt
similarity index 73%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProviderKosmos.kt
index 22a74d2..a5c9561 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProviderKosmos.kt
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.statusbar.notification.collection.provider
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.launchFullScreenIntentProvider by Kosmos.Fixture { LaunchFullScreenIntentProvider() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderKosmos.kt
new file mode 100644
index 0000000..edce5d58
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.collection.render
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.collection.notifLiveDataStore
+import com.android.systemui.statusbar.notification.collection.notifPipeline
+import com.android.systemui.statusbar.notification.collection.provider.NotificationVisibilityProviderImpl
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+
+var Kosmos.notificationVisibilityProvider: NotificationVisibilityProvider by
+ Kosmos.Fixture {
+ NotificationVisibilityProviderImpl(
+ activeNotificationsInteractor,
+ notifLiveDataStore,
+ notifPipeline,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt
new file mode 100644
index 0000000..1e3897b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.row
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.statusbar.notification.collection.coordinator.visualStabilityCoordinator
+import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl
+import com.android.systemui.statusbar.notification.collection.notifCollection
+import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
+import com.android.systemui.statusbar.policy.headsUpManager
+
+var Kosmos.onUserInteractionCallback: OnUserInteractionCallback by
+ Kosmos.Fixture {
+ OnUserInteractionCallbackImpl(
+ notificationVisibilityProvider,
+ notifCollection,
+ headsUpManager,
+ statusBarStateController,
+ visualStabilityCoordinator,
+ )
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
index 22a74d2..370b177 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.statusbar.phone
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.mockito.Mockito.mock
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.dozeServiceHost: DozeServiceHost by Kosmos.Fixture { mock(DozeServiceHost::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
new file mode 100644
index 0000000..6ddc9df
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.phone
+
+import android.app.keyguardManager
+import android.content.applicationContext
+import android.os.fakeExecutorHandler
+import android.service.dream.dreamManager
+import com.android.internal.logging.metricsLogger
+import com.android.internal.widget.lockPatternUtils
+import com.android.systemui.activityIntentHelper
+import com.android.systemui.animation.activityLaunchAnimator
+import com.android.systemui.assist.assistManager
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.settings.userTracker
+import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor
+import com.android.systemui.shade.shadeController
+import com.android.systemui.shade.shadeViewController
+import com.android.systemui.statusbar.notification.collection.provider.launchFullScreenIntentProvider
+import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
+import com.android.systemui.statusbar.notification.notificationLaunchAnimatorControllerProvider
+import com.android.systemui.statusbar.notification.row.onUserInteractionCallback
+import com.android.systemui.statusbar.notificationClickNotifier
+import com.android.systemui.statusbar.notificationLockscreenUserManager
+import com.android.systemui.statusbar.notificationPresenter
+import com.android.systemui.statusbar.notificationRemoteInputManager
+import com.android.systemui.statusbar.notificationShadeWindowController
+import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.wmshell.bubblesManager
+import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.statusBarNotificationActivityStarter by
+ Kosmos.Fixture {
+ StatusBarNotificationActivityStarter(
+ applicationContext,
+ applicationContext.displayId,
+ fakeExecutorHandler,
+ fakeExecutor,
+ notificationVisibilityProvider,
+ headsUpManager,
+ activityStarter,
+ notificationClickNotifier,
+ statusBarKeyguardViewManager,
+ keyguardManager,
+ dreamManager,
+ Optional.of(bubblesManager),
+ { assistManager },
+ notificationRemoteInputManager,
+ notificationLockscreenUserManager,
+ shadeController,
+ keyguardStateController,
+ lockPatternUtils,
+ statusBarRemoteInputCallback,
+ activityIntentHelper,
+ metricsLogger,
+ statusBarNotificationActivityStarterLogger,
+ onUserInteractionCallback,
+ notificationPresenter,
+ shadeViewController,
+ notificationShadeWindowController,
+ activityLaunchAnimator,
+ shadeAnimationInteractor,
+ notificationLaunchAnimatorControllerProvider,
+ launchFullScreenIntentProvider,
+ powerInteractor,
+ userTracker,
+ )
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLoggerKosmos.kt
similarity index 68%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLoggerKosmos.kt
index 22a74d2..31cfc97 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLoggerKosmos.kt
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.statusbar.phone
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+
+val Kosmos.statusBarNotificationActivityStarterLogger by
+ Kosmos.Fixture { StatusBarNotificationActivityStarterLogger(logcatLogBuffer()) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackKosmos.kt
similarity index 72%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackKosmos.kt
index 22a74d2..475d7fa 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackKosmos.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.statusbar.phone
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.statusBarRemoteInputCallback by Kosmos.Fixture { mock<StatusBarRemoteInputCallback>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 5f4d7bf..c010327 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -60,6 +60,8 @@
override val isInService = MutableStateFlow(true)
+ override val isNonTerrestrial = MutableStateFlow(false)
+
private val _isDataEnabled = MutableStateFlow(true)
override val isDataEnabled = _isDataEnabled
@@ -69,7 +71,7 @@
override val signalLevelIcon: MutableStateFlow<SignalIconModel> =
MutableStateFlow(
- SignalIconModel(
+ SignalIconModel.Cellular(
level = 0,
numberOfLevels = 4,
showExclamationMark = false,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
similarity index 72%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
index 22a74d2..0e909c4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.statusbar.policy
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.keyguardStateController by Kosmos.Fixture { mock<KeyguardStateController>() }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wmshell/BubblesManagerKosmos.kt
similarity index 75%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/wmshell/BubblesManagerKosmos.kt
index 22a74d2..1d05d62 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wmshell/BubblesManagerKosmos.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.wmshell
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.bubblesManager by Kosmos.Fixture { mock<BubblesManager>() }
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources>
+ <!-- If true, attach the navigation bar to the app during app transition -->
+ <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources>
+ <!-- If true, attach the navigation bar to the app during app transition -->
+ <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources>
+ <!-- If true, attach the navigation bar to the app during app transition -->
+ <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources>
+ <!-- If true, attach the navigation bar to the app during app transition -->
+ <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 42ab05f..4d42f15 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -58,11 +58,13 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.view.autofill.AutofillManager;
import android.widget.ImageView;
import android.widget.RemoteViews;
+import android.widget.ScrollView;
import android.widget.TextView;
import com.android.internal.R;
@@ -370,9 +372,23 @@
params.windowAnimations = R.style.AutofillSaveAnimation;
params.setTrustedOverlay();
+ ScrollView scrollView = view.findViewById(R.id.autofill_sheet_scroll_view);
+
+ View divider = view.findViewById(R.id.autofill_sheet_divider);
+
+ ViewTreeObserver observer = scrollView.getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(() -> adjustDividerVisibility(scrollView, divider));
+
+ scrollView.getViewTreeObserver()
+ .addOnScrollChangedListener(() -> adjustDividerVisibility(scrollView, divider));
show();
}
+ private void adjustDividerVisibility(ScrollView scrollView, View divider) {
+ boolean canScrollDown = scrollView.canScrollVertically(1); // 1 to check scrolling down
+ divider.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
+ }
+
private boolean applyCustomDescription(@NonNull Context context, @NonNull View saveUiView,
@NonNull ValueFinder valueFinder, @NonNull SaveInfo info) {
final CustomDescription customDescription = info.getCustomDescription();
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 549fa36..4022e33 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -10,6 +10,15 @@
}
flag {
+ name: "enable_metrics_system_backup_agents"
+ namespace: "backup"
+ description: "Enable SystemBackupAgent to collect B&R agent metrics by passing an instance of "
+ "the logger to each BackupHelper."
+ bug: "296844513"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_max_size_writes_to_pipes"
namespace: "onboarding"
description: "Enables the write buffer to pipes to be of maximum size."
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index c2d2468..586aa8a 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -22,9 +22,11 @@
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.companion.CompanionDeviceService;
+import android.companion.DevicePresenceEvent;
import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
+import android.os.ParcelUuid;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -46,7 +48,8 @@
* the services, maintaining the connection (the binding), and invoking callback methods such as
* {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)},
* {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} and
- * {@link CompanionDeviceService#onDeviceEvent(AssociationInfo, int)} in the application process.
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} in the
+ * application process.
*
* <p>
* The following is the list of the APIs provided by {@link CompanionApplicationController} (to be
@@ -54,7 +57,7 @@
* <ul>
* <li> {@link #bindCompanionApplication(int, String, boolean)}
* <li> {@link #unbindCompanionApplication(int, String)}
- * <li> {@link #notifyCompanionApplicationDeviceEvent(AssociationInfo, int)} (AssociationInfo, int)}
+ * <li> {@link #notifyCompanionApplicationDevicePresenceEvent(AssociationInfo, int)}
* <li> {@link #isCompanionApplicationBound(int, String)}
* <li> {@link #isRebindingCompanionApplicationScheduled(int, String)}
* </ul>
@@ -72,6 +75,7 @@
private final @NonNull Context mContext;
private final @NonNull AssociationStore mAssociationStore;
+ private final @NonNull ObservableUuidStore mObservableUuidStore;
private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final @NonNull CompanionServicesRegister mCompanionServicesRegister;
@@ -82,9 +86,11 @@
private final @NonNull AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications;
CompanionApplicationController(Context context, AssociationStore associationStore,
+ ObservableUuidStore observableUuidStore,
CompanionDevicePresenceMonitor companionDevicePresenceMonitor) {
mContext = context;
mAssociationStore = associationStore;
+ mObservableUuidStore = observableUuidStore;
mDevicePresenceMonitor = companionDevicePresenceMonitor;
mCompanionServicesRegister = new CompanionServicesRegister();
mBoundCompanionApplications = new AndroidPackageMap<>();
@@ -281,25 +287,50 @@
primaryServiceConnector.postOnDeviceDisappeared(association);
}
- void notifyCompanionApplicationDeviceEvent(AssociationInfo association, int event) {
+ void notifyCompanionApplicationDevicePresenceEvent(AssociationInfo association, int event) {
final int userId = association.getUserId();
final String packageName = association.getPackageName();
final CompanionDeviceServiceConnector primaryServiceConnector =
getPrimaryServiceConnector(userId, packageName);
+ final DevicePresenceEvent devicePresenceEvent =
+ new DevicePresenceEvent(association.getId(), event, null);
if (primaryServiceConnector == null) {
- Slog.e(TAG, "notifyCompanionApplicationDeviceEvent(): "
+ Slog.e(TAG, "notifyCompanionApplicationDevicePresenceEvent(): "
+ "u" + userId + "/" + packageName
+ " event=[ " + event + " ] is NOT bound.");
Slog.e(TAG, "Stacktrace", new Throwable());
return;
}
- Slog.i(TAG, "Calling onDeviceEvent() to userId=[" + userId + "] package=["
+ Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=["
+ packageName + "] associationId=[" + association.getId()
- + "] state=[" + event + "]");
+ + "] event=[" + event + "]");
- primaryServiceConnector.postOnDeviceEvent(association, event);
+ primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
+ }
+
+ void notifyApplicationDevicePresenceEvent(ObservableUuid uuid, int event) {
+ final int userId = uuid.getUserId();
+ final ParcelUuid parcelUuid = uuid.getUuid();
+ final String packageName = uuid.getPackageName();
+ final CompanionDeviceServiceConnector primaryServiceConnector =
+ getPrimaryServiceConnector(userId, packageName);
+ final DevicePresenceEvent devicePresenceEvent =
+ new DevicePresenceEvent(DevicePresenceEvent.NO_ASSOCIATION, event, parcelUuid);
+
+ if (primaryServiceConnector == null) {
+ Slog.e(TAG, "notifyApplicationDevicePresenceChanged(): "
+ + "u" + userId + "/" + packageName
+ + " event=[ " + event + " ] is NOT bound.");
+ Slog.e(TAG, "Stacktrace", new Throwable());
+ return;
+ }
+
+ Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=["
+ + packageName + "]" + "event= [" + event + "]");
+
+ primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
}
void dump(@NonNull PrintWriter out) {
@@ -364,6 +395,9 @@
// Make sure to clean up the state for all the associations
// that associate with this package.
boolean shouldScheduleRebind = false;
+ boolean shouldScheduleRebindForUuid = false;
+ final List<ObservableUuid> uuids =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
for (AssociationInfo ai :
mAssociationStore.getAssociationsForPackage(userId, packageName)) {
@@ -385,7 +419,14 @@
}
}
- return stillAssociated && shouldScheduleRebind;
+ for (ObservableUuid uuid : uuids) {
+ if (mDevicePresenceMonitor.isDeviceUuidPresent(uuid.getUuid())) {
+ shouldScheduleRebindForUuid = true;
+ break;
+ }
+ }
+
+ return (stillAssociated && shouldScheduleRebind) || shouldScheduleRebindForUuid;
}
private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 84e1d90..2e01ced 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -20,17 +20,17 @@
import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES;
import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
-import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_APPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_DISAPPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_CONNECTED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_DISCONNECTED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_APPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
@@ -45,6 +45,7 @@
import static com.android.server.companion.PackageUtils.getPackageInfo;
import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice;
import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid;
import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks;
@@ -75,6 +76,7 @@
import android.companion.IOnMessageReceivedListener;
import android.companion.IOnTransportsChangedListener;
import android.companion.ISystemDataTransferCallback;
+import android.companion.ObservingDevicePresenceRequest;
import android.companion.datatransfer.PermissionSyncRequest;
import android.content.ComponentName;
import android.content.Context;
@@ -92,6 +94,7 @@
import android.os.Message;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import android.os.ParcelUuid;
import android.os.PowerManagerInternal;
import android.os.PowerWhitelistManager;
import android.os.RemoteCallbackList;
@@ -132,6 +135,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -221,6 +225,8 @@
private CrossDeviceSyncController mCrossDeviceSyncController;
+ private ObservableUuidStore mObservableUuidStore;
+
public CompanionDeviceManagerService(Context context) {
super(context);
@@ -240,6 +246,7 @@
mOnPackageVisibilityChangeListener =
new OnPackageVisibilityChangeListener(mActivityManager);
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+ mObservableUuidStore = new ObservableUuidStore();
}
@Override
@@ -254,13 +261,16 @@
mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
loadAssociationsFromDisk();
+
+ mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
+
mAssociationStore.registerListener(mAssociationStoreChangeListener);
mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(mUserManager,
- mAssociationStore, mDevicePresenceCallback);
+ mAssociationStore, mObservableUuidStore, mDevicePresenceCallback);
mCompanionAppController = new CompanionApplicationController(
- context, mAssociationStore, mDevicePresenceMonitor);
+ context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor);
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
@@ -352,13 +362,29 @@
final int userId = user.getUserIdentifier();
final Set<BluetoothDevice> blueToothDevices =
mDevicePresenceMonitor.getPendingConnectedDevices().get(userId);
+
+ final List<ObservableUuid> observableUuids =
+ mObservableUuidStore.getObservableUuidsForUser(userId);
+
if (blueToothDevices != null) {
for (BluetoothDevice bluetoothDevice : blueToothDevices) {
+ final List<ParcelUuid> deviceUuids = bluetoothDevice.getUuids() == null
+ ? Collections.emptyList() : Arrays.asList(bluetoothDevice.getUuids());
+
for (AssociationInfo ai:
mAssociationStore.getAssociationsByAddress(bluetoothDevice.getAddress())) {
Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected");
mDevicePresenceMonitor.onBluetoothCompanionDeviceConnected(ai.getId());
}
+
+ for (ObservableUuid observableUuid : observableUuids) {
+ if (deviceUuids.contains(observableUuid.getUuid())) {
+ Slog.i(TAG, "onUserUnlocked, UUID( "
+ + observableUuid.getUuid() + " ) is connected");
+ mDevicePresenceMonitor.onDevicePresenceEventByUuid(
+ observableUuid, EVENT_BT_CONNECTED);
+ }
+ }
}
}
}
@@ -423,31 +449,31 @@
}
}
- private void onDeviceEventInternal(int associationId, int event) {
- Slog.i(TAG, "onDeviceEventInternal() id=" + associationId + " event= " + event);
+ private void onDevicePresenceEventInternal(int associationId, int event) {
+ Slog.i(TAG, "onDevicePresenceEventInternal() id=" + associationId + " event= " + event);
final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
final String packageName = association.getPackageName();
final int userId = association.getUserId();
switch (event) {
- case DEVICE_EVENT_BLE_APPEARED:
- case DEVICE_EVENT_BT_CONNECTED:
- case DEVICE_EVENT_SELF_MANAGED_APPEARED:
+ case EVENT_BLE_APPEARED:
+ case EVENT_BT_CONNECTED:
+ case EVENT_SELF_MANAGED_APPEARED:
if (!association.shouldBindWhenPresent()) return;
bindApplicationIfNeeded(association);
- mCompanionAppController.notifyCompanionApplicationDeviceEvent(
+ mCompanionAppController.notifyCompanionApplicationDevicePresenceEvent(
association, event);
break;
- case DEVICE_EVENT_BLE_DISAPPEARED:
- case DEVICE_EVENT_BT_DISCONNECTED:
- case DEVICE_EVENT_SELF_MANAGED_DISAPPEARED:
+ case EVENT_BLE_DISAPPEARED:
+ case EVENT_BT_DISCONNECTED:
+ case EVENT_SELF_MANAGED_DISAPPEARED:
if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
return;
}
if (association.shouldBindWhenPresent()) {
- mCompanionAppController.notifyCompanionApplicationDeviceEvent(
+ mCompanionAppController.notifyCompanionApplicationDevicePresenceEvent(
association, event);
}
// Check if there are other devices associated to the app that are present.
@@ -460,6 +486,45 @@
}
}
+ private void onDevicePresenceEventByUuidInternal(ObservableUuid uuid, int event) {
+ Slog.i(TAG, "onDevicePresenceEventByUuidInternal() id=" + uuid.getUuid()
+ + "for package=" + uuid.getPackageName() + " event=" + event);
+ final String packageName = uuid.getPackageName();
+ final int userId = uuid.getUserId();
+
+ switch(event) {
+ case EVENT_BT_CONNECTED:
+ if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
+ mCompanionAppController.bindCompanionApplication(
+ userId, packageName, /*bindImportant*/ false);
+
+ } else if (DEBUG) {
+ Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound");
+ }
+
+ mCompanionAppController.notifyApplicationDevicePresenceEvent(uuid, event);
+
+ break;
+ case EVENT_BT_DISCONNECTED:
+ if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
+ if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
+ return;
+ }
+
+ mCompanionAppController.notifyApplicationDevicePresenceEvent(uuid, event);
+ // Check if there are other devices associated to the app or the UUID to be
+ // observed are present.
+ if (shouldBindPackage(userId, packageName)) return;
+
+ mCompanionAppController.unbindCompanionApplication(userId, packageName);
+
+ break;
+ default:
+ Slog.e(TAG, "Event: " + event + "is not supported");
+ break;
+ }
+ }
+
private void bindApplicationIfNeeded(AssociationInfo association) {
final String packageName = association.getPackageName();
final int userId = association.getUserId();
@@ -476,15 +541,26 @@
/**
* @return whether the package should be bound (i.e. at least one of the devices associated with
- * the package is currently present).
+ * the package is currently present OR the UUID to be observed by this package is
+ * currently present).
*/
private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) {
final List<AssociationInfo> packageAssociations =
mAssociationStore.getAssociationsForPackage(userId, packageName);
+ final List<ObservableUuid> observableUuids =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+
for (AssociationInfo association : packageAssociations) {
if (!association.shouldBindWhenPresent()) continue;
if (mDevicePresenceMonitor.isDevicePresent(association.getId())) return true;
}
+
+ for (ObservableUuid uuid : observableUuids) {
+ if (mDevicePresenceMonitor.isDeviceUuidPresent(uuid.getUuid())) {
+ return true;
+ }
+ }
+
return false;
}
@@ -568,6 +644,8 @@
// Clear associations.
final List<AssociationInfo> associationsForPackage =
mAssociationStore.getAssociationsForPackage(userId, packageName);
+ final List<ObservableUuid> uuidsTobeObserved =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
for (AssociationInfo association : associationsForPackage) {
mAssociationStore.removeAssociation(association.getId());
}
@@ -575,6 +653,10 @@
for (AssociationInfo association : associationsForPackage) {
maybeRemoveRoleHolderForAssociation(association);
}
+ // Clear the uuids to be observed.
+ for (ObservableUuid uuid : uuidsTobeObserved) {
+ mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName);
+ }
mCompanionAppController.onPackagesChanged(userId);
}
@@ -855,6 +937,95 @@
}
@Override
+ @EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
+ public void startObservingDevicePresence(ObservingDevicePresenceRequest request,
+ String packageName, int userId) {
+ startObservingDevicePresence_enforcePermission();
+ registerDevicePresenceListener(request, packageName, userId, /* active */ true);
+ }
+
+ @Override
+ @EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
+ public void stopObservingDevicePresence(ObservingDevicePresenceRequest request,
+ String packageName, int userId) {
+ stopObservingDevicePresence_enforcePermission();
+ registerDevicePresenceListener(request, packageName, userId, /* active */ false);
+ }
+
+ private void registerDevicePresenceListener(ObservingDevicePresenceRequest request,
+ String packageName, int userId, boolean active) {
+ enforceUsesCompanionDeviceFeature(getContext(), userId, packageName);
+ enforceCallerIsSystemOr(userId, packageName);
+
+ final int associationId = request.getAssociationId();
+ final AssociationInfo associationInfo = mAssociationStore.getAssociationById(
+ associationId);
+ final ParcelUuid uuid = request.getUuid();
+
+ if (uuid != null) {
+ enforceCallerCanObservingDevicePresenceByUuid(getContext());
+ if (active) {
+ startObservingDevicePresenceByUuid(uuid, packageName, userId);
+ } else {
+ stopObservingDevicePresenceByUuid(uuid, packageName, userId);
+ }
+ } else if (associationInfo == null) {
+ throw new IllegalArgumentException("App " + packageName
+ + " is not associated with device " + request.getAssociationId()
+ + " for user " + userId);
+ } else {
+ processDevicePresenceListener(
+ associationInfo, userId, packageName, active);
+ }
+ }
+
+ private void startObservingDevicePresenceByUuid(ParcelUuid uuid, String packageName,
+ int userId) {
+ final List<ObservableUuid> observableUuids =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+
+ for (ObservableUuid observableUuid : observableUuids) {
+ if (observableUuid.getUuid().equals(uuid)) {
+ Slog.i(TAG, "The uuid: " + uuid + " for package:" + packageName
+ + "has been already scheduled for observing");
+ return;
+ }
+ }
+
+ final ObservableUuid observableUuid = new ObservableUuid(userId, uuid,
+ packageName, System.currentTimeMillis());
+
+ mObservableUuidStore.writeObservableUuid(userId, observableUuid);
+ }
+
+ private void stopObservingDevicePresenceByUuid(ParcelUuid uuid, String packageName,
+ int userId) {
+ final List<ObservableUuid> uuidsTobeObserved =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+ boolean isScheduledObserving = false;
+
+ for (ObservableUuid observableUuid : uuidsTobeObserved) {
+ if (observableUuid.getUuid().equals(uuid)) {
+ isScheduledObserving = true;
+ break;
+ }
+ }
+
+ if (!isScheduledObserving) {
+ Slog.i(TAG, "The uuid: " + uuid.toString() + " for package:" + packageName
+ + "has NOT been scheduled for observing yet");
+ return;
+ }
+
+ mObservableUuidStore.removeObservableUuid(userId, uuid, packageName);
+ mDevicePresenceMonitor.removeCurrentConnectedUuidDevice(uuid);
+
+ if (!shouldBindPackage(userId, packageName)) {
+ mCompanionAppController.unbindCompanionApplication(userId, packageName);
+ }
+ }
+
+ @Override
public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
int userId, int associationId) {
return mSystemDataTransferProcessor.buildPermissionTransferUserConsentIntent(
@@ -1002,6 +1173,11 @@
+ " for user " + userId));
}
+ processDevicePresenceListener(association, userId, packageName, active);
+ }
+
+ private void processDevicePresenceListener(AssociationInfo association,
+ int userId, String packageName, boolean active) {
// If already at specified state, then no-op.
if (active == association.isNotifyOnDeviceNearby()) {
if (DEBUG) Log.d(TAG, "Device presence listener is already at desired state.");
@@ -1025,9 +1201,9 @@
if (mDevicePresenceMonitor.isBlePresent(associationId)
|| mDevicePresenceMonitor.isSimulatePresent(associationId)) {
onDeviceAppearedInternal(associationId);
- onDeviceEventInternal(associationId, DEVICE_EVENT_BLE_APPEARED);
+ onDevicePresenceEventInternal(associationId, EVENT_BLE_APPEARED);
} else if (mDevicePresenceMonitor.isBtConnected(associationId)) {
- onDeviceEventInternal(associationId, DEVICE_EVENT_BT_CONNECTED);
+ onDevicePresenceEventInternal(associationId, EVENT_BT_CONNECTED);
}
}
@@ -1518,20 +1694,25 @@
private final CompanionDevicePresenceMonitor.Callback mDevicePresenceCallback =
new CompanionDevicePresenceMonitor.Callback() {
- @Override
- public void onDeviceAppeared(int associationId) {
- onDeviceAppearedInternal(associationId);
- }
+ @Override
+ public void onDeviceAppeared(int associationId) {
+ onDeviceAppearedInternal(associationId);
+ }
- @Override
- public void onDeviceDisappeared(int associationId) {
- onDeviceDisappearedInternal(associationId);
- }
+ @Override
+ public void onDeviceDisappeared(int associationId) {
+ onDeviceDisappearedInternal(associationId);
+ }
- @Override
- public void onDeviceEvent(int associationId, int event) {
- onDeviceEventInternal(associationId, event);
- }
+ @Override
+ public void onDevicePresenceEvent(int associationId, int event) {
+ onDevicePresenceEventInternal(associationId, event);
+ }
+
+ @Override
+ public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) {
+ onDevicePresenceEventByUuidInternal(uuid, event);
+ }
};
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
index 928842c..5abdb42 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
@@ -26,6 +26,7 @@
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.companion.CompanionDeviceService;
+import android.companion.DevicePresenceEvent;
import android.companion.ICompanionDeviceService;
import android.content.ComponentName;
import android.content.Context;
@@ -106,12 +107,11 @@
void postOnDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
post(companionService -> companionService.onDeviceDisappeared(associationInfo));
}
- void postOnDeviceEvent(@NonNull AssociationInfo associationInfo, int event) {
- post(companionService -> companionService.onDeviceEvent(associationInfo, event));
+
+ void postOnDevicePresenceEvent(@NonNull DevicePresenceEvent event) {
+ post(companionService -> companionService.onDevicePresenceEvent(event));
}
-
-
/**
* Post "unbind" job, which will run *after* all previously posted jobs complete.
*
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index e5a8c4f..5663434 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -27,6 +27,7 @@
import android.companion.datatransfer.PermissionSyncRequest;
import android.net.MacAddress;
import android.os.Binder;
+import android.os.ParcelUuid;
import android.os.ShellCommand;
import android.util.Base64;
import android.util.proto.ProtoOutputStream;
@@ -80,6 +81,19 @@
mDevicePresenceMonitor.simulateDeviceEvent(associationId, event);
return 0;
}
+
+ if ("simulate-device-uuid-event".equals(cmd) && Flags.devicePresence()) {
+ String uuid = getNextArgRequired();
+ String packageName = getNextArgRequired();
+ int userId = getNextIntArgRequired();
+ int event = getNextIntArgRequired();
+ ObservableUuid observableUuid = new ObservableUuid(
+ userId, ParcelUuid.fromString(uuid), packageName,
+ System.currentTimeMillis());
+ mDevicePresenceMonitor.simulateDeviceEventByUuid(observableUuid, event);
+ return 0;
+ }
+
switch (cmd) {
case "list": {
final int userId = getNextIntArgRequired();
@@ -447,6 +461,16 @@
pw.println(" Case(3): ");
pw.println(" Make CDM act as if the given companion device is BT disconnected ");
pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
+ pw.println(" simulate-device-uuid-event UUID PACKAGE USERID EVENT");
+ pw.println(" Simulate the companion device event changes:");
+ pw.println(" Case(2): ");
+ pw.println(" Make CDM act as if the given DEVICE is BT connected base"
+ + "on the UUID");
+ pw.println(" Case(3): ");
+ pw.println(" Make CDM act as if the given DEVICE is BT disconnected base"
+ + "on the UUID");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
}
pw.println(" remove-inactive-associations");
diff --git a/services/companion/java/com/android/server/companion/ObservableUuid.java b/services/companion/java/com/android/server/companion/ObservableUuid.java
new file mode 100644
index 0000000..6ab3188
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/ObservableUuid.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.os.ParcelUuid;
+
+public class ObservableUuid {
+ private final int mUserId;
+ private final String mPackageName;
+
+ private final ParcelUuid mUuid;
+
+ private final long mTimeApprovedMs;
+
+ public ObservableUuid(@UserIdInt int userId, @NonNull ParcelUuid uuid,
+ @NonNull String packageName, Long timeApprovedMs) {
+ mUserId = userId;
+ mUuid = uuid;
+ mPackageName = packageName;
+ mTimeApprovedMs = timeApprovedMs;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public ParcelUuid getUuid() {
+ return mUuid;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public long getTimeApprovedMs() {
+ return mTimeApprovedMs;
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/ObservableUuidStore.java
new file mode 100644
index 0000000..94be22a
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/ObservableUuidStore.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.readStringAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeLongAttribute;
+import static com.android.internal.util.XmlUtils.writeStringAttribute;
+import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
+import static com.android.server.companion.DataStoreUtils.isEndOfTag;
+import static com.android.server.companion.DataStoreUtils.isStartOfTag;
+import static com.android.server.companion.DataStoreUtils.writeToFileSafely;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.ParcelUuid;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class ObservableUuidStore {
+ private static final String TAG = "CDM_ObservableUuidStore";
+ private static final String FILE_NAME = "observing_uuids_presence.xml";
+ private static final String XML_TAG_UUIDS = "uuids";
+ private static final String XML_TAG_UUID = "uuid";
+ private static final String XML_ATTR_UUID = "uuid";
+ private static final String XML_ATTR_TIME_APPROVED = "time_approved";
+ private static final String XML_ATTR_USER_ID = "user_id";
+ private static final String XML_ATTR_PACKAGE = "package_name";
+ private static final int READ_FROM_DISK_TIMEOUT = 5; // in seconds
+
+
+ private final ExecutorService mExecutor;
+ private final ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile =
+ new ConcurrentHashMap<>();
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final SparseArray<List<ObservableUuid>> mCachedPerUser =
+ new SparseArray<>();
+
+ public ObservableUuidStore() {
+ mExecutor = Executors.newSingleThreadExecutor();
+ }
+
+ /**
+ * Remove the observable uuid from the disk.
+ */
+ void removeObservableUuid(@UserIdInt int userId, ParcelUuid uuid, String packageName) {
+ List<ObservableUuid> cachedObservableUuids;
+
+ synchronized (mLock) {
+ // Remove requests from cache
+ cachedObservableUuids = readObservableUuidsFromCache(userId);
+ cachedObservableUuids.removeIf(
+ uuid1 -> uuid1.getPackageName().equals(packageName)
+ && uuid1.getUuid().equals(uuid));
+ mCachedPerUser.set(userId, cachedObservableUuids);
+ }
+ // Remove requests from store
+ mExecutor.execute(() -> writeObservableUuidToStore(userId, cachedObservableUuids));
+ }
+
+ void writeObservableUuid(@UserIdInt int userId, ObservableUuid uuid) {
+ Slog.i(TAG, "Writing uuid=" + uuid.getUuid() + " to store.");
+
+ List<ObservableUuid> cachedObservableUuids;
+ synchronized (mLock) {
+ // Write to cache
+ cachedObservableUuids = readObservableUuidsFromCache(userId);
+ cachedObservableUuids.removeIf(uuid1 -> uuid1.getUuid().equals(
+ uuid.getUuid()) && uuid1.getPackageName().equals(uuid.getPackageName()));
+ cachedObservableUuids.add(uuid);
+ mCachedPerUser.set(userId, cachedObservableUuids);
+ }
+ // Write to store
+ mExecutor.execute(() -> writeObservableUuidToStore(userId, cachedObservableUuids));
+ }
+
+ private void writeObservableUuidToStore(@UserIdInt int userId,
+ @NonNull List<ObservableUuid> cachedObservableUuids) {
+ final AtomicFile file = getStorageFileForUser(userId);
+ Slog.i(TAG, "Writing ObservableUuid for user " + userId + " to file="
+ + file.getBaseFile().getPath());
+
+ // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
+ // accesses to the file on the file system using this AtomicFile object.
+ synchronized (file) {
+ writeToFileSafely(file, out -> {
+ final TypedXmlSerializer serializer = Xml.resolveSerializer(out);
+ serializer.setFeature(
+ "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ serializer.startDocument(null, true);
+ writeObservableUuidToXml(serializer, cachedObservableUuids);
+ serializer.endDocument();
+ });
+ }
+ }
+
+ private void writeObservableUuidToXml(@NonNull TypedXmlSerializer serializer,
+ @Nullable Collection<ObservableUuid> uuids) throws IOException {
+ serializer.startTag(null, XML_TAG_UUIDS);
+
+ for (ObservableUuid uuid : uuids) {
+ writeUuidToXml(serializer, uuid);
+ }
+
+ serializer.endTag(null, XML_TAG_UUIDS);
+ }
+
+ private void writeUuidToXml(@NonNull TypedXmlSerializer serializer,
+ @NonNull ObservableUuid uuid) throws IOException {
+ serializer.startTag(null, XML_TAG_UUID);
+
+ writeIntAttribute(serializer, XML_ATTR_USER_ID, uuid.getUserId());
+ writeStringAttribute(serializer, XML_ATTR_UUID, uuid.getUuid().toString());
+ writeStringAttribute(serializer, XML_ATTR_PACKAGE, uuid.getPackageName());
+ writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, uuid.getTimeApprovedMs());
+
+ serializer.endTag(null, XML_TAG_UUID);
+ }
+
+ /**
+ * Read the observable UUIDs from the cache.
+ */
+ @GuardedBy("mLock")
+ private List<ObservableUuid> readObservableUuidsFromCache(@UserIdInt int userId) {
+ List<ObservableUuid> cachedObservableUuids = mCachedPerUser.get(userId);
+ if (cachedObservableUuids == null) {
+ Future<List<ObservableUuid>> future =
+ mExecutor.submit(() -> readObservableUuidFromStore(userId));
+ try {
+ cachedObservableUuids = future.get(READ_FROM_DISK_TIMEOUT, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Slog.e(TAG, "Thread reading ObservableUuid from disk is "
+ + "interrupted.");
+ } catch (ExecutionException e) {
+ Slog.e(TAG, "Error occurred while reading ObservableUuid "
+ + "from disk.");
+ } catch (TimeoutException e) {
+ Slog.e(TAG, "Reading ObservableUuid from disk timed out.");
+ }
+ mCachedPerUser.set(userId, cachedObservableUuids);
+ }
+ return cachedObservableUuids;
+ }
+
+ /**
+ * Reads previously persisted data for the given user
+ *
+ * @param userId Android UserID
+ * @return a list of ObservableUuid
+ */
+ @NonNull
+ public List<ObservableUuid> readObservableUuidFromStore(@UserIdInt int userId) {
+ final AtomicFile file = getStorageFileForUser(userId);
+ Slog.i(TAG, "Reading ObservableUuid for user " + userId + " from "
+ + "file=" + file.getBaseFile().getPath());
+
+ // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
+ // accesses to the file on the file system using this AtomicFile object.
+ synchronized (file) {
+ if (!file.getBaseFile().exists()) {
+ Slog.d(TAG, "File does not exist -> Abort");
+ return new ArrayList<>();
+ }
+ try (FileInputStream in = file.openRead()) {
+ final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+ XmlUtils.beginDocument(parser, XML_TAG_UUIDS);
+
+ return readObservableUuidFromXml(parser);
+ } catch (XmlPullParserException | IOException e) {
+ Slog.e(TAG, "Error while reading requests file", e);
+ return new ArrayList<>();
+ }
+ }
+ }
+
+ @NonNull
+ private List<ObservableUuid> readObservableUuidFromXml(
+ @NonNull TypedXmlPullParser parser) throws XmlPullParserException, IOException {
+ if (!isStartOfTag(parser, XML_TAG_UUIDS)) {
+ throw new XmlPullParserException("The XML doesn't have start tag: " + XML_TAG_UUIDS);
+ }
+
+ List<ObservableUuid> observableUuids = new ArrayList<>();
+
+ while (true) {
+ parser.nextTag();
+ if (isEndOfTag(parser, XML_TAG_UUIDS)) {
+ break;
+ }
+ if (isStartOfTag(parser, XML_TAG_UUID)) {
+ observableUuids.add(readUuidFromXml(parser));
+ }
+ }
+
+ return observableUuids;
+ }
+
+ private ObservableUuid readUuidFromXml(@NonNull TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ if (!isStartOfTag(parser, XML_TAG_UUID)) {
+ throw new XmlPullParserException("XML doesn't have start tag: " + XML_TAG_UUID);
+ }
+
+ final int userId = readIntAttribute(parser, XML_ATTR_USER_ID);
+ final ParcelUuid uuid = ParcelUuid.fromString(readStringAttribute(parser, XML_ATTR_UUID));
+ final String packageName = readStringAttribute(parser, XML_ATTR_PACKAGE);
+ final Long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED);
+
+ return new ObservableUuid(userId, uuid, packageName, timeApproved);
+ }
+
+ /**
+ * Creates and caches {@link AtomicFile} object that represents the back-up file for the given
+ * user.
+ * <p>
+ * IMPORTANT: the method will ALWAYS return the same {@link AtomicFile} object, which makes it
+ * possible to synchronize reads and writes to the file using the returned object.
+ */
+ @NonNull
+ private AtomicFile getStorageFileForUser(@UserIdInt int userId) {
+ return mUserIdToStorageFile.computeIfAbsent(userId,
+ u -> createStorageFileForUser(userId, FILE_NAME));
+ }
+
+ /**
+ * @return A list of ObservableUuids per package.
+ */
+ public List<ObservableUuid> getObservableUuidsForPackage(
+ @UserIdInt int userId, @NonNull String packageName) {
+ final List<ObservableUuid> uuidsTobeObservedPerPackage = new ArrayList<>();
+ synchronized (mLock) {
+ final List<ObservableUuid> uuids = readObservableUuidsFromCache(userId);
+
+ for (ObservableUuid uuid : uuids) {
+ if (uuid.getPackageName().equals(packageName)) {
+ uuidsTobeObservedPerPackage.add(uuid);
+ }
+ }
+ }
+
+ return uuidsTobeObservedPerPackage;
+ }
+
+ /**
+ * @return A list of ObservableUuids per user.
+ */
+ public List<ObservableUuid> getObservableUuidsForUser(@UserIdInt int userId) {
+ synchronized (mLock) {
+ return readObservableUuidsFromCache(userId);
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
index f4e14df..15bebba 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
+import static android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
@@ -174,6 +175,14 @@
+ " for u" + userId + "/" + packageName);
}
+ static void enforceCallerCanObservingDevicePresenceByUuid(@NonNull Context context) {
+ if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
+ != PERMISSION_GRANTED) {
+ throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
+ + "permissions to request observing device presence base on the UUID");
+ }
+ }
+
/**
* Check if the caller is either:
* <ul>
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index 6ba85bd..7eca119 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -16,6 +16,9 @@
package com.android.server.companion.presence;
+import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
+
import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
import static com.android.server.companion.presence.Utils.btDeviceToString;
@@ -27,6 +30,7 @@
import android.net.MacAddress;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.ParcelUuid;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
@@ -35,8 +39,11 @@
import com.android.internal.annotations.GuardedBy;
import com.android.server.companion.AssociationStore;
+import com.android.server.companion.ObservableUuid;
+import com.android.server.companion.ObservableUuidStore;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -53,6 +60,8 @@
void onBluetoothCompanionDeviceConnected(int associationId);
void onBluetoothCompanionDeviceDisconnected(int associationId);
+
+ void onDevicePresenceEventByUuid(ObservableUuid uuid, int event);
}
private final UserManager mUserManager;
@@ -61,6 +70,8 @@
/** A set of ALL connected BT device (not only companion.) */
private final @NonNull Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>();
+ private final @NonNull ObservableUuidStore mObservableUuidStore;
+
/**
* A structure hold the connected BT devices that are pending to be reported to the companion
* app when the user unlocks the local device per userId.
@@ -70,8 +81,10 @@
final SparseArray<Set<BluetoothDevice>> mPendingConnectedDevices = new SparseArray<>();
BluetoothCompanionDeviceConnectionListener(UserManager userManager,
- @NonNull AssociationStore associationStore, @NonNull Callback callback) {
+ @NonNull AssociationStore associationStore,
+ @NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) {
mAssociationStore = associationStore;
+ mObservableUuidStore = observableUuidStore;
mCallback = callback;
mUserManager = userManager;
}
@@ -109,7 +122,6 @@
bluetoothDevices.add(device);
mPendingConnectedDevices.put(userId, bluetoothDevices);
}
-
} else {
onDeviceConnectivityChanged(device, true);
}
@@ -155,8 +167,13 @@
}
private void onDeviceConnectivityChanged(@NonNull BluetoothDevice device, boolean connected) {
+ int userId = UserHandle.myUserId();
final List<AssociationInfo> associations =
mAssociationStore.getAssociationsByAddress(device.getAddress());
+ final List<ObservableUuid> observableUuids =
+ mObservableUuidStore.getObservableUuidsForUser(userId);
+ final List<ParcelUuid> deviceUuids = device.getUuids() == null
+ ? Collections.emptyList() : Arrays.asList(device.getUuids());
if (DEBUG) {
Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device)
@@ -177,6 +194,14 @@
mCallback.onBluetoothCompanionDeviceDisconnected(id);
}
}
+
+ for (ObservableUuid uuid : observableUuids) {
+ if (deviceUuids.contains(uuid.getUuid())) {
+ mCallback.onDevicePresenceEventByUuid(
+ uuid, connected ? EVENT_BT_CONNECTED
+ : EVENT_BT_DISCONNECTED);
+ }
+ }
}
@Override
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index e42b935..54a4692 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -16,12 +16,12 @@
package com.android.server.companion.presence;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_APPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_DISAPPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_CONNECTED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_DISCONNECTED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_APPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
import static android.os.Process.ROOT_UID;
import static android.os.Process.SHELL_UID;
@@ -36,12 +36,15 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelUuid;
import android.os.UserManager;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import com.android.server.companion.AssociationStore;
+import com.android.server.companion.ObservableUuid;
+import com.android.server.companion.ObservableUuidStore;
import java.io.PrintWriter;
import java.util.HashSet;
@@ -61,7 +64,7 @@
* <li> {@link #isDevicePresent(int)}
* <li> {@link Callback#onDeviceAppeared(int) Callback.onDeviceAppeared(int)}
* <li> {@link Callback#onDeviceDisappeared(int) Callback.onDeviceDisappeared(int)}
- * <li> {@link Callback#onDeviceStateChanged(int, int)}}
+ * <li> {@link Callback#onDevicePresenceEvent(int, int)}}
* </ul>
*/
@SuppressLint("LongLogTag")
@@ -78,11 +81,15 @@
/** Invoked when a companion device no longer seen nearby or disconnects. */
void onDeviceDisappeared(int associationId);
- /**Invoked when device has corresponding event changes. */
- void onDeviceEvent(int associationId, int event);
+ /** Invoked when device has corresponding event changes. */
+ void onDevicePresenceEvent(int associationId, int event);
+
+ /** Invoked when device has corresponding event changes base on the UUID */
+ void onDevicePresenceEventByUuid(ObservableUuid uuid, int event);
}
private final @NonNull AssociationStore mAssociationStore;
+ private final @NonNull ObservableUuidStore mObservableUuidStore;
private final @NonNull Callback mCallback;
private final @NonNull BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
private final @NonNull BleCompanionDeviceScanner mBleScanner;
@@ -94,6 +101,7 @@
private final @NonNull Set<Integer> mConnectedBtDevices = new HashSet<>();
private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
+ private final @NonNull Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
// Tracking "simulated" presence. Used for debugging and testing only.
private final @NonNull Set<Integer> mSimulated = new HashSet<>();
@@ -101,11 +109,14 @@
new SimulatedDevicePresenceSchedulerHelper();
public CompanionDevicePresenceMonitor(UserManager userManager,
- @NonNull AssociationStore associationStore, @NonNull Callback callback) {
+ @NonNull AssociationStore associationStore,
+ @NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) {
mAssociationStore = associationStore;
+ mObservableUuidStore = observableUuidStore;
mCallback = callback;
mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager,
- associationStore, /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
+ associationStore, mObservableUuidStore,
+ /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
mBleScanner = new BleCompanionDeviceScanner(associationStore,
/* BleCompanionDeviceScanner.Callback */ this);
}
@@ -126,6 +137,20 @@
}
/**
+ * @return current connected UUID devices.
+ */
+ public Set<ParcelUuid> getCurrentConnectedUuidDevices() {
+ return mConnectedUuidDevices;
+ }
+
+ /**
+ * Remove current connected UUID device.
+ */
+ public void removeCurrentConnectedUuidDevice(ParcelUuid uuid) {
+ mConnectedUuidDevices.remove(uuid);
+ }
+
+ /**
* @return whether the associated companion devices is present. I.e. device is nearby (for BLE);
* or devices is connected (for Bluetooth); or reported (by the application) to be
* nearby (for "self-managed" associations).
@@ -138,6 +163,13 @@
}
/**
+ * @return whether the current uuid to be observed is present.
+ */
+ public boolean isDeviceUuidPresent(ParcelUuid uuid) {
+ return mConnectedUuidDevices.contains(uuid);
+ }
+
+ /**
* @return whether the current device is BT connected and had already reported to the app.
*/
@@ -169,8 +201,8 @@
* {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) notifyDeviceAppeared()}
*/
public void onSelfManagedDeviceConnected(int associationId) {
- onDeviceEvent(mReportedSelfManagedDevices,
- associationId, DEVICE_EVENT_SELF_MANAGED_APPEARED);
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_APPEARED);
}
/**
@@ -183,23 +215,23 @@
* {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) notifyDeviceDisappeared()}
*/
public void onSelfManagedDeviceDisconnected(int associationId) {
- onDeviceEvent(mReportedSelfManagedDevices,
- associationId, DEVICE_EVENT_SELF_MANAGED_DISAPPEARED);
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_DISAPPEARED);
}
/**
* Marks a "self-managed" device as disconnected when binderDied.
*/
public void onSelfManagedDeviceReporterBinderDied(int associationId) {
- onDeviceEvent(mReportedSelfManagedDevices,
- associationId, DEVICE_EVENT_SELF_MANAGED_DISAPPEARED);
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_DISAPPEARED);
}
@Override
public void onBluetoothCompanionDeviceConnected(int associationId) {
Slog.i(TAG, "onBluetoothCompanionDeviceConnected: "
+ "associationId( " + associationId + " )");
- onDeviceEvent(mConnectedBtDevices, associationId, DEVICE_EVENT_BT_CONNECTED);
+ onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
// Stop scanning for BLE devices when this device is connected
// and there are no other devices to connect to.
if (canStopBleScan()) {
@@ -214,22 +246,53 @@
// Start BLE scanning when the device is disconnected.
mBleScanner.startScan();
- onDeviceEvent(mConnectedBtDevices, associationId, DEVICE_EVENT_BT_DISCONNECTED);
+ onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED);
}
@Override
+ public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) {
+ final ParcelUuid parcelUuid = uuid.getUuid();
+
+ switch(event) {
+ case EVENT_BT_CONNECTED:
+ boolean added = mConnectedUuidDevices.add(parcelUuid);
+
+ if (!added) {
+ Slog.w(TAG, "Uuid= " + parcelUuid + "is ALREADY reported as "
+ + "present by this event=" + event);
+ }
+
+ break;
+ case EVENT_BT_DISCONNECTED:
+ final boolean removed = mConnectedUuidDevices.remove(parcelUuid);
+
+ if (!removed) {
+ Slog.w(TAG, "UUID= " + parcelUuid + " was NOT reported "
+ + "as present by this event= " + event);
+
+ return;
+ }
+
+ break;
+ }
+
+ mCallback.onDevicePresenceEventByUuid(uuid, event);
+ }
+
+
+ @Override
public void onBleCompanionDeviceFound(int associationId) {
- onDeviceEvent(mNearbyBleDevices, associationId, DEVICE_EVENT_BLE_APPEARED);
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
}
@Override
public void onBleCompanionDeviceLost(int associationId) {
- onDeviceEvent(mNearbyBleDevices, associationId, DEVICE_EVENT_BLE_DISAPPEARED);
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
}
/** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
@TestApi
- public void simulateDeviceEvent(int associationId, int state) {
+ public void simulateDeviceEvent(int associationId, int event) {
// IMPORTANT: this API should only be invoked via the
// 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
// make this call are SHELL and ROOT.
@@ -238,32 +301,43 @@
// Make sure the association exists.
enforceAssociationExists(associationId);
- switch (state) {
- case DEVICE_EVENT_BLE_APPEARED:
- simulateDeviceAppeared(associationId, state);
+ switch (event) {
+ case EVENT_BLE_APPEARED:
+ simulateDeviceAppeared(associationId, event);
break;
- case DEVICE_EVENT_BT_CONNECTED:
+ case EVENT_BT_CONNECTED:
onBluetoothCompanionDeviceConnected(associationId);
break;
- case DEVICE_EVENT_BLE_DISAPPEARED:
- simulateDeviceDisappeared(associationId, state);
+ case EVENT_BLE_DISAPPEARED:
+ simulateDeviceDisappeared(associationId, event);
break;
- case DEVICE_EVENT_BT_DISCONNECTED:
+ case EVENT_BT_DISCONNECTED:
onBluetoothCompanionDeviceDisconnected(associationId);
break;
default:
- throw new IllegalArgumentException("State: " + state + "is not supported");
+ throw new IllegalArgumentException("Event: " + event + "is not supported");
}
}
+ /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+ @TestApi
+ public void simulateDeviceEventByUuid(ObservableUuid uuid, int event) {
+ // IMPORTANT: this API should only be invoked via the
+ // 'companiondevice simulate-device-uuid-events' Shell command, so the only uid-s allowed to
+ // make this call are SHELL and ROOT.
+ // No other caller (including SYSTEM!) should be allowed.
+ enforceCallerShellOrRoot();
+ onDevicePresenceEventByUuid(uuid, event);
+ }
+
private void simulateDeviceAppeared(int associationId, int state) {
- onDeviceEvent(mSimulated, associationId, state);
+ onDevicePresenceEvent(mSimulated, associationId, state);
mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
}
private void simulateDeviceDisappeared(int associationId, int state) {
mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
- onDeviceEvent(mSimulated, associationId, state);
+ onDevicePresenceEvent(mSimulated, associationId, state);
}
private void enforceAssociationExists(int associationId) {
@@ -273,14 +347,14 @@
}
}
- private void onDeviceEvent(@NonNull Set<Integer> presentDevicesForSource,
+ private void onDevicePresenceEvent(@NonNull Set<Integer> presentDevicesForSource,
int associationId, int event) {
- Slog.i(TAG, "onDeviceEvent() id=" + associationId + ", state=" + event);
+ Slog.i(TAG, "onDevicePresenceEvent() id=" + associationId + ", event=" + event);
switch (event) {
- case DEVICE_EVENT_BLE_APPEARED:
- case DEVICE_EVENT_BT_CONNECTED:
- case DEVICE_EVENT_SELF_MANAGED_APPEARED:
+ case EVENT_BLE_APPEARED:
+ case EVENT_BT_CONNECTED:
+ case EVENT_SELF_MANAGED_APPEARED:
final boolean added = presentDevicesForSource.add(associationId);
if (!added) {
@@ -292,9 +366,9 @@
mCallback.onDeviceAppeared(associationId);
break;
- case DEVICE_EVENT_BLE_DISAPPEARED:
- case DEVICE_EVENT_BT_DISCONNECTED:
- case DEVICE_EVENT_SELF_MANAGED_DISAPPEARED:
+ case EVENT_BLE_DISAPPEARED:
+ case EVENT_BT_DISCONNECTED:
+ case EVENT_SELF_MANAGED_DISAPPEARED:
final boolean removed = presentDevicesForSource.remove(associationId);
if (!removed) {
@@ -312,7 +386,7 @@
return;
}
- mCallback.onDeviceEvent(associationId, event);
+ mCallback.onDevicePresenceEvent(associationId, event);
}
/**
@@ -436,7 +510,7 @@
public void handleMessage(@NonNull Message msg) {
final int associationId = msg.what;
if (mSimulated.contains(associationId)) {
- onDeviceEvent(mSimulated, associationId, DEVICE_EVENT_BLE_DISAPPEARED);
+ onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_DISAPPEARED);
}
}
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index fdcd27d..9375fb1 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -211,7 +211,9 @@
"com_android_wm_shell_flags_lib",
"com.android.server.utils_aconfig-java",
"service-jobscheduler-deviceidle.flags-aconfig-java",
+ "backup_flags_lib",
"policy_flags_lib",
+ "net_flags_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 9b1fade..afb8345 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -4491,6 +4491,12 @@
}
}
if (userId > 0) {
+ if (mAm.isSystemUserOnly(sInfo.flags)) {
+ Slog.w(TAG_SERVICE, service + " is only available for the SYSTEM user,"
+ + " calling userId is: " + userId);
+ return null;
+ }
+
if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo,
sInfo.name, sInfo.flags)
&& mAm.isValidSingletonCall(callingUid, sInfo.applicationInfo.uid)) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 86894fd..52a5408 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5087,13 +5087,11 @@
intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
| Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
- final BroadcastOptions bOptions = mUserController.getTemporaryAppAllowlistBroadcastOptions(
- reason);
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null,
new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
null, null, AppOpsManager.OP_NONE,
- bOptions.toBundle(), true,
+ null, true,
false, MY_PID, SYSTEM_UID,
SYSTEM_UID, MY_PID, app.userId);
}
@@ -13763,6 +13761,11 @@
return result;
}
+ boolean isSystemUserOnly(int flags) {
+ return android.multiuser.Flags.enableSystemUserOnlyForServicesAndProviders()
+ && (flags & ServiceInfo.FLAG_SYSTEM_USER_ONLY) != 0;
+ }
+
/**
* Checks to see if the caller is in the same app as the singleton
* component, or the component is in a special app. It allows special apps
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 095d907..30f21a6 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -1249,9 +1249,9 @@
ProviderInfo cpi = providers.get(i);
boolean singleton = mService.isSingleton(cpi.processName, cpi.applicationInfo,
cpi.name, cpi.flags);
- if (singleton && app.userId != UserHandle.USER_SYSTEM) {
- // This is a singleton provider, but a user besides the
- // default user is asking to initialize a process it runs
+ if (isSingletonOrSystemUserOnly(cpi) && app.userId != UserHandle.USER_SYSTEM) {
+ // This is a singleton or a SYSTEM user only provider, but a user besides the
+ // SYSTEM user is asking to initialize a process it runs
// in... well, no, it doesn't actually run in this process,
// it runs in the process of the default user. Get rid of it.
providers.remove(i);
@@ -1398,8 +1398,7 @@
final boolean processMatch =
Objects.equals(pi.processName, app.processName)
|| pi.multiprocess;
- final boolean userMatch = !mService.isSingleton(
- pi.processName, pi.applicationInfo, pi.name, pi.flags)
+ final boolean userMatch = !isSingletonOrSystemUserOnly(pi)
|| app.userId == UserHandle.USER_SYSTEM;
final boolean isInstantApp = pi.applicationInfo.isInstantApp();
final boolean splitInstalled = pi.splitName == null
@@ -1985,4 +1984,13 @@
return isAuthRedirected;
}
}
+
+ /**
+ * Returns true if Provider is either singleUser or systemUserOnly provider.
+ */
+ private boolean isSingletonOrSystemUserOnly(ProviderInfo pi) {
+ return (android.multiuser.Flags.enableSystemUserOnlyForServicesAndProviders()
+ && mService.isSystemUserOnly(pi.flags))
+ || mService.isSingleton(pi.processName, pi.applicationInfo, pi.name, pi.flags);
+ }
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 9db5d0a..dc14c7a 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -162,6 +162,7 @@
"pdf_viewer",
"pixel_audio_android",
"pixel_bluetooth",
+ "pixel_connectivity_gps",
"pixel_system_sw_video",
"pixel_watch",
"platform_security",
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index de4979a..5b9469b 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -20,7 +20,8 @@
import android.app.backup.BackupAgentHelper;
import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.BackupDataInput;
-import android.app.backup.BackupHelper;
+import android.app.backup.BackupHelperWithLogger;
+import android.app.backup.BackupRestoreEventLogger;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
import android.app.backup.WallpaperBackupHelper;
@@ -33,9 +34,10 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
-
import com.google.android.collect.Sets;
+import com.android.server.backup.Flags;
+
import java.io.File;
import java.io.IOException;
import java.util.Set;
@@ -107,10 +109,12 @@
private int mUserId = UserHandle.USER_SYSTEM;
private boolean mIsProfileUser = false;
+ private BackupRestoreEventLogger mLogger;
@Override
public void onCreate(UserHandle user, @BackupDestination int backupDestination) {
super.onCreate(user, backupDestination);
+ mLogger = this.getBackupRestoreEventLogger();
mUserId = user.getIdentifier();
if (mUserId != UserHandle.USER_SYSTEM) {
@@ -209,9 +213,12 @@
}
}
- private void addHelperIfEligibleForUser(String keyPrefix, BackupHelper helper) {
+ private void addHelperIfEligibleForUser(String keyPrefix, BackupHelperWithLogger helper) {
if (isHelperEligibleForUser(keyPrefix)) {
addHelper(keyPrefix, helper);
+ if (Flags.enableMetricsSystemBackupAgents()) {
+ helper.setLogger(mLogger);
+ }
}
}
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 9dd7daf..9102cfd 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -286,6 +286,9 @@
return new CompatChange(changeId);
});
c.addPackageOverride(packageName, overrides, allowedState, versionCode);
+ Slog.d(TAG, (overrides.isEnabled() ? "Enabled" : "Disabled")
+ + " change " + changeId + (c.getName() != null ? " [" + c.getName() + "]" : "")
+ + " for " + packageName);
invalidateCache();
return alreadyKnown.get();
}
@@ -372,7 +375,14 @@
long changeId = change.getId();
OverrideAllowedState allowedState =
mOverrideValidator.getOverrideAllowedState(changeId, packageName);
- return change.removePackageOverride(packageName, allowedState, versionCode);
+ boolean overrideExists = change.removePackageOverride(packageName, allowedState,
+ versionCode);
+ if (overrideExists) {
+ Slog.d(TAG, "Reset change " + changeId
+ + (change.getName() != null ? " [" + change.getName() + "]" : "")
+ + " for " + packageName + " to default value.");
+ }
+ return overrideExists;
}
/**
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index d34661d..34e75c0 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -47,6 +47,7 @@
import android.media.AudioProfile;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager.TvInputCallback;
+import android.os.Handler;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -97,9 +98,15 @@
private boolean mSystemAudioMute = false;
// If true, do not do routing control/send active source for internal source.
- // Set to true when the device was woken up by <Text/Image View On>.
+ // Set to true for a short duration when the device is woken up by <Text/Image View On>.
private boolean mSkipRoutingControl;
+ // Handler for posting a runnable to set `mSkipRoutingControl` to false after a delay
+ private final Handler mSkipRoutingControlHandler;
+
+ // Runnable that sets `mSkipRoutingControl` to false
+ private final Runnable mResetSkipRoutingControlRunnable = () -> mSkipRoutingControl = false;
+
// Message buffer used to buffer selected messages to process later. <Active Source>
// from a source device, for instance, needs to be buffered if the device is not
// discovered yet. The buffered commands are taken out and when they are ready to
@@ -162,6 +169,7 @@
HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL)
== HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED;
mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
+ mSkipRoutingControlHandler = new Handler(service.getServiceLooper());
}
@Override
@@ -184,7 +192,14 @@
mService.getHdmiCecNetwork().addCecSwitch(
mService.getHdmiCecNetwork().getPhysicalAddress()); // TV is a CEC switch too.
mTvInputs.clear();
+
mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE);
+ mSkipRoutingControlHandler.removeCallbacks(mResetSkipRoutingControlRunnable);
+ if (mSkipRoutingControl) {
+ mSkipRoutingControlHandler.postDelayed(mResetSkipRoutingControlRunnable,
+ HdmiConfig.TIMEOUT_MS);
+ }
+
launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC &&
reason != HdmiControlService.INITIATED_BY_BOOT_UP);
resetSelectRequestBuffer();
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index bc55b24..572d844 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -16,6 +16,8 @@
package com.android.server.input;
+import static com.android.input.flags.Flags.rateLimitUserActivityPokeInDispatcher;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -113,6 +115,8 @@
for (Consumer<String> observer : mObservers.values()) {
observer.accept("just booted");
}
+
+ configureUserActivityPokeInterval();
}
@Override
@@ -228,4 +232,13 @@
mService.setAccessibilityStickyKeysEnabled(
InputSettings.isAccessibilityStickyKeysEnabled(mContext));
}
+
+ private void configureUserActivityPokeInterval() {
+ if (rateLimitUserActivityPokeInDispatcher()) {
+ final int intervalMillis = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_minMillisBetweenInputUserActivityEvents);
+ Log.i(TAG, "Setting user activity interval (ms) of " + intervalMillis);
+ mNative.setMinTimeBetweenUserActivityPokes(intervalMillis);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 6f52020..8aec8ca 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -108,6 +108,8 @@
void setFocusedDisplay(int displayId);
+ void setMinTimeBetweenUserActivityPokes(long millis);
+
boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
boolean isDragDrop);
@@ -344,6 +346,9 @@
public native void setFocusedDisplay(int displayId);
@Override
+ public native void setMinTimeBetweenUserActivityPokes(long millis);
+
+ @Override
public native boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
boolean isDragDrop);
diff --git a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
index c9a3748..62adb25 100644
--- a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
+++ b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
@@ -52,14 +52,14 @@
void reset(@NonNull InputMethodMap methodMap) {
mSubtypeHandles.clear();
final InputMethodSettings settings = InputMethodSettings.create(methodMap, mUserId);
- final List<InputMethodInfo> inputMethods = settings.getEnabledInputMethodListLocked();
+ final List<InputMethodInfo> inputMethods = settings.getEnabledInputMethodList();
for (int i = 0; i < inputMethods.size(); ++i) {
final InputMethodInfo imi = inputMethods.get(i);
if (!imi.shouldShowInInputMethodPicker()) {
continue;
}
final List<InputMethodSubtype> subtypes =
- settings.getEnabledInputMethodSubtypeListLocked(imi, true);
+ settings.getEnabledInputMethodSubtypeList(imi, true);
if (subtypes.isEmpty()) {
mSubtypeHandles.add(InputMethodSubtypeHandle.of(imi, null));
} else {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index e997fcf..50340d2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1167,7 +1167,7 @@
// sender userId can be a real user ID or USER_ALL.
final int senderUserId = pendingResult.getSendingUserId();
if (senderUserId != UserHandle.USER_ALL) {
- if (senderUserId != mSettings.getCurrentUserId()) {
+ if (senderUserId != mSettings.getUserId()) {
// A background user is trying to hide the dialog. Ignore.
return;
}
@@ -1245,7 +1245,7 @@
@GuardedBy("ImfLock.class")
private boolean isChangingPackagesOfCurrentUserLocked() {
final int userId = getChangingUserId();
- final boolean retval = userId == mSettings.getCurrentUserId();
+ final boolean retval = userId == mSettings.getUserId();
if (DEBUG) {
if (!retval) {
Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
@@ -1344,8 +1344,7 @@
}
if (changed) {
AdditionalSubtypeUtils.save(
- mAdditionalSubtypeMap, mSettings.getMethodMap(),
- mSettings.getCurrentUserId());
+ mAdditionalSubtypeMap, mSettings.getMethodMap(), mSettings.getUserId());
mChangedPackages.add(packageName);
}
}
@@ -1425,8 +1424,7 @@
+ imi.getComponent());
mAdditionalSubtypeMap.remove(imi.getId());
AdditionalSubtypeUtils.save(mAdditionalSubtypeMap,
- mSettings.getMethodMap(),
- mSettings.getCurrentUserId());
+ mSettings.getMethodMap(), mSettings.getUserId());
}
}
}
@@ -1440,7 +1438,7 @@
if (change == PACKAGE_TEMPORARY_CHANGE
|| change == PACKAGE_PERMANENT_CHANGE) {
final PackageManager userAwarePackageManager =
- getPackageManagerForUser(mContext, mSettings.getCurrentUserId());
+ getPackageManagerForUser(mContext, mSettings.getUserId());
ServiceInfo si = null;
try {
si = userAwarePackageManager.getServiceInfo(curIm.getComponent(),
@@ -1576,7 +1574,7 @@
void onUnlockUser(@UserIdInt int userId) {
synchronized (ImfLock.class) {
- final int currentUserId = mSettings.getCurrentUserId();
+ final int currentUserId = mSettings.getUserId();
if (DEBUG) {
Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
}
@@ -1665,7 +1663,7 @@
mSettings.getMethodMap(), userId);
mHardwareKeyboardShortcutController =
new HardwareKeyboardShortcutController(mSettings.getMethodMap(),
- mSettings.getCurrentUserId());
+ mSettings.getUserId());
mMenuController = new InputMethodMenuController(this);
mBindingController =
bindingControllerForTesting != null
@@ -1696,7 +1694,7 @@
@GuardedBy("ImfLock.class")
@UserIdInt
int getCurrentImeUserIdLocked() {
- return mSettings.getCurrentUserId();
+ return mSettings.getUserId();
}
private final class InkWindowInitializer implements Runnable {
@@ -1728,7 +1726,7 @@
return;
}
final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes(
- context, mSettings.getEnabledInputMethodListLocked());
+ context, mSettings.getEnabledInputMethodList());
if (suitableImes.isEmpty()) {
Slog.i(TAG, "No default found");
return;
@@ -1784,7 +1782,7 @@
IInputMethodClientInvoker clientToBeReset) {
if (DEBUG) {
Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
- + " currentUserId=" + mSettings.getCurrentUserId());
+ + " currentUserId=" + mSettings.getUserId());
}
maybeInitImeNavbarConfigLocked(newUserId);
@@ -1823,7 +1821,7 @@
if (initialUserSwitch) {
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, newUserId),
- mSettings.getEnabledInputMethodListLocked());
+ mSettings.getEnabledInputMethodList());
}
if (DEBUG) {
@@ -1854,7 +1852,7 @@
}
if (!mSystemReady) {
mSystemReady = true;
- final int currentUserId = mSettings.getCurrentUserId();
+ final int currentUserId = mSettings.getUserId();
mStatusBarManagerInternal =
LocalServices.getService(StatusBarManagerInternal.class);
hideStatusBarIconLocked();
@@ -1875,7 +1873,7 @@
// the "mImeDrawsImeNavBarResLazyInitFuture" field.
synchronized (ImfLock.class) {
mImeDrawsImeNavBarResLazyInitFuture = null;
- if (currentUserId != mSettings.getCurrentUserId()) {
+ if (currentUserId != mSettings.getUserId()) {
// This means that the current user is already switched to other user
// before the background task is executed. In this scenario the relevant
// field should already be initialized.
@@ -1900,7 +1898,7 @@
updateFromSettingsLocked(true);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, currentUserId),
- mSettings.getEnabledInputMethodListLocked());
+ mSettings.getEnabledInputMethodList());
}
}
}
@@ -1947,7 +1945,7 @@
}
synchronized (ImfLock.class) {
final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
- mSettings.getCurrentUserId(), null);
+ mSettings.getUserId(), null);
if (resolvedUserIds.length != 1) {
return Collections.emptyList();
}
@@ -1970,7 +1968,7 @@
}
synchronized (ImfLock.class) {
final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
- mSettings.getCurrentUserId(), null);
+ mSettings.getUserId(), null);
if (resolvedUserIds.length != 1) {
return Collections.emptyList();
}
@@ -1997,7 +1995,7 @@
}
// Check if selected IME of current user supports handwriting.
- if (userId == mSettings.getCurrentUserId()) {
+ if (userId == mSettings.getUserId()) {
return mBindingController.supportsStylusHandwriting();
}
//TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
@@ -2025,7 +2023,7 @@
private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness, int callingUid) {
final InputMethodSettings settings;
- if (userId == mSettings.getCurrentUserId()
+ if (userId == mSettings.getUserId()
&& directBootAwareness == DirectBootAwareness.AUTO) {
settings = mSettings;
} else {
@@ -2048,12 +2046,12 @@
int callingUid) {
final ArrayList<InputMethodInfo> methodList;
final InputMethodSettings settings;
- if (userId == mSettings.getCurrentUserId()) {
- methodList = mSettings.getEnabledInputMethodListLocked();
+ if (userId == mSettings.getUserId()) {
+ methodList = mSettings.getEnabledInputMethodList();
settings = mSettings;
} else {
settings = queryMethodMapForUser(userId);
- methodList = settings.getEnabledInputMethodListLocked();
+ methodList = settings.getEnabledInputMethodList();
}
// filter caller's access to input methods
methodList.removeIf(imi ->
@@ -2112,7 +2110,7 @@
@GuardedBy("ImfLock.class")
private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
- if (userId == mSettings.getCurrentUserId()) {
+ if (userId == mSettings.getUserId()) {
final InputMethodInfo imi;
String selectedMethodId = getSelectedMethodIdLocked();
if (imiId == null && selectedMethodId != null) {
@@ -2124,7 +2122,7 @@
imi.getPackageName(), callingUid, userId, mSettings)) {
return Collections.emptyList();
}
- return mSettings.getEnabledInputMethodSubtypeListLocked(
+ return mSettings.getEnabledInputMethodSubtypeList(
imi, allowsImplicitlyEnabledSubtypes);
}
final InputMethodSettings settings = queryMethodMapForUser(userId);
@@ -2135,7 +2133,7 @@
if (!canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)) {
return Collections.emptyList();
}
- return settings.getEnabledInputMethodSubtypeListLocked(
+ return settings.getEnabledInputMethodSubtypeList(
imi, allowsImplicitlyEnabledSubtypes);
}
@@ -2300,7 +2298,7 @@
final boolean restarting = !initial;
final Binder startInputToken = new Binder();
- final StartInputInfo info = new StartInputInfo(mSettings.getCurrentUserId(),
+ final StartInputInfo info = new StartInputInfo(mSettings.getUserId(),
getCurTokenLocked(),
mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
UserHandle.getUserId(mCurClient.mUid),
@@ -2315,9 +2313,9 @@
// same-user scenarios.
// That said ignoring cross-user scenario will never affect IMEs that do not have
// INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
- if (mSettings.getCurrentUserId() == UserHandle.getUserId(
+ if (mSettings.getUserId() == UserHandle.getUserId(
mCurClient.mUid)) {
- mPackageManagerInternal.grantImplicitAccess(mSettings.getCurrentUserId(),
+ mPackageManagerInternal.grantImplicitAccess(mSettings.getUserId(),
null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()),
mCurClient.mUid, true /* direct */);
}
@@ -2939,7 +2937,7 @@
} else if (packageName != null) {
if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
final PackageManager userAwarePackageManager =
- getPackageManagerForUser(mContext, mSettings.getCurrentUserId());
+ getPackageManagerForUser(mContext, mSettings.getUserId());
ApplicationInfo applicationInfo = null;
try {
applicationInfo = userAwarePackageManager.getApplicationInfo(packageName,
@@ -3001,7 +2999,7 @@
return false;
}
if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
- && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId())) {
+ && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId())) {
return false;
}
if ((visibility & InputMethodService.IME_ACTIVE) == 0
@@ -3018,7 +3016,7 @@
return false;
}
- List<InputMethodInfo> imes = mSettings.getEnabledInputMethodListWithFilterLocked(
+ List<InputMethodInfo> imes = mSettings.getEnabledInputMethodListWithFilter(
InputMethodInfo::shouldShowInInputMethodPicker);
final int numImes = imes.size();
if (numImes > 2) return true;
@@ -3030,7 +3028,7 @@
for (int i = 0; i < numImes; ++i) {
final InputMethodInfo imi = imes.get(i);
final List<InputMethodSubtype> subtypes =
- mSettings.getEnabledInputMethodSubtypeListLocked(imi, true);
+ mSettings.getEnabledInputMethodSubtypeList(imi, true);
final int subtypeCount = subtypes.size();
if (subtypeCount == 0) {
++nonAuxCount;
@@ -3180,9 +3178,9 @@
void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
if (enabledMayChange) {
final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext,
- mSettings.getCurrentUserId());
+ mSettings.getUserId());
- List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
+ List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
for (int i = 0; i < enabled.size(); i++) {
// We allow the user to select "disabled until used" apps, so if they
// are enabling one of those here we now need to make it enabled.
@@ -3228,18 +3226,18 @@
}
// TODO: Instantiate mSwitchingController for each user.
- if (mSettings.getCurrentUserId() == mSwitchingController.getUserId()) {
+ if (mSettings.getUserId() == mSwitchingController.getUserId()) {
mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
} else {
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
- mContext, mSettings.getMethodMap(), mSettings.getCurrentUserId());
+ mContext, mSettings.getMethodMap(), mSettings.getUserId());
}
// TODO: Instantiate mHardwareKeyboardShortcutController for each user.
- if (mSettings.getCurrentUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+ if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
} else {
mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
- mSettings.getMethodMap(), mSettings.getCurrentUserId());
+ mSettings.getMethodMap(), mSettings.getUserId());
}
sendOnNavButtonFlagsChangedLocked();
}
@@ -3270,7 +3268,7 @@
// See if we need to notify a subtype change within the same IME.
if (id.equals(getSelectedMethodIdLocked())) {
- final int userId = mSettings.getCurrentUserId();
+ final int userId = mSettings.getUserId();
final int subtypeCount = info.getSubtypeCount();
if (subtypeCount <= 0) {
notifyInputMethodSubtypeChangedLocked(userId, info, null);
@@ -3781,7 +3779,7 @@
return InputBindResult.USER_SWITCHING;
}
final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
- mSettings.getCurrentUserId(), false /* enabledOnly */);
+ mSettings.getUserId(), false /* enabledOnly */);
for (int profileId : profileIdsWithDisabled) {
if (profileId == userId) {
scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3800,7 +3798,7 @@
mVisibilityStateComputer.mShowForced = false;
}
- final int currentUserId = mSettings.getCurrentUserId();
+ final int currentUserId = mSettings.getUserId();
if (userId != currentUserId) {
if (ArrayUtils.contains(
mUserManagerInternal.getProfileIds(currentUserId, false), userId)) {
@@ -3944,7 +3942,7 @@
&& mCurFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
return true;
}
- if (mSettings.getCurrentUserId() != UserHandle.getUserId(uid)) {
+ if (mSettings.getUserId() != UserHandle.getUserId(uid)) {
return false;
}
if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid(
@@ -4050,7 +4048,7 @@
if (!calledWithValidTokenLocked(token)) {
return false;
}
- final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
+ final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtype();
final InputMethodInfo lastImi;
if (lastIme != null) {
lastImi = mSettings.getMethodMap().get(lastIme.first);
@@ -4077,7 +4075,7 @@
// This is a safety net. If the currentSubtype can't be added to the history
// and the framework couldn't find the last ime, we will make the last ime be
// the most applicable enabled keyboard subtype of the system imes.
- final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
+ final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
if (enabled != null) {
final int enabledCount = enabled.size();
final String locale;
@@ -4085,14 +4083,13 @@
&& !TextUtils.isEmpty(mCurrentSubtype.getLocale())) {
locale = mCurrentSubtype.getLocale();
} else {
- locale = SystemLocaleWrapper.get(mSettings.getCurrentUserId()).get(0)
- .toString();
+ locale = SystemLocaleWrapper.get(mSettings.getUserId()).get(0).toString();
}
for (int i = 0; i < enabledCount; ++i) {
final InputMethodInfo imi = enabled.get(i);
if (imi.getSubtypeCount() > 0 && imi.isSystem()) {
InputMethodSubtype keyboardSubtype =
- SubtypeUtils.findLastResortApplicableSubtypeLocked(
+ SubtypeUtils.findLastResortApplicableSubtype(
SubtypeUtils.getSubtypes(imi),
SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
if (keyboardSubtype != null) {
@@ -4164,12 +4161,12 @@
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
- if (mSettings.getCurrentUserId() == userId) {
- return mSettings.getLastInputMethodSubtypeLocked();
+ if (mSettings.getUserId() == userId) {
+ return mSettings.getLastInputMethodSubtype();
}
final InputMethodSettings settings = queryMethodMapForUser(userId);
- return settings.getLastInputMethodSubtypeLocked();
+ return settings.getLastInputMethodSubtype();
}
}
@@ -4199,7 +4196,7 @@
return;
}
- if (mSettings.getCurrentUserId() == userId) {
+ if (mSettings.getUserId() == userId) {
if (!mSettings.setAdditionalInputMethodSubtypes(imiId, toBeAdded,
mAdditionalSubtypeMap, mPackageManagerInternal, callingUid)) {
return;
@@ -4243,7 +4240,7 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized (ImfLock.class) {
- final boolean currentUser = (mSettings.getCurrentUserId() == userId);
+ final boolean currentUser = (mSettings.getUserId() == userId);
final InputMethodSettings settings = currentUser
? mSettings : queryMethodMapForUser(userId);
if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
@@ -4614,7 +4611,7 @@
}
return;
}
- if (mSettings.getCurrentUserId() != mSwitchingController.getUserId()) {
+ if (mSettings.getUserId() != mSwitchingController.getUserId()) {
return;
}
final InputMethodInfo imi =
@@ -4677,7 +4674,7 @@
} else {
// Called with current IME's token.
if (mSettings.getMethodMap().get(id) != null
- && mSettings.getEnabledInputMethodListWithFilterLocked(
+ && mSettings.getEnabledInputMethodListWithFilter(
(info) -> info.getId().equals(id)).isEmpty()) {
throw new IllegalStateException("Requested IME is not enabled: " + id);
}
@@ -4849,8 +4846,7 @@
}
synchronized (ImfLock.class) {
final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
- && mWindowManagerInternal.isKeyguardSecure(
- mSettings.getCurrentUserId());
+ && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId());
final String lastInputMethodId = mSettings.getSelectedInputMethod();
int lastInputMethodSubtypeId =
mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
@@ -4858,8 +4854,7 @@
final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
.getSortedInputMethodAndSubtypeList(
showAuxSubtypes, isScreenLocked, true /* forImeMenu */,
- mContext, mSettings.getMethodMap(),
- mSettings.getCurrentUserId());
+ mContext, mSettings.getMethodMap(), mSettings.getUserId());
mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
lastInputMethodId, lastInputMethodSubtypeId, imList);
}
@@ -5043,7 +5038,7 @@
@GuardedBy("ImfLock.class")
private boolean chooseNewDefaultIMELocked() {
final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME(
- mSettings.getEnabledInputMethodListLocked());
+ mSettings.getEnabledInputMethodList());
if (imi != null) {
if (DEBUG) {
Slog.d(TAG, "New default IME was selected: " + imi.getId());
@@ -5157,7 +5152,7 @@
mMethodMapUpdateCount++;
mMyPackageMonitor.clearKnownImePackageNamesLocked();
- mSettings = queryInputMethodServicesInternal(mContext, mSettings.getCurrentUserId(),
+ mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
mAdditionalSubtypeMap, DirectBootAwareness.AUTO);
// Construct the set of possible IME packages for onPackageChanged() to avoid false
@@ -5170,7 +5165,7 @@
final List<ResolveInfo> allInputMethodServices =
mContext.getPackageManager().queryIntentServicesAsUser(
new Intent(InputMethod.SERVICE_INTERFACE),
- PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getCurrentUserId());
+ PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getUserId());
final int numImes = allInputMethodServices.size();
for (int i = 0; i < numImes; ++i) {
final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
@@ -5185,7 +5180,7 @@
if (!resetDefaultEnabledIme) {
boolean enabledImeFound = false;
boolean enabledNonAuxImeFound = false;
- final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodListLocked();
+ final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodList();
final int numImes = enabledImes.size();
for (int i = 0; i < numImes; ++i) {
final InputMethodInfo imi = enabledImes.get(i);
@@ -5241,18 +5236,18 @@
updateDefaultVoiceImeIfNeededLocked();
// TODO: Instantiate mSwitchingController for each user.
- if (mSettings.getCurrentUserId() == mSwitchingController.getUserId()) {
+ if (mSettings.getUserId() == mSwitchingController.getUserId()) {
mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
} else {
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
- mContext, mSettings.getMethodMap(), mSettings.getCurrentUserId());
+ mContext, mSettings.getMethodMap(), mSettings.getUserId());
}
// TODO: Instantiate mHardwareKeyboardShortcutController for each user.
- if (mSettings.getCurrentUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+ if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
} else {
mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
- mSettings.getMethodMap(), mSettings.getCurrentUserId());
+ mSettings.getMethodMap(), mSettings.getUserId());
}
sendOnNavButtonFlagsChangedLocked();
@@ -5260,7 +5255,7 @@
// Notify InputMethodListListeners of the new installed InputMethods.
final List<InputMethodInfo> inputMethodList = mSettings.getMethodList();
mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED,
- mSettings.getCurrentUserId(), 0 /* unused */, inputMethodList).sendToTarget();
+ mSettings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget();
}
@GuardedBy("ImfLock.class")
@@ -5330,9 +5325,9 @@
return false;
} else {
final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
- .getEnabledInputMethodsAndSubtypeListLocked();
+ .getEnabledInputMethodsAndSubtypeList();
StringBuilder builder = new StringBuilder();
- if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+ if (mSettings.buildAndPutEnabledInputMethodsStrRemovingId(
builder, enabledInputMethodsList, id)) {
if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
// Disabled input method is currently selected, switch to another one.
@@ -5346,7 +5341,7 @@
// new default one but only update the settings.
InputMethodInfo newDefaultIme =
InputMethodInfoUtils.getMostApplicableDefaultIME(
- mSettings.getEnabledInputMethodListLocked());
+ mSettings.getEnabledInputMethodList());
mSettings.putSelectedDefaultDeviceInputMethod(
newDefaultIme == null ? "" : newDefaultIme.getId());
}
@@ -5381,7 +5376,7 @@
mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
}
}
- notifyInputMethodSubtypeChangedLocked(mSettings.getCurrentUserId(), imi, mCurrentSubtype);
+ notifyInputMethodSubtypeChangedLocked(mSettings.getUserId(), imi, mCurrentSubtype);
if (!setSubtypeOnly) {
// Set InputMethod here
@@ -5395,7 +5390,7 @@
int lastSubtypeId = NOT_A_SUBTYPE_ID;
// newDefaultIme is empty when there is no candidate for the selected IME.
if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
- String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
+ String subtypeHashCode = mSettings.getLastSubtypeForInputMethod(newDefaultIme);
if (subtypeHashCode != null) {
try {
lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
@@ -5422,7 +5417,7 @@
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
- if (mSettings.getCurrentUserId() == userId) {
+ if (mSettings.getUserId() == userId) {
return getCurrentInputMethodSubtypeLocked();
}
@@ -5460,19 +5455,19 @@
// the most applicable subtype from explicitly or implicitly enabled
// subtypes.
List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
- mSettings.getEnabledInputMethodSubtypeListLocked(imi, true);
+ mSettings.getEnabledInputMethodSubtypeList(imi, true);
// If there is only one explicitly or implicitly enabled subtype,
// just returns it.
if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
} else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
- final String locale = SystemLocaleWrapper.get(mSettings.getCurrentUserId())
+ final String locale = SystemLocaleWrapper.get(mSettings.getUserId())
.get(0).toString();
- mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
+ mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtype(
explicitlyOrImplicitlyEnabledSubtypes,
SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
if (mCurrentSubtype == null) {
- mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
+ mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtype(
explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
}
}
@@ -5490,7 +5485,7 @@
@GuardedBy("ImfLock.class")
private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
final InputMethodSettings settings;
- if (userId == mSettings.getCurrentUserId()) {
+ if (userId == mSettings.getUserId()) {
settings = mSettings;
} else {
final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
@@ -5512,9 +5507,9 @@
@GuardedBy("ImfLock.class")
private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
- if (userId == mSettings.getCurrentUserId()) {
+ if (userId == mSettings.getUserId()) {
if (!mSettings.getMethodMap().containsKey(imeId)
- || !mSettings.getEnabledInputMethodListLocked()
+ || !mSettings.getEnabledInputMethodList()
.contains(mSettings.getMethodMap().get(imeId))) {
return false; // IME is not found or not enabled.
}
@@ -5523,7 +5518,7 @@
}
final InputMethodSettings settings = queryMethodMapForUser(userId);
if (!settings.getMethodMap().containsKey(imeId)
- || !settings.getEnabledInputMethodListLocked().contains(
+ || !settings.getEnabledInputMethodList().contains(
settings.getMethodMap().get(imeId))) {
return false; // IME is not found or not enabled.
}
@@ -5654,7 +5649,7 @@
@Override
public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) {
synchronized (ImfLock.class) {
- if (userId == mSettings.getCurrentUserId()) {
+ if (userId == mSettings.getUserId()) {
if (!mSettings.getMethodMap().containsKey(imeId)) {
return false; // IME is not found.
}
@@ -5673,9 +5668,9 @@
settings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
}
} else {
- settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+ settings.buildAndPutEnabledInputMethodsStrRemovingId(
new StringBuilder(),
- settings.getEnabledInputMethodsAndSubtypeListLocked(), imeId);
+ settings.getEnabledInputMethodsAndSubtypeList(), imeId);
}
return true;
}
@@ -6032,7 +6027,7 @@
p.println(" mSwitchingController:");
mSwitchingController.dump(p);
p.println(" mSettings:");
- mSettings.dumpLocked(p, " ");
+ mSettings.dump(p, " ");
p.println(" mStartInputHistory:");
mStartInputHistory.dump(pw, " ");
@@ -6296,7 +6291,7 @@
}
synchronized (ImfLock.class) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
+ mSettings.getUserId(), shellCommand.getErrPrintWriter());
try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
for (int userId : userIds) {
final List<InputMethodInfo> methods = all
@@ -6341,7 +6336,7 @@
PrintWriter error = shellCommand.getErrPrintWriter()) {
synchronized (ImfLock.class) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
+ mSettings.getUserId(), shellCommand.getErrPrintWriter());
for (int userId : userIds) {
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
@@ -6400,7 +6395,7 @@
PrintWriter error) {
boolean failedToEnableUnknownIme = false;
boolean previouslyEnabled = false;
- if (userId == mSettings.getCurrentUserId()) {
+ if (userId == mSettings.getUserId()) {
if (enabled && !mSettings.getMethodMap().containsKey(imeId)) {
failedToEnableUnknownIme = true;
} else {
@@ -6422,9 +6417,9 @@
}
} else {
previouslyEnabled =
- settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+ settings.buildAndPutEnabledInputMethodsStrRemovingId(
new StringBuilder(),
- settings.getEnabledInputMethodsAndSubtypeListLocked(), imeId);
+ settings.getEnabledInputMethodsAndSubtypeList(), imeId);
}
}
if (failedToEnableUnknownIme) {
@@ -6462,7 +6457,7 @@
PrintWriter error = shellCommand.getErrPrintWriter()) {
synchronized (ImfLock.class) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
+ mSettings.getUserId(), shellCommand.getErrPrintWriter());
for (int userId : userIds) {
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
@@ -6502,7 +6497,7 @@
synchronized (ImfLock.class) {
try (PrintWriter out = shellCommand.getOutPrintWriter()) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
+ mSettings.getUserId(), shellCommand.getErrPrintWriter());
for (int userId : userIds) {
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
@@ -6514,14 +6509,14 @@
}
final String nextIme;
final List<InputMethodInfo> nextEnabledImes;
- if (userId == mSettings.getCurrentUserId()) {
+ if (userId == mSettings.getUserId()) {
hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
0 /* flags */, null /* resultReceiver */,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
mBindingController.unbindCurrentMethod();
// Enable default IMEs, disable others
- var toDisable = mSettings.getEnabledInputMethodListLocked();
+ var toDisable = mSettings.getEnabledInputMethodList();
var defaultEnabled = InputMethodInfoUtils.getDefaultEnabledImes(
mContext, mSettings.getMethodList());
toDisable.removeAll(defaultEnabled);
@@ -6537,10 +6532,10 @@
}
updateInputMethodsFromSettingsLocked(true /* enabledMayChange */);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
- getPackageManagerForUser(mContext, mSettings.getCurrentUserId()),
- mSettings.getEnabledInputMethodListLocked());
+ getPackageManagerForUser(mContext, mSettings.getUserId()),
+ mSettings.getEnabledInputMethodList());
nextIme = mSettings.getSelectedInputMethod();
- nextEnabledImes = mSettings.getEnabledInputMethodListLocked();
+ nextEnabledImes = mSettings.getEnabledInputMethodList();
} else {
final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
new ArrayMap<>();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index c9752fb..a51002b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -63,7 +63,7 @@
private final List<InputMethodInfo> mMethodList;
@UserIdInt
- private final int mCurrentUserId;
+ private final int mUserId;
private static void buildEnabledInputMethodsSettingString(
StringBuilder builder, Pair<String, ArrayList<String>> ime) {
@@ -87,7 +87,7 @@
private InputMethodSettings(InputMethodMap methodMap, @UserIdInt int userId) {
mMethodMap = methodMap;
mMethodList = methodMap.values();
- mCurrentUserId = userId;
+ mUserId = userId;
String ime = getSelectedInputMethod();
String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
@@ -109,48 +109,48 @@
}
private void putString(@NonNull String key, @Nullable String str) {
- SecureSettingsWrapper.putString(key, str, mCurrentUserId);
+ SecureSettingsWrapper.putString(key, str, mUserId);
}
@Nullable
private String getString(@NonNull String key, @Nullable String defaultValue) {
- return SecureSettingsWrapper.getString(key, defaultValue, mCurrentUserId);
+ return SecureSettingsWrapper.getString(key, defaultValue, mUserId);
}
private void putInt(String key, int value) {
- SecureSettingsWrapper.putInt(key, value, mCurrentUserId);
+ SecureSettingsWrapper.putInt(key, value, mUserId);
}
private int getInt(String key, int defaultValue) {
- return SecureSettingsWrapper.getInt(key, defaultValue, mCurrentUserId);
+ return SecureSettingsWrapper.getInt(key, defaultValue, mUserId);
}
- ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
- return getEnabledInputMethodListWithFilterLocked(null /* matchingCondition */);
+ ArrayList<InputMethodInfo> getEnabledInputMethodList() {
+ return getEnabledInputMethodListWithFilter(null /* matchingCondition */);
}
@NonNull
- ArrayList<InputMethodInfo> getEnabledInputMethodListWithFilterLocked(
+ ArrayList<InputMethodInfo> getEnabledInputMethodListWithFilter(
@Nullable Predicate<InputMethodInfo> matchingCondition) {
- return createEnabledInputMethodListLocked(
- getEnabledInputMethodsAndSubtypeListLocked(), matchingCondition);
+ return createEnabledInputMethodList(
+ getEnabledInputMethodsAndSubtypeList(), matchingCondition);
}
- List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
+ List<InputMethodSubtype> getEnabledInputMethodSubtypeList(
InputMethodInfo imi, boolean allowsImplicitlyEnabledSubtypes) {
List<InputMethodSubtype> enabledSubtypes =
- getEnabledInputMethodSubtypeListLocked(imi);
+ getEnabledInputMethodSubtypeList(imi);
if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) {
- enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
- SystemLocaleWrapper.get(mCurrentUserId), imi);
+ enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypes(
+ SystemLocaleWrapper.get(mUserId), imi);
}
return InputMethodSubtype.sort(imi, enabledSubtypes);
}
- List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) {
- final List<Pair<String, ArrayList<String>>> imsList =
- getEnabledInputMethodsAndSubtypeListLocked();
- final List<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
+ List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi) {
+ List<Pair<String, ArrayList<String>>> imsList =
+ getEnabledInputMethodsAndSubtypeList();
+ ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
if (imi != null) {
for (int i = 0; i < imsList.size(); ++i) {
final Pair<String, ArrayList<String>> imsPair = imsList.get(i);
@@ -173,7 +173,7 @@
return enabledSubtypes;
}
- List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
+ List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeList() {
final String enabledInputMethodsStr = getEnabledInputMethodsStr();
final TextUtils.SimpleStringSplitter inputMethodSplitter =
new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
@@ -205,7 +205,7 @@
*
* @return the specified id was removed or not.
*/
- boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+ boolean buildAndPutEnabledInputMethodsStrRemovingId(
StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
boolean isRemoved = false;
boolean needsAppendSeparator = false;
@@ -233,7 +233,7 @@
return isRemoved;
}
- private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
+ private ArrayList<InputMethodInfo> createEnabledInputMethodList(
List<Pair<String, ArrayList<String>>> imsList,
Predicate<InputMethodInfo> matchingCondition) {
final ArrayList<InputMethodInfo> res = new ArrayList<>();
@@ -295,7 +295,7 @@
}
private void addSubtypeToHistory(String imeId, String subtypeId) {
- final List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+ final List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistory();
for (int i = 0; i < subtypeHistory.size(); ++i) {
final Pair<String, String> ime = subtypeHistory.get(i);
if (ime.first.equals(imeId)) {
@@ -327,14 +327,14 @@
}
}
- Pair<String, String> getLastInputMethodAndSubtypeLocked() {
+ Pair<String, String> getLastInputMethodAndSubtype() {
// Gets the first one from the history
- return getLastSubtypeForInputMethodLockedInternal(null);
+ return getLastSubtypeForInputMethodInternal(null);
}
@Nullable
- InputMethodSubtype getLastInputMethodSubtypeLocked() {
- final Pair<String, String> lastIme = getLastInputMethodAndSubtypeLocked();
+ InputMethodSubtype getLastInputMethodSubtype() {
+ final Pair<String, String> lastIme = getLastInputMethodAndSubtype();
// TODO: Handle the case of the last IME with no subtypes
if (lastIme == null || TextUtils.isEmpty(lastIme.first)
|| TextUtils.isEmpty(lastIme.second)) {
@@ -355,8 +355,8 @@
}
}
- String getLastSubtypeForInputMethodLocked(String imeId) {
- Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
+ String getLastSubtypeForInputMethod(String imeId) {
+ Pair<String, String> ime = getLastSubtypeForInputMethodInternal(imeId);
if (ime != null) {
return ime.second;
} else {
@@ -364,10 +364,10 @@
}
}
- private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
+ private Pair<String, String> getLastSubtypeForInputMethodInternal(String imeId) {
final List<Pair<String, ArrayList<String>>> enabledImes =
- getEnabledInputMethodsAndSubtypeListLocked();
- final List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+ getEnabledInputMethodsAndSubtypeList();
+ final List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistory();
for (int i = 0; i < subtypeHistory.size(); ++i) {
final Pair<String, String> imeAndSubtype = subtypeHistory.get(i);
final String imeInTheHistory = imeAndSubtype.first;
@@ -375,7 +375,7 @@
if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
final String subtypeInTheHistory = imeAndSubtype.second;
final String subtypeHashCode =
- getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
+ getEnabledSubtypeHashCodeForInputMethodAndSubtype(
enabledImes, imeInTheHistory, subtypeInTheHistory);
if (!TextUtils.isEmpty(subtypeHashCode)) {
if (DEBUG) {
@@ -392,9 +392,9 @@
return null;
}
- private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
+ private String getEnabledSubtypeHashCodeForInputMethodAndSubtype(List<Pair<String,
ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
- final LocaleList localeList = SystemLocaleWrapper.get(mCurrentUserId);
+ final LocaleList localeList = SystemLocaleWrapper.get(mUserId);
for (int i = 0; i < enabledImes.size(); ++i) {
final Pair<String, ArrayList<String>> enabledIme = enabledImes.get(i);
if (enabledIme.first.equals(imeId)) {
@@ -407,7 +407,7 @@
// are enabled implicitly, so needs to treat them to be enabled.
if (imi != null && imi.getSubtypeCount() > 0) {
List<InputMethodSubtype> implicitlyEnabledSubtypes =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(localeList,
+ SubtypeUtils.getImplicitlyApplicableSubtypes(localeList,
imi);
final int numSubtypes = implicitlyEnabledSubtypes.size();
for (int j = 0; j < numSubtypes; ++j) {
@@ -444,7 +444,7 @@
return null;
}
- private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
+ private List<Pair<String, String>> loadInputMethodAndSubtypeHistory() {
ArrayList<Pair<String, String>> imsList = new ArrayList<>();
final String subtypeHistoryStr = getSubtypeHistoryStr();
if (TextUtils.isEmpty(subtypeHistoryStr)) {
@@ -483,16 +483,14 @@
void putSelectedInputMethod(String imeId) {
if (DEBUG) {
- Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
- + mCurrentUserId);
+ Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", " + mUserId);
}
putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
}
void putSelectedSubtype(int subtypeId) {
if (DEBUG) {
- Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
- + mCurrentUserId);
+ Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", " + mUserId);
}
putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
}
@@ -510,24 +508,21 @@
String getSelectedDefaultDeviceInputMethod() {
final String imi = getString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null);
if (DEBUG) {
- Slog.d(TAG, "getSelectedDefaultDeviceInputMethodStr: " + imi + ", "
- + mCurrentUserId);
+ Slog.d(TAG, "getSelectedDefaultDeviceInputMethodStr: " + imi + ", " + mUserId);
}
return imi;
}
void putSelectedDefaultDeviceInputMethod(String imeId) {
if (DEBUG) {
- Slog.d(TAG, "putSelectedDefaultDeviceInputMethodStr: " + imeId + ", "
- + mCurrentUserId);
+ Slog.d(TAG, "putSelectedDefaultDeviceInputMethodStr: " + imeId + ", " + mUserId);
}
putString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, imeId);
}
void putDefaultVoiceInputMethod(String imeId) {
if (DEBUG) {
- Slog.d(TAG,
- "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
+ Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mUserId);
}
putString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, imeId);
}
@@ -551,8 +546,8 @@
}
@UserIdInt
- public int getCurrentUserId() {
- return mCurrentUserId;
+ public int getUserId() {
+ return mUserId;
}
int getSelectedInputMethodSubtypeId(String selectedImiId) {
@@ -607,7 +602,7 @@
// If there are no selected subtypes, the framework will try to find the most applicable
// subtype from explicitly or implicitly enabled subtypes.
final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
- getEnabledInputMethodSubtypeListLocked(imi, true);
+ getEnabledInputMethodSubtypeList(imi, true);
// If there is only one explicitly or implicitly enabled subtype, just returns it.
if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) {
return null;
@@ -615,14 +610,14 @@
if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
return explicitlyOrImplicitlyEnabledSubtypes.get(0);
}
- final String locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
- final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
+ final String locale = SystemLocaleWrapper.get(mUserId).get(0).toString();
+ final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtype(
explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
locale, true);
if (subtype != null) {
return subtype;
}
- return SubtypeUtils.findLastResortApplicableSubtypeLocked(
+ return SubtypeUtils.findLastResortApplicableSubtype(
explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
}
@@ -644,7 +639,7 @@
} else {
additionalSubtypeMap.put(imi.getId(), subtypes);
}
- AdditionalSubtypeUtils.save(additionalSubtypeMap, mMethodMap, getCurrentUserId());
+ AdditionalSubtypeUtils.save(additionalSubtypeMap, mMethodMap, getUserId());
return true;
}
@@ -714,7 +709,7 @@
return sb.toString();
}
- void dumpLocked(final Printer pw, final String prefix) {
- pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
+ void dump(final Printer pw, final String prefix) {
+ pw.println(prefix + "mUserId=" + mUserId);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index b37d040..1379d16 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -165,7 +165,7 @@
final String mSystemLocaleStr = SystemLocaleWrapper.get(userId).get(0).toLanguageTag();
final InputMethodSettings settings = InputMethodSettings.create(methodMap, userId);
- final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodListLocked();
+ final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodList();
if (imis.isEmpty()) {
return new ArrayList<>();
}
@@ -183,7 +183,7 @@
continue;
}
final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList =
- settings.getEnabledInputMethodSubtypeListLocked(imi, true);
+ settings.getEnabledInputMethodSubtypeList(imi, true);
final ArraySet<String> enabledSubtypeSet = new ArraySet<>();
for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) {
enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
diff --git a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
index 95df998..3d5c8677 100644
--- a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
@@ -26,7 +26,6 @@
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
@@ -52,7 +51,7 @@
"EnabledWhenDefaultIsNotAsciiCapable";
// A temporary workaround for the performance concerns in
- // #getImplicitlyApplicableSubtypesLocked(Resources, InputMethodInfo).
+ // #getImplicitlyApplicableSubtypes(Resources, InputMethodInfo).
// TODO: Optimize all the critical paths including this one.
// TODO(b/235661780): Make the cache supports multi-users.
private static final Object sCacheLock = new Object();
@@ -121,9 +120,8 @@
private static final LocaleUtils.LocaleExtractor<InputMethodSubtype> sSubtypeToLocale =
source -> source != null ? source.getLocaleObject() : null;
- @VisibleForTesting
@NonNull
- static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
+ static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypes(
@NonNull LocaleList systemLocales, InputMethodInfo imi) {
synchronized (sCacheLock) {
// We intentionally do not use InputMethodInfo#equals(InputMethodInfo) here because
@@ -133,11 +131,11 @@
}
}
- // Note: Only resource info in "res" is used in getImplicitlyApplicableSubtypesLockedImpl().
- // TODO: Refactor getImplicitlyApplicableSubtypesLockedImpl() so that it can receive
+ // Note: Only resource info in "res" is used in getImplicitlyApplicableSubtypesImpl().
+ // TODO: Refactor getImplicitlyApplicableSubtypesImpl() so that it can receive
// LocaleList rather than Resource.
final ArrayList<InputMethodSubtype> result =
- getImplicitlyApplicableSubtypesLockedImpl(systemLocales, imi);
+ getImplicitlyApplicableSubtypesImpl(systemLocales, imi);
synchronized (sCacheLock) {
// Both LocaleList and InputMethodInfo are immutable. No need to copy them here.
sCachedSystemLocales = systemLocales;
@@ -147,7 +145,7 @@
return result;
}
- private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLockedImpl(
+ private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesImpl(
@NonNull LocaleList systemLocales, InputMethodInfo imi) {
final List<InputMethodSubtype> subtypes = getSubtypes(imi);
final String systemLocale = systemLocales.get(0).toString();
@@ -215,7 +213,7 @@
}
if (applicableSubtypes.isEmpty()) {
- InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
+ InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtype(
subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
if (lastResortKeyboardSubtype != null) {
applicableSubtypes.add(lastResortKeyboardSubtype);
@@ -244,7 +242,7 @@
*
* @return the most applicable subtypeId
*/
- static InputMethodSubtype findLastResortApplicableSubtypeLocked(
+ static InputMethodSubtype findLastResortApplicableSubtype(
List<InputMethodSubtype> subtypes, String mode, @NonNull String locale,
boolean canIgnoreLocaleAsLastResort) {
if (subtypes == null || subtypes.isEmpty()) {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 542b3b0..a06607b 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -16,6 +16,7 @@
package com.android.server.locksettings;
+import static android.security.Flags.reportPrimaryAuthAttempts;
import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS;
@@ -92,6 +93,7 @@
import android.os.IBinder;
import android.os.IProgressListener;
import android.os.Process;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -137,6 +139,7 @@
import com.android.internal.util.Preconditions;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.ILockSettingsStateListener;
import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
import com.android.internal.widget.LockPatternUtils;
@@ -329,6 +332,9 @@
private HashMap<UserHandle, UserManager> mUserManagerCache = new HashMap<>();
+ private final RemoteCallbackList<ILockSettingsStateListener> mLockSettingsStateListeners =
+ new RemoteCallbackList<>();
+
// This class manages life cycle events for encrypted users on File Based Encryption (FBE)
// devices. The most basic of these is to show/hide notifications about missing features until
// the user unlocks the account and credential-encrypted storage is available.
@@ -2364,9 +2370,37 @@
requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
}
}
+ if (reportPrimaryAuthAttempts()) {
+ final boolean success =
+ response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK;
+ notifyLockSettingsStateListeners(success, userId);
+ }
return response;
}
+ private void notifyLockSettingsStateListeners(boolean success, int userId) {
+ int i = mLockSettingsStateListeners.beginBroadcast();
+ try {
+ while (i > 0) {
+ i--;
+ try {
+ if (success) {
+ mLockSettingsStateListeners.getBroadcastItem(i)
+ .onAuthenticationSucceeded(userId);
+ } else {
+ mLockSettingsStateListeners.getBroadcastItem(i)
+ .onAuthenticationFailed(userId);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception while notifying LockSettingsStateListener:"
+ + " success = " + success + ", userId = " + userId, e);
+ }
+ }
+ } finally {
+ mLockSettingsStateListeners.finishBroadcast();
+ }
+ }
+
@Override
public VerifyCredentialResponse verifyTiedProfileChallenge(LockscreenCredential credential,
int userId, @LockPatternUtils.VerifyFlag int flags) {
@@ -3684,6 +3718,18 @@
public void refreshStrongAuthTimeout(int userId) {
mStrongAuth.refreshStrongAuthTimeout(userId);
}
+
+ @Override
+ public void registerLockSettingsStateListener(
+ @NonNull ILockSettingsStateListener listener) {
+ mLockSettingsStateListeners.register(listener);
+ }
+
+ @Override
+ public void unregisterLockSettingsStateListener(
+ @NonNull ILockSettingsStateListener listener) {
+ mLockSettingsStateListeners.unregister(listener);
+ }
}
private class RebootEscrowCallbacks implements RebootEscrowManager.Callbacks {
diff --git a/services/core/java/com/android/server/net/Android.bp b/services/core/java/com/android/server/net/Android.bp
new file mode 100644
index 0000000..71d8e6b
--- /dev/null
+++ b/services/core/java/com/android/server/net/Android.bp
@@ -0,0 +1,10 @@
+aconfig_declarations {
+ name: "net_flags",
+ package: "com.android.server.net",
+ srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "net_flags_lib",
+ aconfig_declarations: "net_flags",
+}
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index 681d1a0..d25f529 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -17,6 +17,7 @@
package com.android.server.net;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -27,6 +28,7 @@
import static android.net.INetd.FIREWALL_DENYLIST;
import static android.net.INetd.FIREWALL_RULE_ALLOW;
import static android.net.INetd.FIREWALL_RULE_DENY;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
@@ -187,6 +189,13 @@
*/
@GuardedBy("mRulesLock")
private final SparseIntArray mUidFirewallLowPowerStandbyRules = new SparseIntArray();
+
+ /**
+ * Contains the per-UID firewall rules that are used when Background chain is enabled.
+ */
+ @GuardedBy("mRulesLock")
+ private final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray();
+
/** Set of states for the child firewall chains. True if the chain is active. */
@GuardedBy("mRulesLock")
final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
@@ -449,13 +458,15 @@
syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, "powersave ");
syncFirewallChainLocked(FIREWALL_CHAIN_RESTRICTED, "restricted ");
syncFirewallChainLocked(FIREWALL_CHAIN_LOW_POWER_STANDBY, "low power standby ");
+ syncFirewallChainLocked(FIREWALL_CHAIN_BACKGROUND, FIREWALL_CHAIN_NAME_BACKGROUND);
final int[] chains = {
FIREWALL_CHAIN_STANDBY,
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_POWERSAVE,
FIREWALL_CHAIN_RESTRICTED,
- FIREWALL_CHAIN_LOW_POWER_STANDBY
+ FIREWALL_CHAIN_LOW_POWER_STANDBY,
+ FIREWALL_CHAIN_BACKGROUND,
};
for (int chain : chains) {
@@ -1206,6 +1217,8 @@
return FIREWALL_CHAIN_NAME_RESTRICTED;
case FIREWALL_CHAIN_LOW_POWER_STANDBY:
return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
+ case FIREWALL_CHAIN_BACKGROUND:
+ return FIREWALL_CHAIN_NAME_BACKGROUND;
default:
throw new IllegalArgumentException("Bad child chain: " + chain);
}
@@ -1223,6 +1236,8 @@
return FIREWALL_ALLOWLIST;
case FIREWALL_CHAIN_LOW_POWER_STANDBY:
return FIREWALL_ALLOWLIST;
+ case FIREWALL_CHAIN_BACKGROUND:
+ return FIREWALL_ALLOWLIST;
default:
return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST;
}
@@ -1343,6 +1358,8 @@
return mUidFirewallRestrictedRules;
case FIREWALL_CHAIN_LOW_POWER_STANDBY:
return mUidFirewallLowPowerStandbyRules;
+ case FIREWALL_CHAIN_BACKGROUND:
+ return mUidFirewallBackgroundRules;
case FIREWALL_CHAIN_NONE:
return mUidFirewallRules;
default:
@@ -1395,6 +1412,10 @@
pw.println(getFirewallChainState(FIREWALL_CHAIN_LOW_POWER_STANDBY));
dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY,
mUidFirewallLowPowerStandbyRules);
+
+ pw.print("UID firewall background chain enabled: ");
+ pw.println(getFirewallChainState(FIREWALL_CHAIN_BACKGROUND));
+ dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_BACKGROUND, mUidFirewallBackgroundRules);
}
pw.print("Firewall enabled: "); pw.println(mFirewallEnabled);
@@ -1494,6 +1515,11 @@
if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of low power standby");
return true;
}
+ if (getFirewallChainState(FIREWALL_CHAIN_BACKGROUND)
+ && mUidFirewallBackgroundRules.get(uid) != FIREWALL_RULE_ALLOW) {
+ if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because it is in background");
+ return true;
+ }
if (mUidRejectOnMetered.get(uid)) {
if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data"
+ " in the background");
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index d7188c7..8e2d778 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -16,6 +16,7 @@
package com.android.server.net;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -24,6 +25,7 @@
import static android.net.INetd.FIREWALL_RULE_ALLOW;
import static android.net.INetd.FIREWALL_RULE_DENY;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
@@ -389,6 +391,8 @@
return FIREWALL_CHAIN_NAME_RESTRICTED;
case FIREWALL_CHAIN_LOW_POWER_STANDBY:
return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
+ case FIREWALL_CHAIN_BACKGROUND:
+ return FIREWALL_CHAIN_NAME_BACKGROUND;
default:
return String.valueOf(chain);
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index e4e48bd..f9ffb1c 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -47,6 +47,7 @@
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
@@ -54,6 +55,7 @@
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -77,6 +79,7 @@
import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_NOT_IN_BACKGROUND;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLIST;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
@@ -96,6 +99,7 @@
import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED;
import static android.net.NetworkPolicyManager.allowedReasonsToString;
import static android.net.NetworkPolicyManager.blockedReasonsToString;
+import static android.net.NetworkPolicyManager.isProcStateAllowedNetworkWhileBackground;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileInLowPowerStandby;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
@@ -202,12 +206,12 @@
import android.os.MessageQueue.IdleHandler;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
+import android.os.PowerExemptionManager;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.PowerManager;
import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
-import android.os.PowerWhitelistManager;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -243,6 +247,7 @@
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.SparseSetArray;
+import android.util.TimeUtils;
import android.util.Xml;
import com.android.internal.R;
@@ -458,6 +463,12 @@
*/
private static final int MSG_UIDS_BLOCKED_REASONS_CHANGED = 23;
+ /**
+ * Message to update background restriction rules for uids that should lose network access
+ * due to being in the background.
+ */
+ private static final int MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS = 24;
+
private static final int UID_MSG_STATE_CHANGED = 100;
private static final int UID_MSG_GONE = 101;
@@ -476,7 +487,7 @@
private ConnectivityManager mConnManager;
private PowerManagerInternal mPowerManagerInternal;
- private PowerWhitelistManager mPowerWhitelistManager;
+ private PowerExemptionManager mPowerExemptionManager;
@NonNull
private final Dependencies mDeps;
@@ -491,6 +502,12 @@
// Denotes the status of restrict background read from disk.
private boolean mLoadedRestrictBackground;
+ /**
+ * Whether or not network for apps in proc-states greater than
+ * {@link NetworkPolicyManager#BACKGROUND_THRESHOLD_STATE} is always blocked.
+ */
+ private boolean mBackgroundNetworkRestricted;
+
// See main javadoc for instructions on how to use these locks.
final Object mUidRulesFirstLock = new Object();
final Object mNetworkPoliciesSecondLock = new Object();
@@ -515,6 +532,15 @@
private volatile boolean mNetworkManagerReady;
+ /**
+ * Delay after which a uid going into a process state greater than or equal to
+ * {@link NetworkPolicyManager#BACKGROUND_THRESHOLD_STATE} will lose network access.
+ * The delay is meant to prevent churn due to quick process-state changes.
+ * Note that there is no delay while granting network access.
+ */
+ @VisibleForTesting
+ long mBackgroundRestrictionDelayMs = TimeUnit.SECONDS.toMillis(5);
+
/** Defined network policies. */
@GuardedBy("mNetworkPoliciesSecondLock")
final ArrayMap<NetworkTemplate, NetworkPolicy> mNetworkPolicy = new ArrayMap<>();
@@ -546,6 +572,8 @@
@GuardedBy("mUidRulesFirstLock")
final SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
@GuardedBy("mUidRulesFirstLock")
+ final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray();
+ @GuardedBy("mUidRulesFirstLock")
final SparseIntArray mUidFirewallRestrictedModeRules = new SparseIntArray();
@GuardedBy("mUidRulesFirstLock")
final SparseIntArray mUidFirewallLowPowerStandbyModeRules = new SparseIntArray();
@@ -625,6 +653,14 @@
@GuardedBy("mUidRulesFirstLock")
private final SparseArray<UidBlockedState> mTmpUidBlockedState = new SparseArray<>();
+ /**
+ * Stores a map of uids to the time their transition to background is considered complete. They
+ * will lose network access after this time. This is used to prevent churn in rules due to quick
+ * process-state transitions.
+ */
+ @GuardedBy("mUidRulesFirstLock")
+ private final SparseLongArray mBackgroundTransitioningUids = new SparseLongArray();
+
/** Map from network ID to last observed meteredness state */
@GuardedBy("mNetworkPoliciesSecondLock")
private final SparseBooleanArray mNetworkMetered = new SparseBooleanArray();
@@ -824,7 +860,7 @@
mContext = Objects.requireNonNull(context, "missing context");
mActivityManager = Objects.requireNonNull(activityManager, "missing activityManager");
mNetworkManager = Objects.requireNonNull(networkManagement, "missing networkManagement");
- mPowerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class);
+ mPowerExemptionManager = mContext.getSystemService(PowerExemptionManager.class);
mClock = Objects.requireNonNull(clock, "missing Clock");
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
@@ -860,15 +896,15 @@
@GuardedBy("mUidRulesFirstLock")
private void updatePowerSaveAllowlistUL() {
- int[] whitelist = mPowerWhitelistManager.getWhitelistedAppIds(/* includingIdle */ false);
+ int[] allowlist = mPowerExemptionManager.getAllowListedAppIds(/* includingIdle */ false);
mPowerSaveWhitelistExceptIdleAppIds.clear();
- for (int uid : whitelist) {
+ for (int uid : allowlist) {
mPowerSaveWhitelistExceptIdleAppIds.put(uid, true);
}
- whitelist = mPowerWhitelistManager.getWhitelistedAppIds(/* includingIdle */ true);
+ allowlist = mPowerExemptionManager.getAllowListedAppIds(/* includingIdle */ true);
mPowerSaveWhitelistAppIds.clear();
- for (int uid : whitelist) {
+ for (int uid : allowlist) {
mPowerSaveWhitelistAppIds.put(uid, true);
}
}
@@ -1018,6 +1054,14 @@
writePolicyAL();
}
+ // The flag is boot-stable.
+ mBackgroundNetworkRestricted = Flags.networkBlockedForTopSleepingAndAbove();
+ if (mBackgroundNetworkRestricted) {
+ // Firewall rules and UidBlockedState will get updated in
+ // updateRulesForGlobalChangeAL below.
+ enableFirewallChainUL(FIREWALL_CHAIN_BACKGROUND, true);
+ }
+
setRestrictBackgroundUL(mLoadedRestrictBackground, "init_service");
updateRulesForGlobalChangeAL(false);
updateNotificationsNL();
@@ -1028,17 +1072,22 @@
final int changes = ActivityManager.UID_OBSERVER_PROCSTATE
| ActivityManager.UID_OBSERVER_GONE
| ActivityManager.UID_OBSERVER_CAPABILITY;
+
+ final int cutpoint = mBackgroundNetworkRestricted ? PROCESS_STATE_UNKNOWN
+ : NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE;
+ // TODO (b/319728914): Filter out the unnecessary changes when using no cutpoint.
+
mActivityManagerInternal.registerNetworkPolicyUidObserver(mUidObserver, changes,
- NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE, "android");
+ cutpoint, "android");
mNetworkManager.registerObserver(mAlertObserver);
} catch (RemoteException e) {
// ignored; both services live in system_server
}
// listen for changes to power save allowlist
- final IntentFilter whitelistFilter = new IntentFilter(
+ final IntentFilter allowlistFilter = new IntentFilter(
PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
- mContext.registerReceiver(mPowerSaveWhitelistReceiver, whitelistFilter, null, mHandler);
+ mContext.registerReceiver(mPowerSaveAllowlistReceiver, allowlistFilter, null, mHandler);
// watch for network interfaces to be claimed
final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION);
@@ -1189,12 +1238,15 @@
}
}
- final private BroadcastReceiver mPowerSaveWhitelistReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mPowerSaveAllowlistReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// on background handler thread, and POWER_SAVE_WHITELIST_CHANGED is protected
synchronized (mUidRulesFirstLock) {
updatePowerSaveAllowlistUL();
+ if (mBackgroundNetworkRestricted) {
+ updateRulesForBackgroundChainUL();
+ }
updateRulesForRestrictPowerUL();
updateRulesForAppIdleUL();
}
@@ -3914,6 +3966,11 @@
}
fout.println();
+ fout.println("Flags:");
+ fout.println("Network blocked for TOP_SLEEPING and above: "
+ + mBackgroundNetworkRestricted);
+
+ fout.println();
fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode);
fout.println("mRestrictBackgroundBeforeBsm: " + mRestrictBackgroundBeforeBsm);
fout.println("mLoadedRestrictBackground: " + mLoadedRestrictBackground);
@@ -4055,6 +4112,22 @@
fout.decreaseIndent();
}
+ size = mBackgroundTransitioningUids.size();
+ if (size > 0) {
+ final long nowUptime = SystemClock.uptimeMillis();
+ fout.println("Uids transitioning to background:");
+ fout.increaseIndent();
+ for (int i = 0; i < size; i++) {
+ fout.print("UID=");
+ fout.print(mBackgroundTransitioningUids.keyAt(i));
+ fout.print(", ");
+ TimeUtils.formatDuration(mBackgroundTransitioningUids.valueAt(i), nowUptime,
+ fout);
+ fout.println();
+ }
+ fout.decreaseIndent();
+ }
+
final SparseBooleanArray knownUids = new SparseBooleanArray();
collectKeys(mUidState, knownUids);
synchronized (mUidBlockedState) {
@@ -4182,6 +4255,12 @@
return isProcStateAllowedWhileInLowPowerStandby(uidState);
}
+ @GuardedBy("mUidRulesFirstLock")
+ private boolean isUidExemptFromBackgroundRestrictions(int uid) {
+ return mBackgroundTransitioningUids.indexOfKey(uid) >= 0
+ || isProcStateAllowedNetworkWhileBackground(mUidState.get(uid));
+ }
+
/**
* Process state of UID changed; if needed, will trigger
* {@link #updateRulesForDataUsageRestrictionsUL(int)} and
@@ -4207,6 +4286,8 @@
// state changed, push updated rules
mUidState.put(uid, newUidState);
updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, newUidState);
+
+ boolean updatePowerRestrictionRules = false;
boolean allowedWhileIdleOrPowerSaveModeChanged =
isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState)
!= isProcStateAllowedWhileIdleOrPowerSaveMode(newUidState);
@@ -4218,19 +4299,44 @@
if (mRestrictPower) {
updateRuleForRestrictPowerUL(uid);
}
- updateRulesForPowerRestrictionsUL(uid, procState);
+ updatePowerRestrictionRules = true;
+ }
+ if (mBackgroundNetworkRestricted) {
+ final boolean wasAllowed = isProcStateAllowedNetworkWhileBackground(
+ oldUidState);
+ final boolean isAllowed = isProcStateAllowedNetworkWhileBackground(newUidState);
+ if (!wasAllowed && isAllowed) {
+ mBackgroundTransitioningUids.delete(uid);
+ updateRuleForBackgroundUL(uid);
+ updatePowerRestrictionRules = true;
+ } else if (wasAllowed && !isAllowed) {
+ final long completionTimeMs = SystemClock.uptimeMillis()
+ + mBackgroundRestrictionDelayMs;
+ if (mBackgroundTransitioningUids.indexOfKey(uid) < 0) {
+ // This is just a defensive check in case the upstream code ever makes
+ // multiple calls for the same process state change.
+ mBackgroundTransitioningUids.put(uid, completionTimeMs);
+ }
+ if (!mHandler.hasMessages(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS)) {
+ // Many uids may be in this "transitioning" state at the same time, so
+ // using one message at a time to avoid congestion in the MessageQueue.
+ mHandler.sendEmptyMessageAtTime(
+ MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS, completionTimeMs);
+ }
+ }
}
if (mLowPowerStandbyActive) {
boolean allowedInLpsChanged =
isProcStateAllowedWhileInLowPowerStandby(oldUidState)
!= isProcStateAllowedWhileInLowPowerStandby(newUidState);
if (allowedInLpsChanged) {
- if (!allowedWhileIdleOrPowerSaveModeChanged) {
- updateRulesForPowerRestrictionsUL(uid, procState);
- }
updateRuleForLowPowerStandbyUL(uid);
+ updatePowerRestrictionRules = true;
}
}
+ if (updatePowerRestrictionRules) {
+ updateRulesForPowerRestrictionsUL(uid, procState);
+ }
return true;
}
} finally {
@@ -4253,6 +4359,12 @@
if (mRestrictPower) {
updateRuleForRestrictPowerUL(uid);
}
+ if (mBackgroundNetworkRestricted) {
+ // Uid is no longer running, there is no point in any grace period of network
+ // access during transitions to lower importance proc-states.
+ mBackgroundTransitioningUids.delete(uid);
+ updateRuleForBackgroundUL(uid);
+ }
updateRulesForPowerRestrictionsUL(uid);
if (mLowPowerStandbyActive) {
updateRuleForLowPowerStandbyUL(uid);
@@ -4460,11 +4572,41 @@
}
}
+ /**
+ * Updates the rules for apps allowlisted to use network while in the background.
+ */
+ @GuardedBy("mUidRulesFirstLock")
+ private void updateRulesForBackgroundChainUL() {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForBackgroundChainUL");
+ try {
+ final SparseIntArray uidRules = mUidFirewallBackgroundRules;
+ uidRules.clear();
+
+ final List<UserInfo> users = mUserManager.getUsers();
+ for (int ui = users.size() - 1; ui >= 0; ui--) {
+ final UserInfo user = users.get(ui);
+ updateRulesForAllowlistedAppIds(uidRules, mPowerSaveTempWhitelistAppIds, user.id);
+ updateRulesForAllowlistedAppIds(uidRules, mPowerSaveWhitelistAppIds, user.id);
+ updateRulesForAllowlistedAppIds(uidRules, mPowerSaveWhitelistExceptIdleAppIds,
+ user.id);
+ }
+ for (int i = mUidState.size() - 1; i >= 0; i--) {
+ if (mBackgroundTransitioningUids.indexOfKey(mUidState.keyAt(i)) >= 0
+ || isProcStateAllowedNetworkWhileBackground(mUidState.valueAt(i))) {
+ uidRules.put(mUidState.keyAt(i), FIREWALL_RULE_ALLOW);
+ }
+ }
+ setUidFirewallRulesUL(FIREWALL_CHAIN_BACKGROUND, uidRules);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+ }
+ }
+
private void updateRulesForAllowlistedAppIds(final SparseIntArray uidRules,
- final SparseBooleanArray whitelistedAppIds, int userId) {
- for (int i = whitelistedAppIds.size() - 1; i >= 0; --i) {
- if (whitelistedAppIds.valueAt(i)) {
- final int appId = whitelistedAppIds.keyAt(i);
+ final SparseBooleanArray allowlistedAppIds, int userId) {
+ for (int i = allowlistedAppIds.size() - 1; i >= 0; --i) {
+ if (allowlistedAppIds.valueAt(i)) {
+ final int appId = allowlistedAppIds.keyAt(i);
final int uid = UserHandle.getUid(userId, appId);
uidRules.put(uid, FIREWALL_RULE_ALLOW);
}
@@ -4523,12 +4665,12 @@
@GuardedBy("mUidRulesFirstLock")
private boolean isAllowlistedFromPowerSaveUL(int uid, boolean deviceIdleMode) {
final int appId = UserHandle.getAppId(uid);
- boolean isWhitelisted = mPowerSaveTempWhitelistAppIds.get(appId)
+ boolean allowlisted = mPowerSaveTempWhitelistAppIds.get(appId)
|| mPowerSaveWhitelistAppIds.get(appId);
if (!deviceIdleMode) {
- isWhitelisted = isWhitelisted || isAllowlistedFromPowerSaveExceptIdleUL(uid);
+ allowlisted = allowlisted || isAllowlistedFromPowerSaveExceptIdleUL(uid);
}
- return isWhitelisted;
+ return allowlisted;
}
/**
@@ -4617,6 +4759,38 @@
}
/**
+ * Update firewall rule for a single uid whenever there are any interesting changes in the uid.
+ * Currently, it is called when:
+ * - The uid is added to or removed from power allowlists
+ * - The uid undergoes a process-state change
+ * - A package belonging to this uid is added
+ * - The uid is evicted from memory
+ */
+ @GuardedBy("mUidRulesFirstLock")
+ void updateRuleForBackgroundUL(int uid) {
+ if (!isUidValidForAllowlistRulesUL(uid)) {
+ return;
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRuleForBackgroundUL: " + uid);
+ try {
+ // The uid should be absent from mUidState and mBackgroundTransitioningUids if it is
+ // not running when this method is called. Then, the firewall state will depend on the
+ // allowlist alone. This is the desired behavior.
+ if (isAllowlistedFromPowerSaveUL(uid, false)
+ || isUidExemptFromBackgroundRestrictions(uid)) {
+ setUidFirewallRuleUL(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_ALLOW);
+ if (LOGD) Log.d(TAG, "updateRuleForBackgroundUL ALLOW " + uid);
+ } else {
+ setUidFirewallRuleUL(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DEFAULT);
+ if (LOGD) Log.d(TAG, "updateRuleForBackgroundUL " + uid + " to DEFAULT");
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
+ }
+
+ /**
* Toggle the firewall standby chain and inform listeners if the uid rules have effectively
* changed.
*/
@@ -4663,6 +4837,9 @@
"updateRulesForGlobalChangeAL: " + (restrictedNetworksChanged ? "R" : "-"));
}
try {
+ if (mBackgroundNetworkRestricted) {
+ updateRulesForBackgroundChainUL();
+ }
updateRulesForAppIdleUL();
updateRulesForRestrictPowerUL();
updateRulesForRestrictBackgroundUL();
@@ -4822,6 +4999,9 @@
updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN);
updateRuleForDeviceIdleUL(uid);
updateRuleForRestrictPowerUL(uid);
+ if (mBackgroundNetworkRestricted) {
+ updateRuleForBackgroundUL(uid);
+ }
// Update internal rules.
updateRulesForPowerRestrictionsUL(uid);
}
@@ -4959,6 +5139,8 @@
mUidFirewallStandbyRules.delete(uid);
mUidFirewallDozableRules.delete(uid);
mUidFirewallPowerSaveRules.delete(uid);
+ mUidFirewallBackgroundRules.delete(uid);
+ mBackgroundTransitioningUids.delete(uid);
mPowerSaveWhitelistExceptIdleAppIds.delete(uid);
mPowerSaveWhitelistAppIds.delete(uid);
mPowerSaveTempWhitelistAppIds.delete(uid);
@@ -4992,6 +5174,9 @@
updateRuleForDeviceIdleUL(uid);
updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN);
updateRuleForRestrictPowerUL(uid);
+ if (mBackgroundNetworkRestricted) {
+ updateRuleForBackgroundUL(uid);
+ }
// If the uid has the necessary permissions, then it should be added to the restricted mode
// firewall allowlist.
@@ -5176,7 +5361,6 @@
* Similar to above but ignores idle state if app standby is currently disabled by parole.
*
* @param uid the uid of the app to update rules for
- * @param oldUidRules the current rules for the uid, in order to determine if there's a change
* @param isUidIdle whether uid is idle or not
*/
@GuardedBy("mUidRulesFirstLock")
@@ -5222,6 +5406,7 @@
newBlockedReasons |= (mLowPowerStandbyActive ? BLOCKED_REASON_LOW_POWER_STANDBY : 0);
newBlockedReasons |= (isUidIdle ? BLOCKED_REASON_APP_STANDBY : 0);
newBlockedReasons |= (uidBlockedState.blockedReasons & BLOCKED_REASON_RESTRICTED_MODE);
+ newBlockedReasons |= mBackgroundNetworkRestricted ? BLOCKED_REASON_APP_BACKGROUND : 0;
newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0);
newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0);
@@ -5234,6 +5419,9 @@
& ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS);
newAllowedReasons |= (isAllowlistedFromLowPowerStandbyUL(uid))
? ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST : 0;
+ newAllowedReasons |= (mBackgroundNetworkRestricted
+ && isUidExemptFromBackgroundRestrictions(uid))
+ ? ALLOWED_REASON_NOT_IN_BACKGROUND : 0;
uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
& BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
@@ -5255,7 +5443,7 @@
oldEffectiveBlockedReasons = previousUidBlockedState.effectiveBlockedReasons;
newEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
- uidRules = oldEffectiveBlockedReasons == newEffectiveBlockedReasons
+ uidRules = (oldEffectiveBlockedReasons == newEffectiveBlockedReasons)
? RULE_NONE
: uidBlockedState.deriveUidRules();
}
@@ -5448,6 +5636,28 @@
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
return true;
}
+ case MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS: {
+ final long now = SystemClock.uptimeMillis();
+ long nextCheckTime = Long.MAX_VALUE;
+ synchronized (mUidRulesFirstLock) {
+ for (int i = mBackgroundTransitioningUids.size() - 1; i >= 0; i--) {
+ final long completionTimeMs = mBackgroundTransitioningUids.valueAt(i);
+ if (completionTimeMs > now) {
+ nextCheckTime = Math.min(nextCheckTime, completionTimeMs);
+ continue;
+ }
+ final int uid = mBackgroundTransitioningUids.keyAt(i);
+ mBackgroundTransitioningUids.removeAt(i);
+ updateRuleForBackgroundUL(uid);
+ updateRulesForPowerRestrictionsUL(uid, false);
+ }
+ }
+ if (nextCheckTime < Long.MAX_VALUE) {
+ mHandler.sendEmptyMessageAtTime(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS,
+ nextCheckTime);
+ }
+ return true;
+ }
case MSG_POLICIES_CHANGED: {
final int uid = msg.arg1;
final int policy = msg.arg2;
@@ -5859,6 +6069,8 @@
mUidFirewallRestrictedModeRules.put(uid, rule);
} else if (chain == FIREWALL_CHAIN_LOW_POWER_STANDBY) {
mUidFirewallLowPowerStandbyModeRules.put(uid, rule);
+ } else if (chain == FIREWALL_CHAIN_BACKGROUND) {
+ mUidFirewallBackgroundRules.put(uid, rule);
}
try {
@@ -5915,6 +6127,8 @@
FIREWALL_RULE_DEFAULT);
mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid,
FIREWALL_RULE_DEFAULT);
+ mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, uid,
+ FIREWALL_RULE_DEFAULT);
mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false);
mLogger.meteredAllowlistChanged(uid, false);
mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false);
@@ -6441,10 +6655,12 @@
effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
effectiveBlockedReasons &= ~BLOCKED_REASON_DOZE;
effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+ effectiveBlockedReasons &= ~BLOCKED_REASON_APP_BACKGROUND;
}
if ((allowedReasons & ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST) != 0) {
effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+ effectiveBlockedReasons &= ~BLOCKED_REASON_APP_BACKGROUND;
}
if ((allowedReasons & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS) != 0) {
effectiveBlockedReasons &= ~BLOCKED_REASON_RESTRICTED_MODE;
@@ -6455,19 +6671,24 @@
if ((allowedReasons & ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST) != 0) {
effectiveBlockedReasons &= ~BLOCKED_REASON_LOW_POWER_STANDBY;
}
+ if ((allowedReasons & ALLOWED_REASON_NOT_IN_BACKGROUND) != 0) {
+ effectiveBlockedReasons &= ~BLOCKED_REASON_APP_BACKGROUND;
+ }
return effectiveBlockedReasons;
}
static int getAllowedReasonsForProcState(int procState) {
- if (procState > NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE) {
- return ALLOWED_REASON_NONE;
- } else if (procState <= NetworkPolicyManager.TOP_THRESHOLD_STATE) {
+ if (procState <= NetworkPolicyManager.TOP_THRESHOLD_STATE) {
return ALLOWED_REASON_TOP | ALLOWED_REASON_FOREGROUND
- | ALLOWED_METERED_REASON_FOREGROUND;
- } else {
- return ALLOWED_REASON_FOREGROUND | ALLOWED_METERED_REASON_FOREGROUND;
+ | ALLOWED_METERED_REASON_FOREGROUND | ALLOWED_REASON_NOT_IN_BACKGROUND;
+ } else if (procState <= NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE) {
+ return ALLOWED_REASON_FOREGROUND | ALLOWED_METERED_REASON_FOREGROUND
+ | ALLOWED_REASON_NOT_IN_BACKGROUND;
+ } else if (procState < NetworkPolicyManager.BACKGROUND_THRESHOLD_STATE) {
+ return ALLOWED_REASON_NOT_IN_BACKGROUND;
}
+ return ALLOWED_REASON_NONE;
}
@Override
@@ -6492,6 +6713,7 @@
BLOCKED_REASON_APP_STANDBY,
BLOCKED_REASON_RESTRICTED_MODE,
BLOCKED_REASON_LOW_POWER_STANDBY,
+ BLOCKED_REASON_APP_BACKGROUND,
BLOCKED_METERED_REASON_DATA_SAVER,
BLOCKED_METERED_REASON_USER_RESTRICTED,
BLOCKED_METERED_REASON_ADMIN_DISABLED,
@@ -6505,6 +6727,7 @@
ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST,
ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS,
ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST,
+ ALLOWED_REASON_NOT_IN_BACKGROUND,
ALLOWED_METERED_REASON_USER_EXEMPTED,
ALLOWED_METERED_REASON_SYSTEM,
ALLOWED_METERED_REASON_FOREGROUND,
@@ -6524,6 +6747,8 @@
return "RESTRICTED_MODE";
case BLOCKED_REASON_LOW_POWER_STANDBY:
return "LOW_POWER_STANDBY";
+ case BLOCKED_REASON_APP_BACKGROUND:
+ return "APP_BACKGROUND";
case BLOCKED_METERED_REASON_DATA_SAVER:
return "DATA_SAVER";
case BLOCKED_METERED_REASON_USER_RESTRICTED:
@@ -6554,6 +6779,8 @@
return "RESTRICTED_MODE_PERMISSIONS";
case ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST:
return "LOW_POWER_STANDBY_ALLOWLIST";
+ case ALLOWED_REASON_NOT_IN_BACKGROUND:
+ return "NOT_IN_BACKGROUND";
case ALLOWED_METERED_REASON_USER_EXEMPTED:
return "METERED_USER_EXEMPTED";
case ALLOWED_METERED_REASON_SYSTEM:
@@ -6621,7 +6848,8 @@
int powerBlockedReasons = BLOCKED_REASON_APP_STANDBY
| BLOCKED_REASON_DOZE
| BLOCKED_REASON_BATTERY_SAVER
- | BLOCKED_REASON_LOW_POWER_STANDBY;
+ | BLOCKED_REASON_LOW_POWER_STANDBY
+ | BLOCKED_REASON_APP_BACKGROUND;
if ((effectiveBlockedReasons & powerBlockedReasons) != 0) {
uidRule |= RULE_REJECT_ALL;
} else if ((blockedReasons & powerBlockedReasons) != 0) {
diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig
new file mode 100644
index 0000000..419665a
--- /dev/null
+++ b/services/core/java/com/android/server/net/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.net"
+
+flag {
+ name: "network_blocked_for_top_sleeping_and_above"
+ namespace: "backstage_power"
+ description: "Block network access for apps in a low importance background state"
+ bug: "304347838"
+}
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 25a39cc..86d05d9 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) {
+ if (targetOverlayableName != null && !mPackageManager.getNamedActors().isEmpty()) {
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 b9464d9..a61b03f 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -32,6 +32,7 @@
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;
@@ -362,7 +363,7 @@
defaultPackages.add(packageName);
}
}
- return defaultPackages.toArray(new String[defaultPackages.size()]);
+ return defaultPackages.toArray(new String[0]);
}
private final class OverlayManagerPackageMonitor extends PackageMonitor {
@@ -1143,9 +1144,10 @@
};
private static final class PackageManagerHelperImpl implements PackageManagerHelper {
- private static class PackageStateUsers {
+ private static final class PackageStateUsers {
private PackageState mPackageState;
- private final Set<Integer> mInstalledUsers = new ArraySet<>();
+ private Boolean mDefinesOverlayable = null;
+ private final ArraySet<Integer> mInstalledUsers = new ArraySet<>();
private PackageStateUsers(@NonNull PackageState packageState) {
this.mPackageState = packageState;
}
@@ -1160,7 +1162,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 Set<Integer> mInitializedUsers = new ArraySet<>();
+ private final ArraySet<Integer> mInitializedUsers = new ArraySet<>();
PackageManagerHelperImpl(Context context) {
mContext = context;
@@ -1176,8 +1178,7 @@
*/
@NonNull
public ArrayMap<String, PackageState> initializeForUser(final int userId) {
- if (!mInitializedUsers.contains(userId)) {
- mInitializedUsers.add(userId);
+ if (mInitializedUsers.add(userId)) {
mPackageManagerInternal.forEachPackageState((packageState -> {
if (packageState.getPkg() != null
&& packageState.getUserStateOrDefault(userId).isInstalled()) {
@@ -1196,13 +1197,11 @@
return userPackages;
}
- @Override
- @Nullable
- public PackageState getPackageStateForUser(@NonNull final String packageName,
+ private PackageStateUsers getRawPackageStateForUser(@NonNull final String packageName,
final int userId) {
final PackageStateUsers pkg = mCache.get(packageName);
if (pkg != null && pkg.mInstalledUsers.contains(userId)) {
- return pkg.mPackageState;
+ return pkg;
}
try {
if (!mPackageManager.isPackageAvailable(packageName, userId)) {
@@ -1216,8 +1215,14 @@
return addPackageUser(packageName, userId);
}
- @NonNull
- private PackageState addPackageUser(@NonNull final String packageName,
+ @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,
final int user) {
final PackageState pkg = mPackageManagerInternal.getPackageStateInternal(packageName);
if (pkg == null) {
@@ -1229,20 +1234,20 @@
}
@NonNull
- private PackageState addPackageUser(@NonNull final PackageState pkg,
+ private PackageStateUsers 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 {
+ } else if (pkgUsers.mPackageState != pkg) {
pkgUsers.mPackageState = pkg;
+ pkgUsers.mDefinesOverlayable = null;
}
pkgUsers.mInstalledUsers.add(user);
- return pkgUsers.mPackageState;
+ return pkgUsers;
}
-
@NonNull
private void removePackageUser(@NonNull final String packageName, final int user) {
final PackageStateUsers pkgUsers = mCache.get(packageName);
@@ -1260,15 +1265,15 @@
}
}
- @Nullable
public PackageState onPackageAdded(@NonNull final String packageName, final int userId) {
- return addPackageUser(packageName, userId);
+ final var pu = addPackageUser(packageName, userId);
+ return pu != null ? pu.mPackageState : null;
}
- @Nullable
public PackageState onPackageUpdated(@NonNull final String packageName,
final int userId) {
- return addPackageUser(packageName, userId);
+ final var pu = addPackageUser(packageName, userId);
+ return pu != null ? pu.mPackageState : null;
}
public void onPackageRemoved(@NonNull final String packageName, final int userId) {
@@ -1308,22 +1313,30 @@
return (pkgs.length == 0) ? null : pkgs[0];
}
- @Nullable
@Override
public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
@NonNull String targetOverlayableName, int userId)
throws IOException {
- var packageState = getPackageStateForUser(packageName, userId);
- var pkg = packageState == null ? null : packageState.getAndroidPackage();
+ final var psu = getRawPackageStateForUser(packageName, userId);
+ final var pkg = (psu == null || psu.mPackageState == null)
+ ? null : psu.mPackageState.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);
- return apkAssets.getOverlayableInfo(targetOverlayableName);
+ if (psu.mDefinesOverlayable == null) {
+ psu.mDefinesOverlayable = apkAssets.definesOverlayable();
+ }
+ return Boolean.FALSE.equals(psu.mDefinesOverlayable)
+ ? null : apkAssets.getOverlayableInfo(targetOverlayableName);
} finally {
if (apkAssets != null) {
try {
@@ -1337,24 +1350,29 @@
@Override
public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
throws IOException {
- var packageState = getPackageStateForUser(targetPackageName, userId);
- var pkg = packageState == null ? null : packageState.getAndroidPackage();
+ final var psu = getRawPackageStateForUser(targetPackageName, userId);
+ var pkg = (psu == null || psu.mPackageState == null)
+ ? null : psu.mPackageState.getAndroidPackage();
if (pkg == null) {
throw new IOException("Unable to get target package");
}
- 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) {
+ 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) {
+ }
}
}
}
+ return psu.mDefinesOverlayable;
}
@Override
@@ -1545,8 +1563,7 @@
final OverlayPaths frameworkOverlays =
mImpl.getEnabledOverlayPaths("android", userId, false);
for (final String targetPackageName : targetPackageNames) {
- final OverlayPaths.Builder list = new OverlayPaths.Builder();
- list.addAll(frameworkOverlays);
+ final var list = new OverlayPaths.Builder(frameworkOverlays);
if (!"android".equals(targetPackageName)) {
list.addAll(mImpl.getEnabledOverlayPaths(targetPackageName, userId, true));
}
@@ -1558,17 +1575,21 @@
final HashSet<String> invalidPackages = new HashSet<>();
pm.setEnabledOverlayPackages(userId, pendingChanges, updatedPackages, invalidPackages);
- for (final String targetPackageName : targetPackageNames) {
- if (DEBUG) {
- Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=["
- + pendingChanges.get(targetPackageName)
- + "] userId=" + userId);
- }
+ if (DEBUG || !invalidPackages.isEmpty()) {
+ 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 972c78d..c1b6ccc 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -772,24 +772,20 @@
OverlayPaths getEnabledOverlayPaths(@NonNull final String targetPackageName,
final int userId, boolean includeImmutableOverlays) {
- 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);
+ final var paths = new OverlayPaths.Builder();
+ mSettings.forEachMatching(userId, null, targetPackageName, oi -> {
if (!oi.isEnabled()) {
- continue;
+ return;
}
if (!includeImmutableOverlays && !oi.isMutable) {
- continue;
+ return;
}
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 eae614a..b8b49f3e 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -47,6 +47,7 @@
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;
@@ -182,6 +183,23 @@
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/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index f3df424..cc4c2b5 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -1031,12 +1031,12 @@
private void recomputeComponentVisibility(
ArrayMap<String, ? extends PackageStateInternal> existingSettings) {
final WatchedArraySet<String> protectedBroadcasts;
- final WatchedArraySet<Integer> forceQueryable;
+ final ArraySet<Integer> forceQueryable;
synchronized (mProtectedBroadcastsLock) {
- protectedBroadcasts = mProtectedBroadcasts.snapshot();
+ protectedBroadcasts = new WatchedArraySet<String>(mProtectedBroadcasts);
}
synchronized (mForceQueryableLock) {
- forceQueryable = mForceQueryable.snapshot();
+ forceQueryable = new ArraySet<Integer>(mForceQueryable.untrackedStorage());
}
final ParallelComputeComponentVisibility computer = new ParallelComputeComponentVisibility(
existingSettings, forceQueryable, protectedBroadcasts);
diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java
index 200734b..a02a1bc 100644
--- a/services/core/java/com/android/server/pm/AppsFilterUtils.java
+++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java
@@ -198,12 +198,12 @@
private static final int MAX_THREADS = 4;
private final ArrayMap<String, ? extends PackageStateInternal> mExistingSettings;
- private final WatchedArraySet<Integer> mForceQueryable;
+ private final ArraySet<Integer> mForceQueryable;
private final WatchedArraySet<String> mProtectedBroadcasts;
ParallelComputeComponentVisibility(
@NonNull ArrayMap<String, ? extends PackageStateInternal> existingSettings,
- @NonNull WatchedArraySet<Integer> forceQueryable,
+ @NonNull ArraySet<Integer> forceQueryable,
@NonNull WatchedArraySet<String> protectedBroadcasts) {
mExistingSettings = existingSettings;
mForceQueryable = forceQueryable;
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 4f9eab9..f311034 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2900,6 +2900,12 @@
// code is loaded by a new Activity before ApplicationInfo changes have
// propagated to all application threads.
mPm.scheduleDeferredNoKillPostDelete(args);
+ if (Flags.improveInstallDontKill()) {
+ synchronized (mPm.mInstallLock) {
+ PackageManagerServiceUtils.linkSplitsToOldDirs(mPm.mInstaller,
+ packageName, pkgSetting.getPath(), pkgSetting.getOldPaths());
+ }
+ }
} else {
mRemovePackageHelper.cleanUpResources(packageName, args.getCodeFile(),
args.getInstructionSets());
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index ac826af..b5346a3 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1680,9 +1680,8 @@
mContext,
/* requestCode */ 0,
intent,
- PendingIntent.FLAG_ONE_SHOT
- | PendingIntent.FLAG_IMMUTABLE
- | PendingIntent.FLAG_CANCEL_CURRENT,
+ PendingIntent.FLAG_IMMUTABLE
+ | FLAG_UPDATE_CURRENT,
/* options */ null,
user);
return pi == null ? null : pi.getIntentSender();
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 1a20c8d..32f5646 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -195,6 +195,7 @@
Computer snapshot = mPm.snapshotComputer();
int userId = userHandle.getIdentifier();
int binderUid = Binder.getCallingUid();
+ int binderPid = Binder.getCallingPid();
if (!PackageManagerServiceUtils.isSystemOrRootOrShell(binderUid)) {
verifyCaller(snapshot.getPackageUid(callerPackageName, 0, userId), binderUid);
}
@@ -229,7 +230,8 @@
DELETE_ARCHIVE | DELETE_KEEP_DATA,
intentSender,
userId,
- binderUid);
+ binderUid,
+ binderPid);
})
.exceptionally(
e -> {
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index ee5875e..68f6ca1 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -88,6 +88,13 @@
final boolean didRestore = (msg.arg2 != 0);
mPm.mRunningInstalls.delete(msg.arg1);
+ if (request == null) {
+ if (DEBUG_INSTALL) {
+ Slog.i(TAG, "InstallRequest is null. Nothing to do for post-install "
+ + "token " + msg.arg1);
+ }
+ break;
+ }
request.closeFreezer();
request.onInstallCompleted();
request.runPostInstallRunnable();
@@ -116,10 +123,19 @@
}
} break;
case WRITE_SETTINGS: {
- mPm.writeSettings(/*sync=*/false);
+ if (!mPm.tryWriteSettings(/*sync=*/false)) {
+ // Failed to write.
+ this.removeMessages(WRITE_SETTINGS);
+ mPm.scheduleWriteSettings();
+ }
} break;
case WRITE_PACKAGE_LIST: {
- mPm.writePackageList(msg.arg1);
+ int userId = msg.arg1;
+ if (!mPm.tryWritePackageList(userId)) {
+ // Failed to write.
+ this.removeMessages(WRITE_PACKAGE_LIST);
+ mPm.scheduleWritePackageList(userId);
+ }
} break;
case CHECK_PENDING_VERIFICATION: {
final int verificationId = msg.arg1;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index cfafe7c..c6d448d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1405,11 +1405,12 @@
flags,
statusReceiver,
userId,
- Binder.getCallingUid());
+ Binder.getCallingUid(),
+ Binder.getCallingPid());
}
void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
- IntentSender statusReceiver, int userId, int callingUid) {
+ IntentSender statusReceiver, int userId, int callingUid, int callingPid) {
final Computer snapshot = mPm.snapshotComputer();
snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
if (!PackageManagerServiceUtils.isRootOrShell(callingUid)) {
@@ -1426,7 +1427,7 @@
final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
statusReceiver, versionedPackage.getPackageName(),
canSilentlyInstallPackage, userId, mPackageArchiver, flags);
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
+ if (mContext.checkPermission(Manifest.permission.DELETE_PACKAGES, callingPid, callingUid)
== PackageManager.PERMISSION_GRANTED) {
// Sweet, call straight through!
mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
@@ -1446,8 +1447,8 @@
} else {
ApplicationInfo appInfo = snapshot.getApplicationInfo(callerPackageName, 0, userId);
if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.REQUEST_DELETE_PACKAGES,
- null);
+ mContext.enforcePermission(Manifest.permission.REQUEST_DELETE_PACKAGES, callingPid,
+ callingUid, null);
}
// Take a short detour to confirm with user
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 1a0e807..27c3dad 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -54,6 +54,7 @@
import static com.android.internal.util.XmlUtils.writeUriAttribute;
import static com.android.server.pm.PackageInstallerService.prepareStageDir;
import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME;
+import static com.android.server.pm.PackageManagerService.DEFAULT_FILE_ACCESS_MODE;
import static com.android.server.pm.PackageManagerServiceUtils.isInstalledByAdb;
import static com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
@@ -1832,7 +1833,7 @@
try {
Os.link(path, sourcePath);
// Grant READ access for APK to be read successfully
- Os.chmod(sourcePath, 0644);
+ Os.chmod(sourcePath, DEFAULT_FILE_ACCESS_MODE);
} catch (ErrnoException e) {
e.rethrowAsIOException();
}
@@ -1901,7 +1902,8 @@
// If file is app metadata then set permission to 0640 to deny user read access since it
// might contain sensitive information.
- int mode = name.equals(APP_METADATA_FILE_NAME) ? APP_METADATA_FILE_ACCESS_MODE : 0644;
+ int mode = name.equals(APP_METADATA_FILE_NAME)
+ ? APP_METADATA_FILE_ACCESS_MODE : DEFAULT_FILE_ACCESS_MODE;
ParcelFileDescriptor targetPfd = openTargetInternal(target.getAbsolutePath(),
O_CREAT | O_WRONLY, mode);
Os.chmod(target.getAbsolutePath(), mode);
@@ -4246,7 +4248,7 @@
throw new IOException("Failed to copy " + fromFile + " to " + tmpFile);
}
try {
- Os.chmod(tmpFile.getAbsolutePath(), 0644);
+ Os.chmod(tmpFile.getAbsolutePath(), DEFAULT_FILE_ACCESS_MODE);
} catch (ErrnoException e) {
throw new IOException("Failed to chmod " + tmpFile);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 609b3aa..135bd4f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -489,6 +489,9 @@
*/
static final long WATCHDOG_TIMEOUT = 1000*60*10; // ten minutes
+ // How long to wait for Lock in async writeSettings and writePackageList.
+ private static final long WRITE_LOCK_TIMEOUT_MS = 1000 * 10; // 10 seconds
+
/**
* Default IncFs timeouts. Maximum values in IncFs is 1hr.
*
@@ -593,6 +596,8 @@
static final String APP_METADATA_FILE_NAME = "app.metadata";
+ static final int DEFAULT_FILE_ACCESS_MODE = 0644;
+
final Handler mHandler;
final Handler mBackgroundHandler;
@@ -1562,7 +1567,7 @@
}
}
- private void scheduleWritePackageListLocked(int userId) {
+ void scheduleWritePackageList(int userId) {
invalidatePackageInfoCache();
if (!mHandler.hasMessages(WRITE_PACKAGE_LIST)) {
Message msg = mHandler.obtainMessage(WRITE_PACKAGE_LIST);
@@ -1614,22 +1619,41 @@
mSettings.writePackageRestrictions(dirtyUsers);
}
- void writeSettings(boolean sync) {
- synchronized (mLock) {
+ private boolean tryUnderLock(boolean sync, long timeoutMs, Runnable runnable) {
+ try {
+ if (sync) {
+ mLock.lock();
+ } else if (!mLock.tryLock(timeoutMs, TimeUnit.MILLISECONDS)) {
+ return false;
+ }
+ try {
+ runnable.run();
+ return true;
+ } finally {
+ mLock.unlock();
+ }
+ } catch (InterruptedException e) {
+ Slog.e(TAG, "Failed to obtain mLock", e);
+ }
+ return false;
+ }
+
+ boolean tryWriteSettings(boolean sync) {
+ return tryUnderLock(sync, WRITE_LOCK_TIMEOUT_MS, () -> {
mHandler.removeMessages(WRITE_SETTINGS);
mBackgroundHandler.removeMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS);
writeSettingsLPrTEMP(sync);
synchronized (mDirtyUsers) {
mDirtyUsers.clear();
}
- }
+ });
}
- void writePackageList(int userId) {
- synchronized (mLock) {
+ boolean tryWritePackageList(int userId) {
+ return tryUnderLock(/*sync=*/false, WRITE_LOCK_TIMEOUT_MS, () -> {
mHandler.removeMessages(WRITE_PACKAGE_LIST);
mSettings.writePackageListLPr(userId);
- }
+ });
}
private static final Handler.Callback BACKGROUND_HANDLER_CALLBACK = new Handler.Callback() {
@@ -3054,7 +3078,9 @@
if (mHandler.hasMessages(WRITE_SETTINGS)
|| mBackgroundHandler.hasMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS)
|| mHandler.hasMessages(WRITE_PACKAGE_LIST)) {
- writeSettings(/*sync=*/true);
+ while (!tryWriteSettings(/*sync=*/true)) {
+ Slog.wtf(TAG, "Failed to write settings on shutdown");
+ }
}
}
}
@@ -4334,11 +4360,11 @@
mDirtyUsers.remove(userId);
}
mUserNeedsBadging.delete(userId);
- mPermissionManager.onUserRemoved(userId);
+ mDeletePackageHelper.removeUnusedPackagesLPw(userManager, userId);
mSettings.removeUserLPw(userId);
mPendingBroadcasts.remove(userId);
- mDeletePackageHelper.removeUnusedPackagesLPw(userManager, userId);
mAppsFilter.onUserDeleted(snapshotComputer(), userId);
+ mPermissionManager.onUserRemoved(userId);
}
mInstantAppRegistry.onUserRemoved(userId);
mPackageMonitorCallbackHelper.onUserRemoved(userId);
@@ -4361,7 +4387,7 @@
}
synchronized (mLock) {
scheduleWritePackageRestrictions(userId);
- scheduleWritePackageListLocked(userId);
+ scheduleWritePackageList(userId);
mAppsFilter.onUserCreated(snapshotComputer(), userId);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index cd34163..8531692 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -30,6 +30,7 @@
import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED;
+import static com.android.server.pm.PackageManagerService.DEFAULT_FILE_ACCESS_MODE;
import static com.android.server.pm.PackageManagerService.RANDOM_CODEPATH_PREFIX;
import static com.android.server.pm.PackageManagerService.RANDOM_DIR_PREFIX;
import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
@@ -69,6 +70,7 @@
import android.os.Environment;
import android.os.FileUtils;
import android.os.Process;
+import android.os.SELinux;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.incremental.IncrementalManager;
@@ -129,10 +131,12 @@
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.zip.GZIPInputStream;
@@ -853,7 +857,7 @@
FileUtils.copy(fileIn, outputStream);
// Flush anything in buffer before chmod, because any writes after chmod will fail.
outputStream.flush();
- Os.fchmod(outputStream.getFD(), 0644);
+ Os.fchmod(outputStream.getFD(), DEFAULT_FILE_ACCESS_MODE);
atomicFile.finishWrite(outputStream);
return PackageManager.INSTALL_SUCCEEDED;
} catch (IOException e) {
@@ -1081,8 +1085,8 @@
final File targetFile = new File(targetDir, targetName);
final FileDescriptor targetFd = Os.open(targetFile.getAbsolutePath(),
- O_RDWR | O_CREAT, 0644);
- Os.chmod(targetFile.getAbsolutePath(), 0644);
+ O_RDWR | O_CREAT, DEFAULT_FILE_ACCESS_MODE);
+ Os.chmod(targetFile.getAbsolutePath(), DEFAULT_FILE_ACCESS_MODE);
FileInputStream source = null;
try {
source = new FileInputStream(sourcePath);
@@ -1552,4 +1556,72 @@
public static boolean isInstalledByAdb(String initiatingPackageName) {
return initiatingPackageName == null || SHELL_PACKAGE_NAME.equals(initiatingPackageName);
}
+
+ public static void linkSplitsToOldDirs(@NonNull Installer installer,
+ @NonNull String packageName,
+ @NonNull File newPath,
+ @Nullable Set<File> oldPaths) {
+ if (oldPaths == null || oldPaths.isEmpty()) {
+ return;
+ }
+ if (IncrementalManager.isIncrementalPath(newPath.getPath())) {
+ //TODO(b/291212866): handle incremental installs
+ return;
+ }
+ final File[] filesInNewPath = newPath.listFiles();
+ if (filesInNewPath == null || filesInNewPath.length == 0) {
+ return;
+ }
+ final List<String> splitApkNames = new ArrayList<String>();
+ for (int i = 0; i < filesInNewPath.length; i++) {
+ if (!filesInNewPath[i].isDirectory() && filesInNewPath[i].toString().endsWith(".apk")) {
+ splitApkNames.add(filesInNewPath[i].getName());
+ }
+ }
+ final int numSplits = splitApkNames.size();
+ if (numSplits == 0) {
+ return;
+ }
+ for (File oldPath : oldPaths) {
+ if (!oldPath.exists()) {
+ continue;
+ }
+ for (int i = 0; i < numSplits; i++) {
+ final String splitApkName = splitApkNames.get(i);
+ final File linkedSplit = new File(oldPath, splitApkName);
+ if (linkedSplit.exists()) {
+ if (DEBUG) {
+ Slog.d(PackageManagerService.TAG, "Skipping existing linked split <"
+ + linkedSplit + ">");
+ }
+ continue;
+ }
+ final File sourceSplit = new File(newPath, splitApkName);
+ try {
+ installer.linkFile(packageName, splitApkName,
+ newPath.getAbsolutePath(), oldPath.getAbsolutePath());
+ if (DEBUG) {
+ Slog.d(PackageManagerService.TAG, "Linked <"
+ + sourceSplit + "> to <" + linkedSplit + ">");
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.w(PackageManagerService.TAG, "Failed to link split <"
+ + sourceSplit + " > to <" + linkedSplit + ">", e);
+ continue;
+ }
+ try {
+ Os.chmod(linkedSplit.getAbsolutePath(), DEFAULT_FILE_ACCESS_MODE);
+ } catch (ErrnoException e) {
+ Slog.w(PackageManagerService.TAG, "Failed to set mode for linked split <"
+ + linkedSplit + ">", e);
+ continue;
+ }
+ if (!SELinux.restorecon(linkedSplit)) {
+ Slog.w(PackageManagerService.TAG, "Failed to restorecon for linked split <"
+ + linkedSplit + ">");
+ }
+ }
+ }
+ //TODO(b/291212866): support native libs
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index dc1f1ac..5c9c8c6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -28,6 +28,7 @@
import static android.content.pm.PackageManager.RESTRICTION_NONE;
import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
+import static com.android.server.pm.PackageManagerService.DEFAULT_FILE_ACCESS_MODE;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.accounts.IAccountManager;
@@ -2349,7 +2350,7 @@
Streams.copy(inStream, outStream);
}
// Give read permissions to the other group.
- Os.chmod(outputProfilePath, /*mode*/ 0644 );
+ Os.chmod(outputProfilePath, /*mode*/ DEFAULT_FILE_ACCESS_MODE);
} catch (IOException | ErrnoException e) {
pw.println("Error when reading the profile fd: " + e.getMessage());
e.printStackTrace(pw);
diff --git a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
index e15e8a8..75e1803f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
+++ b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
@@ -16,9 +16,11 @@
package com.android.server.pm;
+import java.util.concurrent.locks.ReentrantLock;
+
/**
* This is a unique class that is used as the PackageManager lock. It can be targeted for lock
* injection, similar to {@link ActivityManagerGlobalLock}.
*/
-public class PackageManagerTracedLock {
+public class PackageManagerTracedLock extends ReentrantLock {
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
index c737283..f7603b5 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
@@ -27,6 +27,8 @@
import com.android.server.pm.PackageKeySetData;
import com.android.server.pm.permission.LegacyPermissionState;
+import java.io.File;
+import java.util.Set;
import java.util.UUID;
/**
@@ -111,4 +113,7 @@
*/
@Nullable
String getAppMetadataFilePath();
+
+ @Nullable
+ Set<File> getOldPaths();
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index bf669fb..0abf304 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5634,7 +5634,7 @@
Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn",
0 /* cookie */);
updateScreenOffSleepToken(false /* acquire */, false /* isSwappingDisplay */);
- mDefaultDisplayPolicy.screenTurnedOn(screenOnListener);
+ mDefaultDisplayPolicy.screenTurningOn(screenOnListener);
mBootAnimationDismissable = false;
synchronized (mLock) {
@@ -5676,6 +5676,7 @@
mKeyguardDelegate.onScreenTurnedOn();
}
}
+ mDefaultDisplayPolicy.screenTurnedOn();
reportScreenStateToVrManager(true);
}
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 871e98b..4bf8a78 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -319,6 +319,11 @@
pd.setMax(100);
pd.setProgress(0);
pd.setIndeterminate(false);
+ boolean showPercent = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_showPercentageTextDuringRebootToUpdate);
+ if (!showPercent) {
+ pd.setProgressPercentFormat(null);
+ }
pd.setProgressNumberFormat(null);
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pd.setMessage(context.getText(
@@ -911,4 +916,4 @@
com.android.internal.R.string.config_defaultShutdownVibrationFile);
}
}
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index b384725..92b5764 100755
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -309,6 +309,24 @@
}
}
+ public SparseArray<String> getHardwareInputIdMap() {
+ synchronized (mLock) {
+ return mHardwareInputIdMap.clone();
+ }
+ }
+
+ public SparseArray<String> getHdmiInputIdMap() {
+ synchronized (mLock) {
+ return mHdmiInputIdMap.clone();
+ }
+ }
+
+ public Map<String, TvInputInfo> getInputMap() {
+ synchronized (mLock) {
+ return Collections.unmodifiableMap(mInputMap);
+ }
+ }
+
public Map<String, List<String>> getHdmiParentInputMap() {
synchronized (mLock) {
return Collections.unmodifiableMap(mHdmiParentInputMap);
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index ac1b4df..e434df7 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -135,6 +135,7 @@
private static final int APP_TAG_SELF = TunedInfo.APP_TAG_SELF;
private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS =
"com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS";
+ private static final long UPDATE_HARDWARE_TIS_BINDING_DELAY_IN_MILLIS = 10 * 1000; // 10 seconds
// There are two different formats of DVB frontend devices. One is /dev/dvb%d.frontend%d,
// another one is /dev/dvb/adapter%d/frontend%d. Followings are the patterns for selecting the
@@ -174,7 +175,7 @@
@GuardedBy("mLock")
private final Map<String, SessionState> mSessionIdToSessionStateMap = new HashMap<>();
- private final WatchLogHandler mWatchLogHandler;
+ private final MessageHandler mMessageHandler;
private final ActivityManager mActivityManager;
@@ -187,8 +188,8 @@
super(context);
mContext = context;
- mWatchLogHandler = new WatchLogHandler(mContext.getContentResolver(),
- IoThread.get().getLooper());
+ mMessageHandler =
+ new MessageHandler(mContext.getContentResolver(), IoThread.get().getLooper());
mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
mActivityManager =
@@ -372,10 +373,10 @@
// service to populate the hardware list.
serviceState = new ServiceState(component, userId);
userState.serviceStateMap.put(component, serviceState);
+ updateServiceConnectionLocked(component, userId);
} else {
inputList.addAll(serviceState.hardwareInputMap.values());
}
- updateServiceConnectionLocked(component, userId);
} else {
try {
TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
@@ -510,6 +511,7 @@
}
}
+ @GuardedBy("mLock")
private void startProfileLocked(int userId) {
mRunningProfiles.add(userId);
buildTvInputListLocked(userId, null);
@@ -538,8 +540,10 @@
mCurrentUserId = userId;
buildTvInputListLocked(userId, null);
buildTvContentRatingSystemListLocked(userId);
- mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_SWITCH_CONTENT_RESOLVER,
- getContentResolverForUser(userId)).sendToTarget();
+ mMessageHandler
+ .obtainMessage(MessageHandler.MSG_SWITCH_CONTENT_RESOLVER,
+ getContentResolverForUser(userId))
+ .sendToTarget();
}
}
@@ -593,7 +597,7 @@
Slog.e(TAG, "error in unregisterCallback", e);
}
}
- mContext.unbindService(serviceState.connection);
+ unbindService(serviceState);
it.remove();
}
}
@@ -661,7 +665,7 @@
Slog.e(TAG, "error in unregisterCallback", e);
}
}
- mContext.unbindService(serviceState.connection);
+ unbindService(serviceState);
}
}
userState.serviceStateMap.clear();
@@ -774,7 +778,8 @@
boolean shouldBind;
if (userId == mCurrentUserId || mRunningProfiles.contains(userId)) {
- shouldBind = !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
+ shouldBind = !serviceState.sessionTokens.isEmpty()
+ || (serviceState.isHardware && serviceState.neverConnected);
} else {
// For a non-current user,
// if sessionTokens is not empty, it contains recording sessions only
@@ -783,31 +788,14 @@
shouldBind = !serviceState.sessionTokens.isEmpty();
}
- if (serviceState.service == null && shouldBind) {
- // This means that the service is not yet connected but its state indicates that we
- // have pending requests. Then, connect the service.
- if (serviceState.bound) {
- // We have already bound to the service so we don't try to bind again until after we
- // unbind later on.
- return;
+ // only bind/unbind when necessary.
+ if (shouldBind && !serviceState.bound) {
+ bindService(serviceState, userId);
+ } else if (!shouldBind && serviceState.bound) {
+ unbindService(serviceState);
+ if (!serviceState.isHardware) {
+ userState.serviceStateMap.remove(component);
}
- if (DEBUG) {
- Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
- }
-
- Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
- serviceState.bound = mContext.bindServiceAsUser(
- i, serviceState.connection,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
- new UserHandle(userId));
- } else if (serviceState.service != null && !shouldBind) {
- // This means that the service is already connected but its state indicates that we have
- // nothing to do with it. Then, disconnect the service.
- if (DEBUG) {
- Slog.d(TAG, "unbindService(service=" + component + ")");
- }
- mContext.unbindService(serviceState.connection);
- userState.serviceStateMap.remove(component);
}
}
@@ -829,7 +817,11 @@
sendSessionTokenToClientLocked(sessionState.client,
sessionState.inputId, null, null, sessionState.seq);
}
- updateServiceConnectionLocked(serviceState.component, userId);
+ if (!serviceState.isHardware) {
+ updateServiceConnectionLocked(serviceState.component, userId);
+ } else {
+ updateHardwareServiceConnectionDelayed(userId);
+ }
}
@GuardedBy("mLock")
@@ -948,13 +940,17 @@
if (serviceState != null) {
serviceState.sessionTokens.remove(sessionToken);
}
- updateServiceConnectionLocked(sessionState.componentName, userId);
+ if (!serviceState.isHardware) {
+ updateServiceConnectionLocked(sessionState.componentName, userId);
+ } else {
+ updateHardwareServiceConnectionDelayed(userId);
+ }
// Log the end of watch.
SomeArgs args = SomeArgs.obtain();
args.arg1 = sessionToken;
args.arg2 = System.currentTimeMillis();
- mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
+ mMessageHandler.obtainMessage(MessageHandler.MSG_LOG_WATCH_END, args).sendToTarget();
}
@GuardedBy("mLock")
@@ -1153,8 +1149,7 @@
ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
int oldState = inputState.state;
inputState.state = state;
- if (serviceState != null && serviceState.service == null
- && (!serviceState.sessionTokens.isEmpty() || serviceState.isHardware)) {
+ if (serviceState != null && serviceState.reconnecting) {
// We don't notify state change while reconnecting. It should remain disconnected.
return;
}
@@ -1881,7 +1876,7 @@
args.arg3 = ContentUris.parseId(channelUri);
args.arg4 = params;
args.arg5 = sessionToken;
- mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
+ mMessageHandler.obtainMessage(MessageHandler.MSG_LOG_WATCH_START, args)
.sendToTarget();
} catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in tune", e);
@@ -3327,16 +3322,21 @@
private final ComponentName component;
private final boolean isHardware;
private final Map<String, TvInputInfo> hardwareInputMap = new HashMap<>();
+ private final List<TvInputHardwareInfo> hardwareDeviceRemovedBuffer = new ArrayList<>();
+ private final List<HdmiDeviceInfo> hdmiDeviceRemovedBuffer = new ArrayList<>();
+ private final List<HdmiDeviceInfo> hdmiDeviceUpdatedBuffer = new ArrayList<>();
private ITvInputService service;
private ServiceCallback callback;
private boolean bound;
private boolean reconnecting;
+ private boolean neverConnected;
private ServiceState(ComponentName component, int userId) {
this.component = component;
this.connection = new InputServiceConnection(component, userId);
this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
+ this.neverConnected = true;
}
}
@@ -3449,6 +3449,97 @@
}
}
+ @GuardedBy("mLock")
+ private void bindService(ServiceState serviceState, int userId) {
+ if (serviceState.bound) {
+ // We have already bound to the service so we don't try to bind again until after we
+ // unbind later on.
+ // For hardware services, call updateHardwareServiceConnectionDelayed() to delay the
+ // possible unbinding.
+ if (serviceState.isHardware) {
+ updateHardwareServiceConnectionDelayed(userId);
+ }
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "bindServiceAsUser(service=" + serviceState.component + ", userId=" + userId
+ + ")");
+ }
+ Intent i =
+ new Intent(TvInputService.SERVICE_INTERFACE).setComponent(serviceState.component);
+ serviceState.bound = mContext.bindServiceAsUser(i, serviceState.connection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+ new UserHandle(userId));
+ if (!serviceState.bound) {
+ Slog.e(TAG, "failed to bind " + serviceState.component + " for userId " + userId);
+ mContext.unbindService(serviceState.connection);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void unbindService(ServiceState serviceState) {
+ if (!serviceState.bound) {
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "unbindService(service=" + serviceState.component + ")");
+ }
+ mContext.unbindService(serviceState.connection);
+ serviceState.bound = false;
+ serviceState.service = null;
+ serviceState.callback = null;
+ }
+
+ @GuardedBy("mLock")
+ private void updateHardwareTvInputServiceBindingLocked(int userId) {
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> services =
+ pm.queryIntentServicesAsUser(new Intent(TvInputService.SERVICE_INTERFACE),
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, userId);
+ for (ResolveInfo ri : services) {
+ ServiceInfo si = ri.serviceInfo;
+ if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
+ continue;
+ }
+ ComponentName component = new ComponentName(si.packageName, si.name);
+ if (hasHardwarePermission(pm, component)) {
+ updateServiceConnectionLocked(component, userId);
+ }
+ }
+ }
+
+ private void updateHardwareServiceConnectionDelayed(int userId) {
+ mMessageHandler.removeMessages(MessageHandler.MSG_UPDATE_HARDWARE_TIS_BINDING);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = userId;
+ Message msg =
+ mMessageHandler.obtainMessage(MessageHandler.MSG_UPDATE_HARDWARE_TIS_BINDING, args);
+ mMessageHandler.sendMessageDelayed(msg, UPDATE_HARDWARE_TIS_BINDING_DELAY_IN_MILLIS);
+ }
+
+ @GuardedBy("mLock")
+ private void addHardwareInputLocked(
+ TvInputInfo inputInfo, ComponentName component, int userId) {
+ ServiceState serviceState = getServiceStateLocked(component, userId);
+ serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo);
+ buildTvInputListLocked(userId, null);
+ }
+
+ @GuardedBy("mLock")
+ private void removeHardwareInputLocked(String inputId, int userId) {
+ if (!mTvInputHardwareManager.getInputMap().containsKey(inputId)) {
+ return;
+ }
+ ComponentName component = mTvInputHardwareManager.getInputMap().get(inputId).getComponent();
+ ServiceState serviceState = getServiceStateLocked(component, userId);
+ boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
+ if (removed) {
+ buildTvInputListLocked(userId, null);
+ mTvInputHardwareManager.removeHardwareInput(inputId);
+ }
+ }
+
private final class InputServiceConnection implements ServiceConnection {
private final ComponentName mComponent;
private final int mUserId;
@@ -3472,6 +3563,7 @@
}
ServiceState serviceState = userState.serviceStateMap.get(mComponent);
serviceState.service = ITvInputService.Stub.asInterface(service);
+ serviceState.neverConnected = false;
// Register a callback, if we need to.
if (serviceState.isHardware && serviceState.callback == null) {
@@ -3483,6 +3575,58 @@
}
}
+ for (TvInputState inputState : userState.inputMap.values()) {
+ if (inputState.info.getComponent().equals(component)
+ && inputState.state != INPUT_STATE_CONNECTED) {
+ notifyInputStateChangedLocked(userState, inputState.info.getId(),
+ inputState.state, null);
+ }
+ }
+
+ if (serviceState.isHardware) {
+ for (TvInputHardwareInfo hardwareToBeRemoved :
+ serviceState.hardwareDeviceRemovedBuffer) {
+ try {
+ serviceState.service.notifyHardwareRemoved(hardwareToBeRemoved);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in hardwareDeviceRemovedBuffer", e);
+ }
+ }
+ serviceState.hardwareDeviceRemovedBuffer.clear();
+ for (HdmiDeviceInfo hdmiDeviceToBeRemoved :
+ serviceState.hdmiDeviceRemovedBuffer) {
+ try {
+ serviceState.service.notifyHdmiDeviceRemoved(hdmiDeviceToBeRemoved);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in hdmiDeviceRemovedBuffer", e);
+ }
+ }
+ serviceState.hdmiDeviceRemovedBuffer.clear();
+ for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) {
+ try {
+ serviceState.service.notifyHardwareAdded(hardware);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in notifyHardwareAdded", e);
+ }
+ }
+ for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) {
+ try {
+ serviceState.service.notifyHdmiDeviceAdded(device);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
+ }
+ }
+ for (HdmiDeviceInfo hdmiDeviceToBeUpdated :
+ serviceState.hdmiDeviceUpdatedBuffer) {
+ try {
+ serviceState.service.notifyHdmiDeviceUpdated(hdmiDeviceToBeUpdated);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in hdmiDeviceUpdatedBuffer", e);
+ }
+ }
+ serviceState.hdmiDeviceUpdatedBuffer.clear();
+ }
+
List<IBinder> tokensToBeRemoved = new ArrayList<>();
// And create sessions, if any.
@@ -3496,30 +3640,8 @@
removeSessionStateLocked(sessionToken, mUserId);
}
- for (TvInputState inputState : userState.inputMap.values()) {
- if (inputState.info.getComponent().equals(component)
- && inputState.state != INPUT_STATE_CONNECTED) {
- notifyInputStateChangedLocked(userState, inputState.info.getId(),
- inputState.state, null);
- }
- }
-
if (serviceState.isHardware) {
- serviceState.hardwareInputMap.clear();
- for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) {
- try {
- serviceState.service.notifyHardwareAdded(hardware);
- } catch (RemoteException e) {
- Slog.e(TAG, "error in notifyHardwareAdded", e);
- }
- }
- for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) {
- try {
- serviceState.service.notifyHdmiDeviceAdded(device);
- } catch (RemoteException e) {
- Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
- }
- }
+ updateHardwareServiceConnectionDelayed(mUserId);
}
}
}
@@ -3570,13 +3692,6 @@
}
}
- @GuardedBy("mLock")
- private void addHardwareInputLocked(TvInputInfo inputInfo) {
- ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
- serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo);
- buildTvInputListLocked(mUserId, null);
- }
-
public void addHardwareInput(int deviceId, TvInputInfo inputInfo) {
ensureHardwarePermission();
ensureValidInput(inputInfo);
@@ -3587,8 +3702,11 @@
if (serviceState.hardwareInputMap.containsKey(inputInfo.getId())) {
return;
}
+ Slog.d("ServiceCallback",
+ "addHardwareInput: device id " + deviceId + ", "
+ + inputInfo.toString());
mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
- addHardwareInputLocked(inputInfo);
+ addHardwareInputLocked(inputInfo, mComponent, mUserId);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -3606,7 +3724,7 @@
return;
}
mTvInputHardwareManager.addHdmiInput(id, inputInfo);
- addHardwareInputLocked(inputInfo);
+ addHardwareInputLocked(inputInfo, mComponent, mUserId);
if (mOnScreenInputId != null && mOnScreenSessionState != null) {
if (TextUtils.equals(mOnScreenInputId, inputInfo.getParentId())) {
// catch the use case when a CEC device is plugged in an HDMI port,
@@ -3635,14 +3753,9 @@
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
- boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
- if (removed) {
- buildTvInputListLocked(mUserId, null);
- mTvInputHardwareManager.removeHardwareInput(inputId);
- } else {
- Slog.e(TAG, "failed to remove input " + inputId);
- }
+ Slog.d("ServiceCallback",
+ "removeHardwareInput " + inputId + " by " + mComponent);
+ removeHardwareInputLocked(inputId, mUserId);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -4226,11 +4339,12 @@
return loggedReason;
}
+ @GuardedBy("mLock")
private UserState getUserStateLocked(int userId) {
return mUserStates.get(userId);
}
- private static final class WatchLogHandler extends Handler {
+ private final class MessageHandler extends Handler {
// There are only two kinds of watch events that can happen on the system:
// 1. The current TV input session is tuned to a new channel.
// 2. The session is released for some reason.
@@ -4242,10 +4356,11 @@
static final int MSG_LOG_WATCH_START = 1;
static final int MSG_LOG_WATCH_END = 2;
static final int MSG_SWITCH_CONTENT_RESOLVER = 3;
+ static final int MSG_UPDATE_HARDWARE_TIS_BINDING = 4;
private ContentResolver mContentResolver;
- WatchLogHandler(ContentResolver contentResolver, Looper looper) {
+ MessageHandler(ContentResolver contentResolver, Looper looper) {
super(looper);
mContentResolver = contentResolver;
}
@@ -4304,6 +4419,14 @@
mContentResolver = (ContentResolver) msg.obj;
break;
}
+ case MSG_UPDATE_HARDWARE_TIS_BINDING:
+ SomeArgs args = (SomeArgs) msg.obj;
+ int userId = (int) args.arg1;
+ synchronized (mLock) {
+ updateHardwareTvInputServiceBindingLocked(userId);
+ }
+ args.recycle();
+ break;
default: {
Slog.w(TAG, "unhandled message code: " + msg.what);
break;
@@ -4359,29 +4482,46 @@
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
for (ServiceState serviceState : userState.serviceStateMap.values()) {
- if (!serviceState.isHardware || serviceState.service == null) continue;
+ if (!serviceState.isHardware) {
+ continue;
+ }
try {
- serviceState.service.notifyHardwareAdded(info);
+ bindService(serviceState, mCurrentUserId);
+ if (serviceState.service != null) {
+ serviceState.service.notifyHardwareAdded(info);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "error in notifyHardwareAdded", e);
}
}
+ updateHardwareServiceConnectionDelayed(mCurrentUserId);
}
}
@Override
public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
synchronized (mLock) {
+ String relatedInputId =
+ mTvInputHardwareManager.getHardwareInputIdMap().get(info.getDeviceId());
+ removeHardwareInputLocked(relatedInputId, mCurrentUserId);
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
for (ServiceState serviceState : userState.serviceStateMap.values()) {
- if (!serviceState.isHardware || serviceState.service == null) continue;
+ if (!serviceState.isHardware) {
+ continue;
+ }
try {
- serviceState.service.notifyHardwareRemoved(info);
+ bindService(serviceState, mCurrentUserId);
+ if (serviceState.service != null) {
+ serviceState.service.notifyHardwareRemoved(info);
+ } else {
+ serviceState.hardwareDeviceRemovedBuffer.add(info);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "error in notifyHardwareRemoved", e);
}
}
+ updateHardwareServiceConnectionDelayed(mCurrentUserId);
}
}
@@ -4391,29 +4531,46 @@
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
for (ServiceState serviceState : userState.serviceStateMap.values()) {
- if (!serviceState.isHardware || serviceState.service == null) continue;
+ if (!serviceState.isHardware) {
+ continue;
+ }
try {
- serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
+ bindService(serviceState, mCurrentUserId);
+ if (serviceState.service != null) {
+ serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
}
}
+ updateHardwareServiceConnectionDelayed(mCurrentUserId);
}
}
@Override
public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
synchronized (mLock) {
+ String relatedInputId =
+ mTvInputHardwareManager.getHdmiInputIdMap().get(deviceInfo.getId());
+ removeHardwareInputLocked(relatedInputId, mCurrentUserId);
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
for (ServiceState serviceState : userState.serviceStateMap.values()) {
- if (!serviceState.isHardware || serviceState.service == null) continue;
+ if (!serviceState.isHardware) {
+ continue;
+ }
try {
- serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
+ bindService(serviceState, mCurrentUserId);
+ if (serviceState.service != null) {
+ serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
+ } else {
+ serviceState.hdmiDeviceRemovedBuffer.add(deviceInfo);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
}
}
+ updateHardwareServiceConnectionDelayed(mCurrentUserId);
}
}
@@ -4441,13 +4598,21 @@
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
for (ServiceState serviceState : userState.serviceStateMap.values()) {
- if (!serviceState.isHardware || serviceState.service == null) continue;
+ if (!serviceState.isHardware) {
+ continue;
+ }
try {
- serviceState.service.notifyHdmiDeviceUpdated(deviceInfo);
+ bindService(serviceState, mCurrentUserId);
+ if (serviceState.service != null) {
+ serviceState.service.notifyHdmiDeviceUpdated(deviceInfo);
+ } else {
+ serviceState.hdmiDeviceUpdatedBuffer.add(deviceInfo);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "error in notifyHdmiDeviceUpdated", e);
}
}
+ updateHardwareServiceConnectionDelayed(mCurrentUserId);
}
}
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 9c60fbb..d7b8495 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -1038,7 +1038,7 @@
}
} finally {
if (surface != null) {
- // surface is not used in TvInteractiveAppManagerService.
+ // surface is not used in TvAdManagerService.
surface.release();
}
Binder.restoreCallingIdentity(identity);
@@ -1070,6 +1070,253 @@
@Override
public void startAdService(IBinder sessionToken, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "startAdService(userId=%d)", userId);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "startAdService");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).startAdService();
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in start", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void stopAdService(IBinder sessionToken, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "stopAdService(userId=%d)", userId);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "stopAdService");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).stopAdService();
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in stop", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void resetAdService(IBinder sessionToken, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "resetAdService(userId=%d)", userId);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "resetAdService");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).resetAdService();
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in reset", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void sendCurrentVideoBounds(IBinder sessionToken, Rect bounds, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "sendCurrentVideoBounds(bounds=%s)", bounds.toString());
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "sendCurrentVideoBounds");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).sendCurrentVideoBounds(bounds);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in sendCurrentVideoBounds", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void sendCurrentChannelUri(IBinder sessionToken, Uri channelUri, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "sendCurrentChannelUri(channelUri=%s)", channelUri.toString());
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "sendCurrentChannelUri");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).sendCurrentChannelUri(channelUri);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in sendCurrentChannelUri", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void sendTrackInfoList(IBinder sessionToken, List<TvTrackInfo> tracks, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "sendTrackInfoList(tracks=%s)", tracks.toString());
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "sendTrackInfoList");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).sendTrackInfoList(tracks);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in sendTrackInfoList", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void sendCurrentTvInputId(IBinder sessionToken, String inputId, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "sendCurrentTvInputId(inputId=%s)", inputId);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "sendCurrentTvInputId");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).sendCurrentTvInputId(inputId);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in sendCurrentTvInputId", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void sendSigningResult(
+ IBinder sessionToken, String signingId, byte[] result, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "sendSigningResult(signingId=%s)", signingId);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "sendSigningResult");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).sendSigningResult(signingId, result);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in sendSigningResult", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void notifyError(IBinder sessionToken, String errMsg, Bundle params, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "notifyError(errMsg=%s)", errMsg);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId =
+ resolveCallingUserId(Binder.getCallingPid(), callingUid, userId, "notifyError");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState =
+ getAdSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+ getAdSessionLocked(sessionState).notifyError(errMsg, params);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in notifyError", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void notifyTvMessage(IBinder sessionToken, int type, Bundle data, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "notifyTvMessage(type=%d)", type);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+ "notifyTvMessage");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ AdSessionState sessionState =
+ getAdSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+ getAdSessionLocked(sessionState).notifyTvMessage(type, data);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in notifyTvMessage", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
@@ -1106,6 +1353,67 @@
}
}
+ @Override
+ public void createMediaView(IBinder sessionToken, IBinder windowToken, Rect frame,
+ int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "createMediaView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getAdSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .createMediaView(windowToken, frame);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in createMediaView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void relayoutMediaView(IBinder sessionToken, Rect frame, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "relayoutMediaView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getAdSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .relayoutMediaView(frame);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in relayoutMediaView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void removeMediaView(IBinder sessionToken, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "removeMediaView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getAdSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .removeMediaView();
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in removeMediaView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
}
private final class BinderService extends ITvInteractiveAppManager.Stub {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 51acc8e..8549957 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -410,9 +410,10 @@
// adapt the entries in wallpaper.mCropHints for the actual display
SparseArray<Rect> updatedCropHints = new SparseArray<>();
for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
- Rect defaultCrop = defaultDisplayCrops.valueAt(i);
+ int orientation = wallpaper.mCropHints.keyAt(i);
+ Rect defaultCrop = defaultDisplayCrops.get(orientation);
if (defaultCrop != null) {
- updatedCropHints.put(defaultDisplayCrops.keyAt(i), defaultCrop);
+ updatedCropHints.put(orientation, defaultCrop);
}
}
wallpaper.mCropHints = updatedCropHints;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9b1f9c8..036f7b6 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3965,20 +3965,6 @@
return removedFromHistory;
}
- boolean safelyDestroy(String reason) {
- if (isDestroyable()) {
- if (DEBUG_SWITCH) {
- final Task task = getTask();
- Slog.v(TAG_SWITCH, "Safely destroying " + this + " in state " + getState()
- + " resumed=" + task.getTopResumedActivity()
- + " pausing=" + task.getTopPausingActivity()
- + " for reason " + reason);
- }
- return destroyImmediately(reason);
- }
- return false;
- }
-
/** Note: call {@link #cleanUp(boolean, boolean)} before this method. */
void removeFromHistory(String reason) {
finishActivityResults(Activity.RESULT_CANCELED,
@@ -4047,10 +4033,6 @@
}
}
- boolean isFinishing() {
- return finishing;
- }
-
/**
* This method is to only be called from the client via binder when the activity is destroyed
* AND finished.
@@ -7978,6 +7960,7 @@
if (mLetterboxUiController.shouldIgnoreRequestedOrientation(requestedOrientation)) {
return;
}
+ final int originalRelaunchingCount = mPendingRelaunchCount;
// This is necessary in order to avoid going into size compat mode when the orientation
// change request comes from the app
if (getRequestedConfigurationOrientation(false, requestedOrientation)
@@ -7995,8 +7978,10 @@
// the request is handled at task level with letterbox.
if (!getMergedOverrideConfiguration().equals(
mLastReportedConfiguration.getMergedConfiguration())) {
- ensureActivityConfiguration(
- false /* ignoreVisibility */, true /* isRequestedOrientationChanged */);
+ ensureActivityConfiguration(false /* ignoreVisibility */);
+ if (mPendingRelaunchCount > originalRelaunchingCount) {
+ mLetterboxUiController.setRelaunchingAfterRequestedOrientationChanged(true);
+ }
if (mTransitionController.inPlayingTransition(this)) {
mTransitionController.mValidateActivityCompat.add(this);
}
@@ -9502,11 +9487,6 @@
return ensureActivityConfiguration(false /* ignoreVisibility */);
}
- boolean ensureActivityConfiguration(boolean ignoreVisibility) {
- return ensureActivityConfiguration(ignoreVisibility,
- false /* isRequestedOrientationChanged */);
- }
-
/**
* Make sure the given activity matches the current configuration. Ensures the HistoryRecord
* is updated with the correct configuration and all other bookkeeping is handled.
@@ -9515,13 +9495,10 @@
* (stopped state). This is useful for the case where we know the
* activity will be visible soon and we want to ensure its configuration
* before we make it visible.
- * @param isRequestedOrientationChanged whether this is triggered in response to an app calling
- * {@link android.app.Activity#setRequestedOrientation}.
* @return False if the activity was relaunched and true if it wasn't relaunched because we
* can't or the app handles the specific configuration that is changing.
*/
- boolean ensureActivityConfiguration(boolean ignoreVisibility,
- boolean isRequestedOrientationChanged) {
+ boolean ensureActivityConfiguration(boolean ignoreVisibility) {
final Task rootTask = getRootTask();
if (rootTask.mConfigWillChange) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check "
@@ -9658,9 +9635,6 @@
} else {
mRelaunchReason = RELAUNCH_REASON_NONE;
}
- if (isRequestedOrientationChanged) {
- mLetterboxUiController.setRelaunchingAfterRequestedOrientationChanged(true);
- }
if (mState == PAUSING) {
// A little annoying: we are waiting for this activity to finish pausing. Let's not
// do anything now, but just flag that it needs to be restarted when done pausing.
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 2bd49bf..a4d15e0 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -19,6 +19,7 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.View.FOCUS_FORWARD;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
@@ -60,6 +61,7 @@
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.wm.utils.InsetUtils;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -167,6 +169,24 @@
return null;
}
+ // Move focus to the adjacent embedded window if it is higher than this window
+ final TaskFragment taskFragment = window.getTaskFragment();
+ final TaskFragment adjacentTaskFragment =
+ taskFragment != null ? taskFragment.getAdjacentTaskFragment() : null;
+ if (adjacentTaskFragment != null && taskFragment.isEmbedded()
+ && Flags.embeddedActivityBackNavFlag()) {
+ final WindowContainer parent = taskFragment.getParent();
+ if (parent.mChildren.indexOf(taskFragment) < parent.mChildren.indexOf(
+ adjacentTaskFragment)) {
+ mWindowManagerService.moveFocusToAdjacentWindow(window, FOCUS_FORWARD);
+ window = wmService.getFocusedWindowLocked();
+ if (window == null) {
+ Slog.e(TAG, "Adjacent window is null, returning null.");
+ return null;
+ }
+ }
+ }
+
// This is needed to bridge the old and new back behavior with recents. While in
// Overview with live tile enabled, the previous app is technically focused but we
// add an input consumer to capture all input that would otherwise go to the apps
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 39dd77e..9ac4a5c 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -34,6 +34,7 @@
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
+import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
import static com.android.window.flags.Flags.balRequireOptInSameUid;
import static com.android.window.flags.Flags.balShowToasts;
@@ -70,7 +71,6 @@
import com.android.internal.util.Preconditions;
import com.android.server.UiThread;
import com.android.server.am.PendingIntentRecord;
-import com.android.window.flags.Flags;
import java.lang.annotation.Retention;
import java.util.HashMap;
@@ -275,10 +275,13 @@
@BackgroundActivityStartMode int realCallerBackgroundActivityStartMode =
checkedOptions.getPendingIntentBackgroundActivityStartMode();
- if (balRequireOptInByPendingIntentCreator() && originatingPendingIntent == null) {
- mAutoOptInReason = "notPendingIntent";
- } else if (balRequireOptInByPendingIntentCreator() && mIsCallForResult) {
+ if (!balImproveRealCallerVisibilityCheck()) {
+ // without this fix the auto-opt ins below would violate CTS tests
+ mAutoOptInReason = null;
+ } else if (mIsCallForResult) {
mAutoOptInReason = "callForResult";
+ } else if (originatingPendingIntent == null) {
+ mAutoOptInReason = "notPendingIntent";
} else if (callingUid == realCallingUid && !balRequireOptInSameUid()) {
mAutoOptInReason = "sameUid";
} else {
@@ -950,7 +953,7 @@
// is allowed, or apps like live wallpaper with non app visible window will be allowed.
final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW
|| state.mAppSwitchState == APP_SWITCH_FG_ONLY;
- if (Flags.balImproveRealCallerVisibilityCheck()) {
+ if (balImproveRealCallerVisibilityCheck()) {
if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) {
return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
/*background*/ false, "realCallingUid has visible window");
@@ -1726,7 +1729,7 @@
return ar
+ " :: visible=" + ar.isVisible()
+ ", visibleRequested=" + ar.isVisibleRequested()
- + ", finishing=" + ar.isFinishing()
+ + ", finishing=" + ar.finishing
+ ", alwaysOnTop=" + ar.isAlwaysOnTop()
+ ", lastLaunchTime=" + ar.lastLaunchTime
+ ", lastVisibleTime=" + ar.lastVisibleTime
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 63ca592..e2bc59b 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -794,6 +794,9 @@
}
mService.mAtmService.mKeyguardController.updateDeferTransitionForAod(
mAwake /* waiting */);
+ if (!awake) {
+ mDisplayContent.mWallpaperController.onDisplaySwitchFinished();
+ }
}
}
@@ -836,7 +839,8 @@
mRemoteInsetsControllerControlsSystemBars = remoteInsetsControllerControlsSystemBars;
}
- public void screenTurnedOn(ScreenOnListener screenOnListener) {
+ /** Prepares to turn on screen. The given listener is used to notify that it is ready. */
+ public void screenTurningOn(ScreenOnListener screenOnListener) {
WindowProcessController visibleDozeUiProcess = null;
synchronized (mLock) {
mScreenOnEarly = true;
@@ -858,6 +862,11 @@
}
}
+ /** It is called after {@link #finishScreenTurningOn}. This runs on PowerManager's thread. */
+ public void screenTurnedOn() {
+ mDisplayContent.mWallpaperController.onDisplaySwitchFinished();
+ }
+
public void screenTurnedOff() {
synchronized (mLock) {
mScreenOnEarly = false;
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 32d60c5..6a3cf43 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -469,8 +469,7 @@
case MSG_REMOVE_DRAG_SURFACE_TIMEOUT: {
synchronized (mService.mGlobalLock) {
- mService.mTransactionFactory.get()
- .reparent((SurfaceControl) msg.obj, null).apply();
+ mService.mTransactionFactory.get().remove((SurfaceControl) msg.obj).apply();
}
break;
}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index adbe3bc..d302f06 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -270,7 +270,7 @@
}
if (mSurfaceControl != null) {
if (!mRelinquishDragSurfaceToDropTarget && !relinquishDragSurfaceToDragSource()) {
- mTransaction.reparent(mSurfaceControl, null).apply();
+ mTransaction.remove(mSurfaceControl).apply();
} else {
mDragDropController.sendTimeoutMessage(MSG_REMOVE_DRAG_SURFACE_TIMEOUT,
mSurfaceControl, DragDropController.DRAG_TIMEOUT_MS);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index fcc1e5b..f279689 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -148,7 +148,7 @@
final class LetterboxUiController {
private static final Predicate<ActivityRecord> FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE =
- activityRecord -> activityRecord.fillsParent() && !activityRecord.isFinishing();
+ ActivityRecord::occludesParent;
private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 02b3f15..587cc74 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2783,6 +2783,9 @@
} else {
throw new RuntimeException("Create the same sleep token twice: " + token);
}
+ if (isSwappingDisplay) {
+ display.mWallpaperController.onDisplaySwitchStarted();
+ }
return token;
}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index d68f932..0fc62a7 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WallpaperManager.COMMAND_DISPLAY_SWITCH;
import static android.app.WallpaperManager.COMMAND_FREEZE;
import static android.app.WallpaperManager.COMMAND_UNFREEZE;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -120,6 +121,11 @@
private boolean mShouldOffsetWallpaperCenter;
+ /**
+ * Whether the wallpaper has been notified about a physical display switch event is started.
+ */
+ private volatile boolean mIsWallpaperNotifiedOnDisplaySwitch;
+
private final Consumer<WindowState> mFindWallpapers = w -> {
if (w.mAttrs.type == TYPE_WALLPAPER) {
WallpaperWindowToken token = w.mToken.asWallpaperToken();
@@ -1083,6 +1089,52 @@
}
/**
+ * Notifies the wallpaper that the display turns off when switching physical device. If the
+ * wallpaper is currently visible, its client visibility will be preserved until the display is
+ * confirmed to be off or on.
+ */
+ void onDisplaySwitchStarted() {
+ mIsWallpaperNotifiedOnDisplaySwitch = notifyDisplaySwitch(true /* start */);
+ }
+
+ /**
+ * Called when the screen has finished turning on or the device goes to sleep. This is no-op if
+ * the operation is not part of a display switch.
+ */
+ void onDisplaySwitchFinished() {
+ // The method can be called outside WM lock (turned on), so only acquire lock if needed.
+ // This is to optimize the common cases that regular devices don't have display switch.
+ if (mIsWallpaperNotifiedOnDisplaySwitch) {
+ synchronized (mService.mGlobalLock) {
+ mIsWallpaperNotifiedOnDisplaySwitch = false;
+ notifyDisplaySwitch(false /* start */);
+ }
+ }
+ }
+
+ private boolean notifyDisplaySwitch(boolean start) {
+ boolean notified = false;
+ for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
+ final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
+ for (int i = token.getChildCount() - 1; i >= 0; i--) {
+ final WindowState w = token.getChildAt(i);
+ if (start && !w.mWinAnimator.getShown()) {
+ continue;
+ }
+ try {
+ w.mClient.dispatchWallpaperCommand(COMMAND_DISPLAY_SWITCH, 0 /* x */, 0 /* y */,
+ start ? 1 : 0 /* use z as start or finish */,
+ null /* bundle */, false /* sync */);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to dispatch COMMAND_DISPLAY_SWITCH " + e);
+ }
+ notified = true;
+ }
+ }
+ return notified;
+ }
+
+ /**
* Each window can request a zoom, example:
* - User is in overview, zoomed out.
* - User also pulls down the shade.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f8ac8da..9650b8bc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9156,55 +9156,63 @@
if (fromWin == null || !fromWin.isFocused()) {
return false;
}
- final TaskFragment fromFragment = fromWin.getTaskFragment();
- if (fromFragment == null) {
- return false;
- }
- final TaskFragment adjacentFragment = fromFragment.getAdjacentTaskFragment();
- if (adjacentFragment == null || adjacentFragment.asTask() != null) {
- // Don't move the focus to another task.
- return false;
- }
- final Rect fromBounds = fromFragment.getBounds();
- final Rect adjacentBounds = adjacentFragment.getBounds();
- switch (direction) {
- case View.FOCUS_LEFT:
- if (adjacentBounds.left >= fromBounds.left) {
- return false;
- }
- break;
- case View.FOCUS_UP:
- if (adjacentBounds.top >= fromBounds.top) {
- return false;
- }
- break;
- case View.FOCUS_RIGHT:
- if (adjacentBounds.right <= fromBounds.right) {
- return false;
- }
- break;
- case View.FOCUS_DOWN:
- if (adjacentBounds.bottom <= fromBounds.bottom) {
- return false;
- }
- break;
- case View.FOCUS_BACKWARD:
- case View.FOCUS_FORWARD:
- // These are not absolute directions. Skip checking the bounds.
- break;
- default:
+ return moveFocusToAdjacentWindow(fromWin, direction);
+ }
+ }
+
+ boolean moveFocusToAdjacentWindow(WindowState fromWin, @FocusDirection int direction) {
+ final TaskFragment fromFragment = fromWin.getTaskFragment();
+ if (fromFragment == null) {
+ return false;
+ }
+ final TaskFragment adjacentFragment = fromFragment.getAdjacentTaskFragment();
+ if (adjacentFragment == null || adjacentFragment.asTask() != null) {
+ // Don't move the focus to another task.
+ return false;
+ }
+ if (adjacentFragment.isIsolatedNav()) {
+ // Don't move the focus if the adjacent TF is isolated navigation.
+ return false;
+ }
+ final Rect fromBounds = fromFragment.getBounds();
+ final Rect adjacentBounds = adjacentFragment.getBounds();
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ if (adjacentBounds.left >= fromBounds.left) {
return false;
- }
- final ActivityRecord topRunningActivity = adjacentFragment.topRunningActivity(
- true /* focusableOnly */);
- if (topRunningActivity == null) {
+ }
+ break;
+ case View.FOCUS_UP:
+ if (adjacentBounds.top >= fromBounds.top) {
+ return false;
+ }
+ break;
+ case View.FOCUS_RIGHT:
+ if (adjacentBounds.right <= fromBounds.right) {
+ return false;
+ }
+ break;
+ case View.FOCUS_DOWN:
+ if (adjacentBounds.bottom <= fromBounds.bottom) {
+ return false;
+ }
+ break;
+ case View.FOCUS_BACKWARD:
+ case View.FOCUS_FORWARD:
+ // These are not absolute directions. Skip checking the bounds.
+ break;
+ default:
return false;
- }
- moveDisplayToTopInternal(topRunningActivity.getDisplayId());
- handleTaskFocusChange(topRunningActivity.getTask(), topRunningActivity);
- if (fromWin.isFocused()) {
- return false;
- }
+ }
+ final ActivityRecord topRunningActivity = adjacentFragment.topRunningActivity(
+ true /* focusableOnly */);
+ if (topRunningActivity == null) {
+ return false;
+ }
+ moveDisplayToTopInternal(topRunningActivity.getDisplayId());
+ handleTaskFocusChange(topRunningActivity.getTask(), topRunningActivity);
+ if (fromWin.isFocused()) {
+ return false;
}
return true;
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 2049331..8bc41af 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -281,6 +281,7 @@
void displayRemoved(JNIEnv* env, int32_t displayId);
void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj);
void setFocusedDisplay(int32_t displayId);
+ void setMinTimeBetweenUserActivityPokes(int64_t intervalMillis);
void setInputDispatchMode(bool enabled, bool frozen);
void setSystemUiLightsOut(bool lightsOut);
void setPointerDisplayId(int32_t displayId);
@@ -1169,6 +1170,11 @@
mInputManager->getDispatcher().setFocusedDisplay(displayId);
}
+void NativeInputManager::setMinTimeBetweenUserActivityPokes(int64_t intervalMillis) {
+ mInputManager->getDispatcher().setMinTimeBetweenUserActivityPokes(
+ std::chrono::milliseconds(intervalMillis));
+}
+
void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) {
mInputManager->getDispatcher().setInputDispatchMode(enabled, frozen);
}
@@ -2122,6 +2128,13 @@
im->setFocusedDisplay(displayId);
}
+static void nativeSetUserActivityPokeInterval(JNIEnv* env, jobject nativeImplObj,
+ jlong intervalMillis) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+ im->setMinTimeBetweenUserActivityPokes(intervalMillis);
+}
+
static void nativeRequestPointerCapture(JNIEnv* env, jobject nativeImplObj, jobject tokenObj,
jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2805,6 +2818,7 @@
{"setFocusedApplication", "(ILandroid/view/InputApplicationHandle;)V",
(void*)nativeSetFocusedApplication},
{"setFocusedDisplay", "(I)V", (void*)nativeSetFocusedDisplay},
+ {"setMinTimeBetweenUserActivityPokes", "(J)V", (void*)nativeSetUserActivityPokeInterval},
{"requestPointerCapture", "(Landroid/os/IBinder;Z)V", (void*)nativeRequestPointerCapture},
{"setInputDispatchMode", "(ZZ)V", (void*)nativeSetInputDispatchMode},
{"setSystemUiLightsOut", "(Z)V", (void*)nativeSetSystemUiLightsOut},
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 6ab98fe..d0b290c 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -31,6 +31,7 @@
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <binder/IServiceManager.h>
+#include <com_android_input_flags.h>
#include <gui/SurfaceComposerClient.h>
#include <hardware_legacy/power.h>
#include <hidl/ServiceManagement.h>
@@ -109,10 +110,12 @@
eventTime = now;
}
- if (gLastEventTime[eventType] + MIN_TIME_BETWEEN_USERACTIVITIES > eventTime) {
- return;
+ if (!com::android::input::flags::rate_limit_user_activity_poke_in_dispatcher()) {
+ if (gLastEventTime[eventType] + MIN_TIME_BETWEEN_USERACTIVITIES > eventTime) {
+ return;
+ }
+ gLastEventTime[eventType] = eventTime;
}
- gLastEventTime[eventType] = eventTime;
// Tell the power HAL when user activity occurs.
setPowerBoost(Boost::INTERACTION, 0);
@@ -285,9 +288,11 @@
GET_METHOD_ID(gPowerManagerServiceClassInfo.userActivityFromNative, clazz,
"userActivityFromNative", "(JIII)V");
- // Initialize
- for (int i = 0; i <= USER_ACTIVITY_EVENT_LAST; i++) {
- gLastEventTime[i] = LLONG_MIN;
+ if (!com::android::input::flags::rate_limit_user_activity_poke_in_dispatcher()) {
+ // Initialize
+ for (int i = 0; i <= USER_ACTIVITY_EVENT_LAST; i++) {
+ gLastEventTime[i] = LLONG_MIN;
+ }
}
gPowerManagerServiceObj = NULL;
return 0;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index 532823a..e8c5658 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -17,6 +17,7 @@
package com.android.server.devicepolicy;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
+import static android.app.admin.flags.Flags.defaultSmsPersonalAppSuspensionFixEnabled;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.Nullable;
@@ -42,6 +43,7 @@
import android.view.inputmethod.InputMethodInfo;
import com.android.internal.R;
+import com.android.internal.telephony.SmsApplication;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.utils.Slogf;
@@ -97,7 +99,7 @@
result.removeAll(getSystemLauncherPackages());
result.removeAll(getAccessibilityServices());
result.removeAll(getInputMethodPackages());
- result.remove(Telephony.Sms.getDefaultSmsPackage(mContext));
+ result.remove(getDefaultSmsPackage());
result.remove(getSettingsPackageName());
final String[] unsuspendablePackages =
@@ -202,6 +204,17 @@
return resolveInfos != null && !resolveInfos.isEmpty();
}
+ private String getDefaultSmsPackage() {
+ //TODO(b/319449037): Unflag the following change.
+ if (defaultSmsPersonalAppSuspensionFixEnabled()) {
+ return SmsApplication.getDefaultSmsApplicationAsUser(
+ mContext, /*updateIfNeeded=*/ false, mContext.getUser())
+ .getPackageName();
+ } else {
+ return Telephony.Sms.getDefaultSmsPackage(mContext);
+ }
+ }
+
void dump(IndentingPrintWriter pw) {
pw.println("PersonalAppsSuspensionHelper");
@@ -212,7 +225,7 @@
DevicePolicyManagerService.dumpApps(pw, "accessibility services",
getAccessibilityServices());
DevicePolicyManagerService.dumpApps(pw, "input method packages", getInputMethodPackages());
- pw.printf("SMS package: %s\n", Telephony.Sms.getDefaultSmsPackage(mContext));
+ pw.printf("SMS package: %s\n", getDefaultSmsPackage());
pw.printf("Settings package: %s\n", getSettingsPackageName());
DevicePolicyManagerService.dumpApps(pw, "Packages subject to suspension",
getPersonalAppsForSuspension());
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
similarity index 93%
rename from services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
rename to services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index fd65807..a33e52f 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -25,23 +25,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
import com.android.internal.inputmethod.SoftInputShowHideReason;
import org.junit.Test;
-import org.junit.runner.RunWith;
import java.io.PrintWriter;
import java.io.StringWriter;
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class InputMethodManagerServiceTests {
+public final class InputMethodManagerServiceTests {
static final int SYSTEM_DECORATION_SUPPORT_DISPLAY_ID = 2;
static final int NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID = 3;
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
rename to services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
index a55d1c4..75118ea 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
@@ -22,14 +22,9 @@
import android.util.IntArray;
import androidx.annotation.NonNull;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
-import org.junit.runner.RunWith;
-@SmallTest
-@RunWith(AndroidJUnit4.class)
public final class InputMethodSettingsTest {
private static void verifyUpdateEnabledImeString(@NonNull String expectedEnabledImeStr,
@NonNull String initialEnabledImeStr, @NonNull String imeId,
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index d81df12..2857619 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -269,7 +269,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_EN_US), imi);
assertEquals(1, result.size());
verifyEquality(autoSubtype, result.get(0));
@@ -293,7 +293,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_EN_US), imi);
assertEquals(2, result.size());
verifyEquality(nonAutoEnUS, result.get(0));
@@ -317,7 +317,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_EN_GB), imi);
assertEquals(2, result.size());
verifyEquality(nonAutoEnGB, result.get(0));
@@ -342,7 +342,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_FR), imi);
assertEquals(2, result.size());
verifyEquality(nonAutoFrCA, result.get(0));
@@ -363,7 +363,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_FR_CA), imi);
assertEquals(2, result.size());
verifyEquality(nonAutoFrCA, result.get(0));
@@ -385,7 +385,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_JA_JP), imi);
assertEquals(3, result.size());
verifyEquality(nonAutoJa, result.get(0));
@@ -407,7 +407,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_JA_JP), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoHi, result.get(0));
@@ -424,7 +424,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_JA_JP), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoEnUS, result.get(0));
@@ -441,7 +441,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_JA_JP), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoEnUS, result.get(0));
@@ -463,7 +463,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(Locale.forLanguageTag("sr-Latn-RS")), imi);
assertEquals(2, result.size());
assertThat(nonAutoSrLatn, is(in(result)));
@@ -483,7 +483,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(Locale.forLanguageTag("sr-Cyrl-RS")), imi);
assertEquals(2, result.size());
assertThat(nonAutoSrCyrl, is(in(result)));
@@ -509,7 +509,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(
Locale.forLanguageTag("sr-Latn-RS-x-android"),
Locale.forLanguageTag("ja-JP"),
@@ -536,7 +536,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_FIL_PH), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoFil, result.get(0));
@@ -554,7 +554,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_FI), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoJa, result.get(0));
@@ -570,7 +570,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_IN), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoIn, result.get(0));
@@ -584,7 +584,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_ID), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoIn, result.get(0));
@@ -598,7 +598,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_IN), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoId, result.get(0));
@@ -612,7 +612,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_ID), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoId, result.get(0));
@@ -634,7 +634,7 @@
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypes(
new LocaleList(LOCALE_FR, LOCALE_EN_US, LOCALE_JA_JP), imi);
assertThat(nonAutoFrCA, is(in(result)));
assertThat(nonAutoEnUS, is(in(result)));
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
index 4095be7..18dc114 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
@@ -20,11 +20,13 @@
import android.annotation.NonNull;
import android.app.backup.BackupHelper;
+import android.app.backup.BackupHelperWithLogger;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
import static org.mockito.Mockito.when;
@@ -32,7 +34,10 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.server.backup.Flags;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -55,6 +60,9 @@
@Mock
private PackageManager mPackageManagerMock;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -71,7 +79,7 @@
mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
- assertThat(mSystemBackupAgent.mAddedHelpers)
+ assertThat(mSystemBackupAgent.mAddedHelpersKey)
.containsExactly(
"account_sync_settings",
"preferred_activities",
@@ -96,7 +104,7 @@
mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
- assertThat(mSystemBackupAgent.mAddedHelpers)
+ assertThat(mSystemBackupAgent.mAddedHelpersKey)
.containsExactly(
"account_sync_settings",
"preferred_activities",
@@ -118,7 +126,7 @@
mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
- assertThat(mSystemBackupAgent.mAddedHelpers)
+ assertThat(mSystemBackupAgent.mAddedHelpersKey)
.containsExactly(
"account_sync_settings",
"notifications",
@@ -134,7 +142,7 @@
mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
- assertThat(mSystemBackupAgent.mAddedHelpers)
+ assertThat(mSystemBackupAgent.mAddedHelpersKey)
.containsExactly(
"account_sync_settings",
"preferred_activities",
@@ -147,12 +155,42 @@
"companion");
}
+ @Test
+ public void onAddHelperIfEligibleForUser_flagIsOff_helpersHaveNoLogger() {
+ UserHandle userHandle = new UserHandle(UserHandle.USER_SYSTEM);
+ when(mUserManagerMock.isProfile()).thenReturn(false);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_METRICS_SYSTEM_BACKUP_AGENTS);
+
+ mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
+
+ for (BackupHelperWithLogger helper:mSystemBackupAgent.mAddedHelpers){
+ assertThat(helper.isLoggerSet()).isFalse();
+ }
+ }
+
+ @Test
+ public void onAddHelperIfEligibleForUser_flagIsOn_helpersHaveLogger() {
+ UserHandle userHandle = new UserHandle(UserHandle.USER_SYSTEM);
+ when(mUserManagerMock.isProfile()).thenReturn(false);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_METRICS_SYSTEM_BACKUP_AGENTS);
+
+ mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
+
+ for (BackupHelperWithLogger helper:mSystemBackupAgent.mAddedHelpers){
+ assertThat(helper.isLoggerSet()).isTrue();
+ }
+ }
+
private class TestableSystemBackupAgent extends SystemBackupAgent {
- final Set<String> mAddedHelpers = new ArraySet<>();
+ final Set<String> mAddedHelpersKey = new ArraySet<>();
+ final Set<BackupHelperWithLogger> mAddedHelpers = new ArraySet<>();
@Override
public void addHelper(String keyPrefix, BackupHelper helper) {
- mAddedHelpers.add(keyPrefix);
+ mAddedHelpersKey.add(keyPrefix);
+ if (helper instanceof BackupHelperWithLogger) {
+ mAddedHelpers.add((BackupHelperWithLogger) helper);
+ }
}
@Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index ec7e359..a65ef00 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -367,7 +367,7 @@
verify(mInstallerService).uninstall(
eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)),
eq(CALLER_PACKAGE), eq(DELETE_ARCHIVE | DELETE_KEEP_DATA), eq(mIntentSender),
- eq(UserHandle.CURRENT.getIdentifier()), anyInt());
+ eq(UserHandle.CURRENT.getIdentifier()), anyInt(), anyInt());
ArchiveState expectedArchiveState = createArchiveState();
ArchiveState actualArchiveState = mPackageSetting.readUserState(
@@ -391,7 +391,7 @@
eq(CALLER_PACKAGE),
eq(DELETE_ARCHIVE | DELETE_KEEP_DATA),
eq(mIntentSender),
- eq(UserHandle.CURRENT.getIdentifier()), anyInt());
+ eq(UserHandle.CURRENT.getIdentifier()), anyInt(), anyInt());
ArchiveState expectedArchiveState = createArchiveState();
ArchiveState actualArchiveState = mPackageSetting.readUserState(
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 0831086..be68e9c 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -78,6 +78,7 @@
"securebox",
"flag-junit",
"ravenwood-junit",
+ "net_flags_lib",
],
libs: [
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 0973d46..5e38010 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -25,6 +25,7 @@
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE;
import static com.google.common.truth.Truth.assertThat;
@@ -1807,4 +1808,35 @@
// TV should only send <Give Osd Name> once
assertEquals(1, Collections.frequency(mNativeWrapper.getResultMessages(), giveOsdName));
}
+
+ @Test
+ public void initiateCecByWakeupMessage_selectInternalSourceAfterDelay_broadcastsActiveSource() {
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_WAKE_UP_MESSAGE);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ mHdmiCecLocalDeviceTv.deviceSelect(ADDR_TV, new TestCallback());
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(activeSourceFromTv);
+ }
+
+ @Test
+ public void initiateCecByWakeupMessage_selectInternalSource_doesNotBroadcastActiveSource() {
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_WAKE_UP_MESSAGE);
+ mTestLooper.dispatchAll();
+
+ mHdmiCecLocalDeviceTv.deviceSelect(ADDR_TV, new TestCallback());
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 5a62d92..5081198 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -16,6 +16,8 @@
package com.android.server.locksettings;
+import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS;
+
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
@@ -30,25 +32,30 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.PropertyInvalidatedCache;
+import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.gatekeeper.GateKeeperResponse;
import android.text.TextUtils;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.widget.ILockSettingsStateListener;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -59,6 +66,7 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
@@ -399,6 +407,60 @@
}
@Test
+ public void testVerifyCredential_notifyLockSettingsStateListeners_whenGoodPassword()
+ throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
+ final LockscreenCredential password = newPassword("password");
+ setCredential(PRIMARY_USER_ID, password);
+ final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+ mLocalService.registerLockSettingsStateListener(listener);
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+ .getResponseCode());
+
+ verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void testVerifyCredential_notifyLockSettingsStateListeners_whenBadPassword()
+ throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
+ final LockscreenCredential password = newPassword("password");
+ setCredential(PRIMARY_USER_ID, password);
+ final LockscreenCredential badPassword = newPassword("badPassword");
+ final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+ mLocalService.registerLockSettingsStateListener(listener);
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+ mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */)
+ .getResponseCode());
+
+ verify(listener).onAuthenticationFailed(PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void testLockSettingsStateListener_registeredThenUnregistered() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
+ final LockscreenCredential password = newPassword("password");
+ setCredential(PRIMARY_USER_ID, password);
+ final LockscreenCredential badPassword = newPassword("badPassword");
+ final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+
+ mLocalService.registerLockSettingsStateListener(listener);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+ .getResponseCode());
+ verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID);
+
+ mLocalService.unregisterLockSettingsStateListener(listener);
+ assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+ mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */)
+ .getResponseCode());
+ verify(listener, never()).onAuthenticationFailed(PRIMARY_USER_ID);
+ }
+
+ @Test
public void testSetCredentialNotPossibleInSecureFrpModeDuringSuw() {
setUserSetupComplete(false);
setSecureFrpMode(true);
@@ -537,4 +599,12 @@
assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
}
}
+
+ private ILockSettingsStateListener mockLockSettingsStateListener() {
+ ILockSettingsStateListener listener = mock(ILockSettingsStateListener.Stub.class);
+ IBinder binder = mock(IBinder.class);
+ when(binder.isBinderAlive()).thenReturn(true);
+ when(listener.asBinder()).thenReturn(binder);
+ return listener;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
index 13dc120..d6d2b6d 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.net;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -327,12 +328,20 @@
isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_DENY, true);
expected.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, isRestrictedForLowPowerStandby);
+ // Background chain
+ final ArrayMap<Integer, Boolean> isRestrictedInBackground = new ArrayMap<>();
+ isRestrictedInBackground.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+ isRestrictedInBackground.put(INetd.FIREWALL_RULE_ALLOW, false);
+ isRestrictedInBackground.put(INetd.FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_BACKGROUND, isRestrictedInBackground);
+
final int[] chains = {
FIREWALL_CHAIN_STANDBY,
FIREWALL_CHAIN_POWERSAVE,
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_RESTRICTED,
- FIREWALL_CHAIN_LOW_POWER_STANDBY
+ FIREWALL_CHAIN_LOW_POWER_STANDBY,
+ FIREWALL_CHAIN_BACKGROUND
};
final int[] states = {
INetd.FIREWALL_RULE_ALLOW,
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 2a76452..4451cae 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -26,12 +26,14 @@
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
import static android.net.ConnectivityManager.TYPE_MOBILE;
@@ -48,8 +50,13 @@
import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_NOT_IN_BACKGROUND;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLIST;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_TOP;
+import static android.net.NetworkPolicyManager.BACKGROUND_THRESHOLD_STATE;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.POLICY_NONE;
@@ -64,6 +71,7 @@
import static android.net.NetworkTemplate.MATCH_CARRIER;
import static android.net.NetworkTemplate.MATCH_MOBILE;
import static android.net.NetworkTemplate.MATCH_WIFI;
+import static android.os.PowerExemptionManager.REASON_OTHER;
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
@@ -146,6 +154,8 @@
import android.os.Handler;
import android.os.INetworkManagementService;
import android.os.PersistableBundle;
+import android.os.PowerExemptionManager;
+import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.RemoteException;
@@ -153,6 +163,9 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -169,6 +182,7 @@
import android.util.Range;
import android.util.RecurrenceRule;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
@@ -243,6 +257,9 @@
public class NetworkPolicyManagerServiceTest {
private static final String TAG = "NetworkPolicyManagerServiceTest";
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final long TEST_START = 1194220800000L;
private static final String TEST_IFACE = "test0";
private static final String TEST_WIFI_NETWORK_KEY = "TestWifiNetworkKey";
@@ -285,6 +302,7 @@
private @Mock TelephonyManager mTelephonyManager;
private @Mock UserManager mUserManager;
private @Mock NetworkStatsManager mStatsManager;
+ private @Mock PowerExemptionManager mPowerExemptionManager;
private TestDependencies mDeps;
private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor =
@@ -302,6 +320,7 @@
private NetworkPolicyManagerService mService;
private final ArraySet<BroadcastReceiver> mRegisteredReceivers = new ArraySet<>();
+ private BroadcastReceiver mPowerAllowlistReceiver;
/**
* In some of the tests while initializing NetworkPolicyManagerService,
@@ -446,6 +465,7 @@
@Before
public void callSystemReady() throws Exception {
MockitoAnnotations.initMocks(this);
+ when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean())).thenReturn(new int[0]);
final Context context = InstrumentationRegistry.getContext();
@@ -482,6 +502,8 @@
return mUserManager;
case Context.NETWORK_STATS_SERVICE:
return mStatsManager;
+ case Context.POWER_EXEMPTION_SERVICE:
+ return mPowerExemptionManager;
default:
return super.getSystemService(name);
}
@@ -495,6 +517,9 @@
@Override
public Intent registerReceiver(BroadcastReceiver receiver,
IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ if (filter.hasAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED)) {
+ mPowerAllowlistReceiver = receiver;
+ }
mRegisteredReceivers.add(receiver);
return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
}
@@ -2066,6 +2091,12 @@
expectHasUseRestrictedNetworksPermission(UID_A, true);
expectHasUseRestrictedNetworksPermission(UID_B, false);
+ // Set low enough proc-states to ensure these uids are allowed in the background chain.
+ // To maintain clean separation between separate firewall chains, the tests could
+ // check for the specific blockedReasons in the uidBlockedState.
+ callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE - 1, 21);
+ callAndWaitOnUidStateChanged(UID_B, BACKGROUND_THRESHOLD_STATE - 1, 21);
+
Map<Integer, Integer> firewallUidRules = new ArrayMap<>();
doAnswer(arg -> {
int[] uids = arg.getArgument(1);
@@ -2113,7 +2144,111 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+ public void testBackgroundChainEnabled() throws Exception {
+ verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true);
+ }
+
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+ public void testBackgroundChainOnProcStateChange() throws Exception {
+ // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+ clearInvocations(mNetworkManager);
+
+ mService.mBackgroundRestrictionDelayMs = 500; // To avoid waiting too long in tests.
+
+ // The app will be blocked when there is no prior proc-state.
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+ int procStateSeq = 23;
+ callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE - 1, procStateSeq++);
+
+ verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+ FIREWALL_RULE_ALLOW);
+ assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+
+ callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, procStateSeq++);
+
+ // The app should be blocked after a delay. Posting a message just after the delay and
+ // waiting for it to complete to ensure that the blocking code has executed.
+ waitForDelayedMessageOnHandler(mService.mBackgroundRestrictionDelayMs + 1);
+
+ verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+ FIREWALL_RULE_DEFAULT);
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+ public void testBackgroundChainOnAllowlistChange() throws Exception {
+ // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+ clearInvocations(mNetworkManager);
+
+ // The apps will be blocked when there is no prior proc-state.
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+ assertTrue(mService.isUidNetworkingBlocked(UID_B, false));
+
+ final int procStateSeq = 29;
+ callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, procStateSeq);
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+ when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean()))
+ .thenReturn(new int[]{APP_ID_A, APP_ID_B});
+ final SparseIntArray firewallUidRules = new SparseIntArray();
+ doAnswer(arg -> {
+ final int[] uids = arg.getArgument(1);
+ final int[] rules = arg.getArgument(2);
+ assertTrue(uids.length == rules.length);
+
+ for (int i = 0; i < uids.length; ++i) {
+ firewallUidRules.put(uids[i], rules[i]);
+ }
+ return null;
+ }).when(mNetworkManager).setFirewallUidRules(eq(FIREWALL_CHAIN_BACKGROUND),
+ any(int[].class), any(int[].class));
+
+ mPowerAllowlistReceiver.onReceive(mServiceContext, null);
+
+ assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_A, -1));
+ assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_B, -1));
+
+ assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+ assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+ public void testBackgroundChainOnTempAllowlistChange() throws Exception {
+ // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+ clearInvocations(mNetworkManager);
+
+ // The app will be blocked as is no prior proc-state.
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+ final int procStateSeq = 19;
+ callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, procStateSeq);
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+ final NetworkPolicyManagerInternal internal = LocalServices.getService(
+ NetworkPolicyManagerInternal.class);
+
+ internal.onTempPowerSaveWhitelistChange(APP_ID_A, true, REASON_OTHER, "testing");
+
+ verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+ FIREWALL_RULE_ALLOW);
+ assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+
+ internal.onTempPowerSaveWhitelistChange(APP_ID_A, false, REASON_OTHER, "testing");
+
+ verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+ FIREWALL_RULE_DEFAULT);
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+ }
+
+ @Test
public void testLowPowerStandbyAllowlist() throws Exception {
+ // Chain background is also enabled but these procstates are important enough to be exempt.
callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_TOP, 0);
callAndWaitOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
callAndWaitOnUidStateChanged(UID_C, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
@@ -2200,7 +2335,21 @@
ALLOWED_REASON_TOP), BLOCKED_REASON_NONE);
effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_LOW_POWER_STANDBY,
ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST), BLOCKED_REASON_NONE);
- // TODO: test more combinations of blocked reasons.
+
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+ ALLOWED_REASON_NOT_IN_BACKGROUND), BLOCKED_REASON_NONE);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND
+ | BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_NOT_IN_BACKGROUND),
+ BLOCKED_REASON_BATTERY_SAVER);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND
+ | BLOCKED_REASON_DOZE, ALLOWED_REASON_NOT_IN_BACKGROUND),
+ BLOCKED_REASON_DOZE);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+ ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS), BLOCKED_REASON_APP_BACKGROUND);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+ ALLOWED_REASON_POWER_SAVE_ALLOWLIST), BLOCKED_REASON_NONE);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+ ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST), BLOCKED_REASON_NONE);
for (Map.Entry<Pair<Integer, Integer>, Integer> test : effectiveBlockedReasons.entrySet()) {
final int expectedEffectiveBlockedReasons = test.getValue();
@@ -2529,7 +2678,6 @@
private FutureIntent mRestrictBackgroundChanged;
private void postMsgAndWaitForCompletion() throws InterruptedException {
- final Handler handler = mService.getHandlerForTesting();
final CountDownLatch latch = new CountDownLatch(1);
mService.getHandlerForTesting().post(latch::countDown);
if (!latch.await(5, TimeUnit.SECONDS)) {
@@ -2537,6 +2685,14 @@
}
}
+ private void waitForDelayedMessageOnHandler(long delayMs) throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mService.getHandlerForTesting().postDelayed(latch::countDown, delayMs);
+ if (!latch.await(delayMs + 5_000, TimeUnit.MILLISECONDS)) {
+ fail("Timed out waiting for delayed msg to be handled");
+ }
+ }
+
private void setSubscriptionPlans(int subId, SubscriptionPlan[] plans, String callingPackage)
throws InterruptedException {
mService.setSubscriptionPlans(subId, plans, 0, callingPackage);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index ba7b52e..2a89b02 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1762,32 +1762,6 @@
assertEquals(1, task.getChildCount());
}
- /**
- * Test that an activity will not be destroyed if it is marked as non-destroyable.
- */
- @Test
- public void testSafelyDestroy_nonDestroyable() {
- final ActivityRecord activity = createActivityWithTask();
- doReturn(false).when(activity).isDestroyable();
-
- activity.safelyDestroy("test");
-
- verify(activity, never()).destroyImmediately(anyString());
- }
-
- /**
- * Test that an activity will not be destroyed if it is marked as non-destroyable.
- */
- @Test
- public void testSafelyDestroy_destroyable() {
- final ActivityRecord activity = createActivityWithTask();
- doReturn(true).when(activity).isDestroyable();
-
- activity.safelyDestroy("test");
-
- verify(activity).destroyImmediately(anyString());
- }
-
@Test
public void testRemoveImmediately() {
final Consumer<Consumer<ActivityRecord>> test = setup -> {
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 402cbcc..c44be7b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -56,6 +56,7 @@
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import android.util.ArraySet;
import android.view.WindowManager;
import android.window.BackAnimationAdapter;
@@ -69,6 +70,7 @@
import android.window.WindowOnBackInvokedDispatcher;
import com.android.server.LocalServices;
+import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -81,6 +83,12 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+/**
+ * Tests for the {@link BackNavigationController} class.
+ *
+ * Build/Install/Run:
+ * atest WmTests:BackNavigationControllerTests
+ */
@Presubmit
@RunWith(WindowTestRunner.class)
public class BackNavigationControllerTests extends WindowTestsBase {
@@ -623,6 +631,22 @@
0, navigationObserver.getCount());
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG)
+ public void testAdjacentFocusInActivityEmbedding() {
+ Task task = createTask(mDefaultDisplay);
+ TaskFragment primary = createTaskFragmentWithActivity(task);
+ TaskFragment secondary = createTaskFragmentWithActivity(task);
+ primary.setAdjacentTaskFragment(secondary);
+ secondary.setAdjacentTaskFragment(primary);
+
+ WindowState windowState = mock(WindowState.class);
+ doReturn(windowState).when(mWm).getFocusedWindowLocked();
+ doReturn(primary).when(windowState).getTaskFragment();
+
+ startBackNavigation();
+ verify(mWm).moveFocusToAdjacentWindow(any(), anyInt());
+ }
/**
* Test with
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 782d89c..95850ac 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -2131,8 +2131,8 @@
// Once transition starts, rotation is applied and transition shows DC rotating.
testPlayer.startTransition();
waitUntilHandlersIdle();
- verify(activity1).ensureActivityConfiguration(anyBoolean(), anyBoolean());
- verify(activity2).ensureActivityConfiguration(anyBoolean(), anyBoolean());
+ verify(activity1).ensureActivityConfiguration(anyBoolean());
+ verify(activity2).ensureActivityConfiguration(anyBoolean());
assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
assertNotNull(testPlayer.mLastReady);
assertTrue(testPlayer.mController.isPlaying());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index be96e60..9e00f92 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -283,12 +283,12 @@
policy.screenTurnedOff();
policy.setAwake(false);
- policy.screenTurnedOn(null /* screenOnListener */);
+ policy.screenTurningOn(null /* screenOnListener */);
assertTrue(wpc.isShowingUiWhileDozing());
policy.screenTurnedOff();
assertFalse(wpc.isShowingUiWhileDozing());
- policy.screenTurnedOn(null /* screenOnListener */);
+ policy.screenTurningOn(null /* screenOnListener */);
assertTrue(wpc.isShowingUiWhileDozing());
policy.setAwake(true);
assertFalse(wpc.isShowingUiWhileDozing());
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 5518c60..6759eef 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -197,10 +197,10 @@
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
// Translucent Activity
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
.setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
.build();
- doReturn(false).when(translucentActivity).fillsParent();
mTask.addChild(translucentActivity);
translucentActivity.setState(DESTROYED, "testing");
@@ -225,10 +225,10 @@
// Translucent Activity
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
.setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
.build();
- doReturn(false).when(translucentActivity).fillsParent();
mTask.addChild(translucentActivity);
spyOn(translucentActivity.mLetterboxUiController);
@@ -300,10 +300,10 @@
// Translucent Activity
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
.setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
.build();
- doReturn(false).when(translucentActivity).fillsParent();
mTask.addChild(translucentActivity);
spyOn(translucentActivity.mLetterboxUiController);
@@ -376,10 +376,10 @@
// Launch translucent Activity
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
.setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
.build();
- doReturn(false).when(translucentActivity).fillsParent();
mTask.addChild(translucentActivity);
// Transparent strategy applied
assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
@@ -404,10 +404,10 @@
// Launch translucent Activity
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
.setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
.build();
- doReturn(false).when(translucentActivity).fillsParent();
mTask.addChild(translucentActivity);
// Transparent strategy applied
assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
@@ -441,10 +441,10 @@
// Launch translucent Activity
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
.setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
.build();
- doReturn(false).when(translucentActivity).fillsParent();
mTask.addChild(translucentActivity);
// Transparent strategy applied
assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
@@ -465,12 +465,12 @@
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
// Translucent Activity
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
.setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
.setMinAspectRatio(1.1f)
.setMaxAspectRatio(3f)
.build();
- doReturn(false).when(translucentActivity).fillsParent();
mTask.addChild(translucentActivity);
// We check bounds
final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
@@ -493,9 +493,9 @@
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
// Translucent Activity
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
.build();
- doReturn(false).when(translucentActivity).fillsParent();
final Configuration requestedConfig =
translucentActivity.getRequestedOverrideConfiguration();
final WindowConfiguration translucentWinConf = requestedConfig.windowConfiguration;
@@ -525,12 +525,12 @@
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
// Translucent Activity
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
.setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
.setMinAspectRatio(1.1f)
.setMaxAspectRatio(3f)
.build();
- doReturn(false).when(translucentActivity).fillsParent();
mTask.addChild(translucentActivity);
// We check bounds
final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
@@ -538,10 +538,10 @@
assertEquals(opaqueBounds, translucentRequestedBounds);
// Launch another translucent activity
final ActivityRecord translucentActivity2 = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
.setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
.build();
- doReturn(false).when(translucentActivity2).fillsParent();
mTask.addChild(translucentActivity2);
// We check bounds
final Rect translucent2RequestedBounds = translucentActivity2.getRequestedOverrideBounds();
@@ -558,9 +558,9 @@
// simplicity.
doReturn(true).when(mActivity).isEmbedded();
// Translucent Activity
- final ActivityRecord translucentActivity = new ActivityBuilder(mAtm).build();
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent).build();
doReturn(false).when(translucentActivity).matchParentBounds();
- doReturn(false).when(translucentActivity).fillsParent();
mTask.addChild(translucentActivity);
// Check the strategy has not being applied
assertFalse(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
@@ -580,10 +580,10 @@
assertFalse(mActivity.inSizeCompatMode());
// We launch a transparent activity
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
.setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
.build();
- doReturn(false).when(translucentActivity).fillsParent();
mTask.addChild(translucentActivity);
// It should not be in SCM
assertFalse(translucentActivity.inSizeCompatMode());
@@ -600,12 +600,16 @@
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
// Translucent Activity
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
.build();
- doReturn(false).when(translucentActivity).fillsParent();
- spyOn(mActivity);
+ assertFalse(translucentActivity.fillsParent());
+ assertTrue(mActivity.fillsParent());
+ mActivity.finishing = true;
+ assertFalse(mActivity.occludesParent());
mTask.addChild(translucentActivity);
- verify(mActivity).isFinishing();
+ // The translucent activity won't inherit letterbox behavior from a finishing activity.
+ assertFalse(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
}
@Test
@@ -619,10 +623,10 @@
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
// We launch a transparent activity
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
.setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
.build();
- doReturn(false).when(translucentActivity).fillsParent();
mTask.addChild(translucentActivity);
assertEquals(translucentActivity.getBounds(), mActivity.getBounds());
@@ -655,10 +659,10 @@
// We launch a transparent activity
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
.setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
.build();
- doReturn(false).when(translucentActivity).fillsParent();
mTask.addChild(translucentActivity);
// The transparent activity inherits the compat display insets of the opaque activity
@@ -1020,8 +1024,17 @@
// Activity is sandboxed due to fixed aspect ratio.
assertActivityMaxBoundsSandboxed();
+ // Prepare the states for verifying relaunching after changing orientation.
+ mActivity.finishRelaunching();
+ mActivity.setState(RESUMED, "testFixedAspectRatioOrientationChangeOrientation");
+ mActivity.setLastReportedConfiguration(mAtm.getGlobalConfiguration(),
+ mActivity.getConfiguration());
+
// Change the fixed orientation.
mActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ assertTrue(mActivity.isRelaunching());
+ assertTrue(mActivity.mLetterboxUiController
+ .getIsRelaunchingAfterRequestedOrientationChanged());
assertFitted();
assertEquals(originalBounds.width(), mActivity.getBounds().height());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 45ecc3f..00ecd00 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -215,7 +215,7 @@
doReturn(false).when(newDisplay).supportsSystemDecorations();
}
// Update the display policy to make the screen fully turned on so animation is allowed
- displayPolicy.screenTurnedOn(null /* screenOnListener */);
+ displayPolicy.screenTurningOn(null /* screenOnListener */);
displayPolicy.finishKeyguardDrawn();
displayPolicy.finishWindowsDrawn();
displayPolicy.finishScreenTurningOn();
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 c7c7913..a0bafb6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -229,7 +229,7 @@
mDefaultDisplay = mWm.mRoot.getDefaultDisplay();
// Update the display policy to make the screen fully turned on so animation is allowed
final DisplayPolicy displayPolicy = mDefaultDisplay.getDisplayPolicy();
- displayPolicy.screenTurnedOn(null /* screenOnListener */);
+ displayPolicy.screenTurningOn(null /* screenOnListener */);
displayPolicy.finishKeyguardDrawn();
displayPolicy.finishWindowsDrawn();
displayPolicy.finishScreenTurningOn();
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index b22d8ac..cbd5524 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -13148,7 +13148,7 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public ServiceState getServiceStateForSubscriber(int subId) {
- return getServiceStateForSubscriber(getSubId(), false, false);
+ return getServiceStateForSubscriber(subId, false, false);
}
/**
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 45dd02c..f3f1838 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -56,6 +56,7 @@
#include "java/JavaClassGenerator.h"
#include "java/ManifestClassGenerator.h"
#include "java/ProguardRules.h"
+#include "link/FeatureFlagsFilter.h"
#include "link/Linkers.h"
#include "link/ManifestFixer.h"
#include "link/NoDefaultResourceRemover.h"
@@ -1987,6 +1988,19 @@
context_->SetNameManglerPolicy(NameManglerPolicy{context_->GetCompilationPackage()});
context_->SetSplitNameDependencies(app_info_.split_name_dependencies);
+ FeatureFlagsFilterOptions flags_filter_options;
+ if (context_->GetMinSdkVersion() > SDK_UPSIDE_DOWN_CAKE) {
+ // For API version > U, PackageManager will dynamically read the flag values and disable
+ // manifest elements accordingly when parsing the manifest.
+ // For API version <= U, we remove disabled elements from the manifest with the filter.
+ flags_filter_options.remove_disabled_elements = false;
+ flags_filter_options.flags_must_have_value = false;
+ }
+ FeatureFlagsFilter flags_filter(options_.feature_flag_values, flags_filter_options);
+ if (!flags_filter.Consume(context_, manifest_xml.get())) {
+ return 1;
+ }
+
// Override the package ID when it is "android".
if (context_->GetCompilationPackage() == "android") {
context_->SetPackageId(kAndroidPackageId);
@@ -2531,7 +2545,7 @@
}
for (const std::string& arg : all_feature_flags_args) {
- if (ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) {
+ if (!ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) {
return 1;
}
}
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 26713fd..dc18b1c 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -330,7 +330,11 @@
"should only be used together with the --static-lib flag.",
&options_.merge_only);
AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_);
- AddOptionalFlagList("--feature-flags", "Placeholder, to be implemented.", &feature_flags_args_);
+ AddOptionalFlagList("--feature-flags",
+ "Specify the values of feature flags. The pairs in the argument\n"
+ "are separated by ',' and the name is separated from the value by '='.\n"
+ "Example: \"flag1=true,flag2=false,flag3=\" (flag3 has no given value).",
+ &feature_flags_args_);
}
int Action(const std::vector<std::string>& args) override;
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 7096f5c..9323f3b 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -16,11 +16,10 @@
#include "Link.h"
-#include <android-base/file.h>
-
-#include "AppInfo.h"
#include "Diagnostics.h"
#include "LoadedApk.h"
+#include "android-base/file.h"
+#include "android-base/stringprintf.h"
#include "test/Test.h"
using testing::Eq;
@@ -993,4 +992,213 @@
ASSERT_FALSE(Link(link_args, &diag));
}
+static void BuildSDKWithFeatureFlagAttr(const std::string& apk_path, const std::string& java_path,
+ CommandTestFixture* fixture, android::IDiagnostics* diag) {
+ const std::string android_values =
+ R"(<resources>
+ <staging-public-group type="attr" first-id="0x01fe0063">
+ <public name="featureFlag" />
+ </staging-public-group>
+ <attr name="featureFlag" format="string" />
+ </resources>)";
+
+ SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
+ BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
+}
+
+TEST_F(LinkTest, FeatureFlagDisabled_SdkAtMostUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_UPSIDE_DOWN_CAKE);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=false");
+
+ const std::string app_apk = GetTestPath("app.apk");
+ BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+ // Permission element should be removed if flag is disabled
+ auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+ auto apk_manifest = apk->GetManifest();
+ ASSERT_THAT(apk_manifest, NotNull());
+ auto root = apk_manifest->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, IsNull());
+}
+
+TEST_F(LinkTest, FeatureFlagEnabled_SdkAtMostUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_UPSIDE_DOWN_CAKE);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=true");
+
+ const std::string app_apk = GetTestPath("app.apk");
+ BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+ // Permission element should be kept if flag is enabled
+ auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+ auto apk_manifest = apk->GetManifest();
+ ASSERT_THAT(apk_manifest, NotNull());
+ auto root = apk_manifest->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST_F(LinkTest, FeatureFlagWithNoValue_SdkAtMostUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_UPSIDE_DOWN_CAKE);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=");
+
+ // Flags must have values if <= UDC
+ const std::string app_apk = GetTestPath("app.apk");
+ ASSERT_FALSE(Link(app_link_args.Build(app_apk), &diag));
+}
+
+TEST_F(LinkTest, FeatureFlagDisabled_SdkAfterUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_CUR_DEVELOPMENT);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=false");
+
+ const std::string app_apk = GetTestPath("app.apk");
+ BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+ // Permission element should be kept if > UDC, regardless of flag value
+ auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+ auto apk_manifest = apk->GetManifest();
+ ASSERT_THAT(apk_manifest, NotNull());
+ auto root = apk_manifest->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST_F(LinkTest, FeatureFlagEnabled_SdkAfterUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_CUR_DEVELOPMENT);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=true");
+
+ const std::string app_apk = GetTestPath("app.apk");
+ BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+ // Permission element should be kept if > UDC, regardless of flag value
+ auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+ auto apk_manifest = apk->GetManifest();
+ ASSERT_THAT(apk_manifest, NotNull());
+ auto root = apk_manifest->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST_F(LinkTest, FeatureFlagWithNoValue_SdkAfterUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_CUR_DEVELOPMENT);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=");
+
+ const std::string app_apk = GetTestPath("app.apk");
+ BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+ // Permission element should be kept if > UDC, regardless of flag value
+ auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+ auto apk_manifest = apk->GetManifest();
+ ASSERT_THAT(apk_manifest, NotNull());
+ auto root = apk_manifest->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index 8c644cf..a7f6f55 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -64,29 +64,31 @@
{"@FlaggedApi", AnnotationRule::kFlaggedApi, "@android.annotation.FlaggedApi", true},
}};
-void AnnotationProcessor::AppendCommentLine(std::string comment) {
+void AnnotationProcessor::AppendCommentLine(std::string comment, bool add_api_annotations) {
static constexpr std::string_view sDeprecated = "@deprecated";
- // Treat deprecated specially, since we don't remove it from the source comment.
- if (comment.find(sDeprecated) != std::string::npos) {
- annotation_parameter_map_[AnnotationRule::kDeprecated] = "";
- }
+ if (add_api_annotations) {
+ // Treat deprecated specially, since we don't remove it from the source comment.
+ if (comment.find(sDeprecated) != std::string::npos) {
+ annotation_parameter_map_[AnnotationRule::kDeprecated] = "";
+ }
- for (const AnnotationRule& rule : sAnnotationRules) {
- std::string::size_type idx = comment.find(rule.doc_str.data());
- if (idx != std::string::npos) {
- // Captures all parameters associated with the specified annotation rule
- // by matching the first pair of parentheses after the rule.
- std::regex re(std::string(rule.doc_str).append(R"(\s*\((.+)\))"));
- std::smatch match_result;
- const bool is_match = std::regex_search(comment, match_result, re);
- if (is_match && rule.preserve_params) {
- annotation_parameter_map_[rule.bit_mask] = match_result[1].str();
- comment.erase(comment.begin() + match_result.position(),
- comment.begin() + match_result.position() + match_result.length());
- } else {
- annotation_parameter_map_[rule.bit_mask] = "";
- comment.erase(comment.begin() + idx, comment.begin() + idx + rule.doc_str.size());
+ for (const AnnotationRule& rule : sAnnotationRules) {
+ std::string::size_type idx = comment.find(rule.doc_str.data());
+ if (idx != std::string::npos) {
+ // Captures all parameters associated with the specified annotation rule
+ // by matching the first pair of parentheses after the rule.
+ std::regex re(std::string(rule.doc_str).append(R"(\s*\((.+)\))"));
+ std::smatch match_result;
+ const bool is_match = std::regex_search(comment, match_result, re);
+ if (is_match && rule.preserve_params) {
+ annotation_parameter_map_[rule.bit_mask] = match_result[1].str();
+ comment.erase(comment.begin() + match_result.position(),
+ comment.begin() + match_result.position() + match_result.length());
+ } else {
+ annotation_parameter_map_[rule.bit_mask] = "";
+ comment.erase(comment.begin() + idx, comment.begin() + idx + rule.doc_str.size());
+ }
}
}
}
@@ -109,12 +111,12 @@
comment_ << "\n * " << std::move(comment);
}
-void AnnotationProcessor::AppendComment(StringPiece comment) {
+void AnnotationProcessor::AppendComment(StringPiece comment, bool add_api_annotations) {
// We need to process line by line to clean-up whitespace and append prefixes.
for (StringPiece line : util::Tokenize(comment, '\n')) {
line = util::TrimWhitespace(line);
if (!line.empty()) {
- AppendCommentLine(std::string(line));
+ AppendCommentLine(std::string(line), add_api_annotations);
}
}
}
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
index db3437e..2217ab3 100644
--- a/tools/aapt2/java/AnnotationProcessor.h
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -60,7 +60,10 @@
// Adds more comments. Resources can have value definitions for various configurations, and
// each of the definitions may have comments that need to be processed.
- void AppendComment(android::StringPiece comment);
+ //
+ // If add_api_annotations is false, annotations found in the comment (e.g., "@SystemApi")
+ // will NOT be converted to Java annotations.
+ void AppendComment(android::StringPiece comment, bool add_api_annotations = true);
void AppendNewLine();
@@ -73,7 +76,7 @@
bool has_comments_ = false;
std::unordered_map<uint32_t, std::string> annotation_parameter_map_;
- void AppendCommentLine(std::string line);
+ void AppendCommentLine(std::string line, bool add_api_annotations);
};
} // namespace aapt
diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
index e98e96b..e5eee34 100644
--- a/tools/aapt2/java/AnnotationProcessor_test.cpp
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -136,7 +136,28 @@
EXPECT_THAT(annotations, HasSubstr("This is a system API"));
}
-TEST(AnnotationProcessor, ExtractsFirstSentence) {
+TEST(AnnotationProcessorTest, DoNotAddApiAnnotations) {
+ AnnotationProcessor processor;
+ processor.AppendComment(
+ "@SystemApi This is a system API\n"
+ "@FlaggedApi This is a flagged API\n"
+ "@TestApi This is a test API\n"
+ "@deprecated Deprecate me\n", /*add_api_annotations=*/
+ false);
+
+ std::string annotations;
+ StringOutputStream out(&annotations);
+ Printer printer(&out);
+ processor.Print(&printer);
+ out.Flush();
+
+ EXPECT_THAT(annotations, Not(HasSubstr("@android.annotation.SystemApi")));
+ EXPECT_THAT(annotations, Not(HasSubstr("@android.annotation.FlaggedApi")));
+ EXPECT_THAT(annotations, Not(HasSubstr("@android.annotation.TestApi")));
+ EXPECT_THAT(annotations, Not(HasSubstr("@Deprecated")));
+}
+
+TEST(AnnotationProcessorTest, ExtractsFirstSentence) {
EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence("This is the only sentence"),
Eq("This is the only sentence"));
EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence(
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 58f6564..6e73b01 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -180,7 +180,10 @@
<< "<td>" << std::hex << symbol.value << std::dec << "</td>"
<< "<td>" << util::TrimWhitespace(symbol.symbol.GetComment())
<< "</td></tr>";
- processor->AppendComment(line.str());
+ // add_api_annotations is false since we don't want any annotations
+ // (e.g., "@deprecated")/ found in the enum/flag values to be propagated
+ // up to the attribute.
+ processor->AppendComment(line.str(), /*add_api_annotations=*/false);
}
processor->AppendComment("</table>");
}
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 40395ed..bca9f4b 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -324,7 +324,58 @@
EXPECT_THAT(output, HasSubstr(expected_text));
}
-TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {}
+TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {
+ std::unique_ptr<Attribute> flagAttr =
+ test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_FLAGS)
+ .SetComment("Flag attribute")
+ .AddItemWithComment("flagOne", 0x01, "Flag comment 1")
+ .AddItemWithComment("flagTwo", 0x02, "@deprecated Flag comment 2")
+ .Build();
+ std::unique_ptr<Attribute> enumAttr =
+ test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_ENUM)
+ .SetComment("Enum attribute")
+ .AddItemWithComment("enumOne", 0x01, "@TestApi Enum comment 1")
+ .AddItemWithComment("enumTwo", 0x02, "Enum comment 2")
+ .Build();
+
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .AddValue("android:attr/one", std::move(flagAttr))
+ .AddValue("android:attr/two", std::move(enumAttr))
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGeneratorOptions options;
+ options.use_final = false;
+ JavaClassGenerator generator(context.get(), table.get(), options);
+
+ std::string output;
+ StringOutputStream out(&output);
+ ASSERT_TRUE(generator.Generate("android", &out));
+ out.Flush();
+
+ // Special annotations from the enum/flag values should NOT generate
+ // annotations for the attribute value.
+ EXPECT_THAT(output, Not(HasSubstr("@Deprecated")));
+ EXPECT_THAT(output, Not(HasSubstr("@android.annotation.TestApi")));
+
+ EXPECT_THAT(output, HasSubstr("Flag attribute"));
+ EXPECT_THAT(output, HasSubstr("flagOne"));
+ EXPECT_THAT(output, HasSubstr("Flag comment 1"));
+ EXPECT_THAT(output, HasSubstr("flagTwo"));
+ EXPECT_THAT(output, HasSubstr("@deprecated Flag comment 2"));
+
+ EXPECT_THAT(output, HasSubstr("Enum attribute"));
+ EXPECT_THAT(output, HasSubstr("enumOne"));
+ EXPECT_THAT(output, HasSubstr("@TestApi Enum comment 1"));
+ EXPECT_THAT(output, HasSubstr("enumTwo"));
+ EXPECT_THAT(output, HasSubstr("Enum comment 2"));
+}
TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) {
Attribute attr;
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 65f63dc..b5934e4 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -177,12 +177,25 @@
return *this;
}
+AttributeBuilder& AttributeBuilder::SetComment(StringPiece comment) {
+ attr_->SetComment(comment);
+ return *this;
+}
+
AttributeBuilder& AttributeBuilder::AddItem(StringPiece name, uint32_t value) {
attr_->symbols.push_back(
Attribute::Symbol{Reference(ResourceName({}, ResourceType::kId, name)), value});
return *this;
}
+AttributeBuilder& AttributeBuilder::AddItemWithComment(StringPiece name, uint32_t value,
+ StringPiece comment) {
+ Reference ref(ResourceName({}, ResourceType::kId, name));
+ ref.SetComment(comment);
+ attr_->symbols.push_back(Attribute::Symbol{ref, value});
+ return *this;
+}
+
std::unique_ptr<Attribute> AttributeBuilder::Build() {
return std::move(attr_);
}
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 098535d..9ee44ba 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -116,7 +116,10 @@
AttributeBuilder();
AttributeBuilder& SetTypeMask(uint32_t typeMask);
AttributeBuilder& SetWeak(bool weak);
+ AttributeBuilder& SetComment(android::StringPiece comment);
AttributeBuilder& AddItem(android::StringPiece name, uint32_t value);
+ AttributeBuilder& AddItemWithComment(android::StringPiece name, uint32_t value,
+ android::StringPiece comment);
std::unique_ptr<Attribute> Build();
private:
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 93c1b61..02e4beae 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -251,10 +251,13 @@
return false;
}
- for (StringPiece line : util::Tokenize(contents, ' ')) {
+ for (StringPiece line : util::Tokenize(contents, '\n')) {
line = util::TrimWhitespace(line);
- if (!line.empty()) {
- out_arglist->emplace_back(line);
+ for (StringPiece arg : util::Tokenize(line, ' ')) {
+ arg = util::TrimWhitespace(arg);
+ if (!arg.empty()) {
+ out_arglist->emplace_back(arg);
+ }
}
}
return true;
@@ -270,10 +273,13 @@
return false;
}
- for (StringPiece line : util::Tokenize(contents, ' ')) {
+ for (StringPiece line : util::Tokenize(contents, '\n')) {
line = util::TrimWhitespace(line);
- if (!line.empty()) {
- out_argset->emplace(line);
+ for (StringPiece arg : util::Tokenize(line, ' ')) {
+ arg = util::TrimWhitespace(arg);
+ if (!arg.empty()) {
+ out_argset->emplace(arg);
+ }
}
}
return true;
diff --git a/tools/aapt2/util/Files_test.cpp b/tools/aapt2/util/Files_test.cpp
index 6c38080..618a3e0 100644
--- a/tools/aapt2/util/Files_test.cpp
+++ b/tools/aapt2/util/Files_test.cpp
@@ -25,6 +25,9 @@
using ::android::base::StringPrintf;
+using ::testing::ElementsAre;
+using ::testing::UnorderedElementsAre;
+
namespace aapt {
namespace file {
@@ -34,9 +37,11 @@
constexpr const char sTestDirSep = '/';
#endif
-class FilesTest : public ::testing::Test {
+class FilesTest : public TestDirectoryFixture {
public:
void SetUp() override {
+ TestDirectoryFixture::SetUp();
+
std::stringstream builder;
builder << "hello" << sDirSep << "there";
expected_path_ = builder.str();
@@ -66,6 +71,42 @@
EXPECT_EQ(expected_path_, base);
}
+TEST_F(FilesTest, AppendArgsFromFile) {
+ const std::string args_file = GetTestPath("args.txt");
+ WriteFile(args_file,
+ " \n"
+ "arg1 arg2 arg3 \n"
+ " arg4 arg5");
+ std::vector<std::string> args;
+ std::string error;
+ ASSERT_TRUE(AppendArgsFromFile(args_file, &args, &error));
+ EXPECT_THAT(args, ElementsAre("arg1", "arg2", "arg3", "arg4", "arg5"));
+}
+
+TEST_F(FilesTest, AppendArgsFromFile_InvalidFile) {
+ std::vector<std::string> args;
+ std::string error;
+ ASSERT_FALSE(AppendArgsFromFile(GetTestPath("not_found.txt"), &args, &error));
+}
+
+TEST_F(FilesTest, AppendSetArgsFromFile) {
+ const std::string args_file = GetTestPath("args.txt");
+ WriteFile(args_file,
+ " \n"
+ "arg2 arg4 arg1 \n"
+ " arg5 arg3");
+ std::unordered_set<std::string> args;
+ std::string error;
+ ASSERT_TRUE(AppendSetArgsFromFile(args_file, &args, &error));
+ EXPECT_THAT(args, UnorderedElementsAre("arg1", "arg2", "arg3", "arg4", "arg5"));
+}
+
+TEST_F(FilesTest, AppendSetArgsFromFile_InvalidFile) {
+ std::unordered_set<std::string> args;
+ std::string error;
+ ASSERT_FALSE(AppendSetArgsFromFile(GetTestPath("not_found.txt"), &args, &error));
+}
+
#ifdef _WIN32
TEST_F(FilesTest, WindowsMkdirsLongPath) {
// Creating directory paths longer than the Windows maximum path length (260 charatcers) should