Merge "Rename InputMethodSettings#mCurrentUserId to #mUserId" 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/core/api/current.txt b/core/api/current.txt
index 20b9b0e..adb4eac 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";
@@ -9696,8 +9697,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 +9728,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 +9741,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 +12476,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 +12706,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 +18368,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 +24299,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();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fe32bad..318badf 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";
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/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/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/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/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/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..f9731d4 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" />
@@ -5737,6 +5737,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"
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/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 ba14103..33ea02a 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" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 294b8ae..97b1ec7 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. -->
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..14a4677 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;
@@ -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;
@@ -2779,8 +2784,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 +2851,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 +2869,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/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/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl
index c4806fb..a747e49 100644
--- a/media/java/android/media/tv/ad/ITvAdManager.aidl
+++ b/media/java/android/media/tv/ad/ITvAdManager.aidl
@@ -16,7 +16,6 @@
package android.media.tv.ad;
-import android.graphics.Rect;
import android.media.tv.ad.ITvAdClient;
import android.media.tv.ad.ITvAdManagerCallback;
import android.media.tv.ad.TvAdServiceInfo;
@@ -38,9 +37,4 @@
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 3ca0198..751257c 100644
--- a/media/java/android/media/tv/ad/ITvAdSession.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSession.aidl
@@ -16,7 +16,6 @@
package android.media.tv.ad;
-import android.graphics.Rect;
import android.view.Surface;
/**
@@ -28,8 +27,4 @@
void startAdService();
void setSurface(in Surface surface);
void dispatchSurfaceChanged(int format, int width, int height);
-
- 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 3d5bc89..4df2783 100644
--- a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
+++ b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
@@ -17,8 +17,6 @@
package android.media.tv.ad;
import android.content.Context;
-import android.graphics.Rect;
-import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -45,9 +43,6 @@
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 final HandlerCaller mCaller;
private TvAdService.Session mSessionImpl;
@@ -66,7 +61,6 @@
@Override
public void release() {
- mSessionImpl.scheduleMediaViewCleanup();
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
}
@@ -103,20 +97,6 @@
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;
- }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -149,22 +129,6 @@
mCaller.obtainMessageIIII(DO_DISPATCH_SURFACE_CHANGED, format, width, height, 0));
}
- @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 b2ea00d..9c75051 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -21,7 +21,6 @@
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.flags.Flags;
import android.os.Handler;
@@ -36,7 +35,6 @@
import android.view.InputEvent;
import android.view.InputEventSender;
import android.view.Surface;
-import android.view.View;
import com.android.internal.util.Preconditions;
@@ -288,67 +286,6 @@
}
/**
- * 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}.
*
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 6f7f67d..6995703 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -20,25 +20,19 @@
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.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;
@@ -48,7 +42,6 @@
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
-import android.widget.FrameLayout;
import com.android.internal.os.SomeArgs;
@@ -63,8 +56,6 @@
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
@@ -160,14 +151,7 @@
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;
/**
@@ -182,48 +166,6 @@
}
/**
- * 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();
@@ -238,9 +180,6 @@
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);
}
/**
@@ -368,33 +307,6 @@
}
/**
- * 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) {
@@ -476,137 +388,6 @@
}
}
}
-
- /**
- * 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 5e4a70b..5e67fe9 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -22,8 +22,6 @@
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.RectF;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
@@ -66,9 +64,6 @@
private int mSurfaceViewTop;
private int mSurfaceViewBottom;
- private boolean mMediaViewCreated;
- private Rect mMediaViewFrame;
-
private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@@ -126,20 +121,6 @@
mTvAdManager = (TvAdManager) getContext().getSystemService(Context.TV_AD_SERVICE);
}
- /** @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) {
@@ -169,11 +150,6 @@
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() {
@@ -186,7 +162,6 @@
@Override
protected void updateSurface() {
super.updateSurface();
- relayoutSessionMediaView();
}};
// The surface view's content should be treated as secure all the time.
mSurfaceView.setSecure(true);
@@ -199,69 +174,6 @@
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;
@@ -273,7 +185,7 @@
if (mSession == null) {
return;
}
- mSession.dispatchSurfaceChanged(format, width, height);
+ //mSession.dispatchSurfaceChanged(format, width, height);
}
/**
@@ -334,7 +246,6 @@
dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
}
}
- createSessionMediaView();
} else {
// Failed to create
// Todo: forward error to Tv App
@@ -351,8 +262,6 @@
Log.w(TAG, "onSessionReleased - session not created");
return;
}
- mMediaViewCreated = false;
- mMediaViewFrame = null;
mSessionCallback = null;
mSession = null;
}
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/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/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..a7557d8 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,13 @@
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
@@ -78,6 +81,13 @@
throwComposeUnavailableError()
}
+ override fun createStickyKeysDialog(
+ dialogFactory: SystemUIDialogFactory,
+ viewModel: StickyKeysIndicatorViewModel
+ ): Dialog {
+ throwComposeUnavailableError()
+ }
+
override fun createCommunalView(
context: Context,
viewModel: BaseCommunalViewModel,
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..d63939d 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,8 @@
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -120,6 +125,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/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/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/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/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/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e8201ecb..09b02f8 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>
@@ -1936,5 +1938,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/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/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/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 641064b..2f9fd2e 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,13 @@
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
@@ -86,6 +89,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/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..9d6b30f 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
@@ -40,6 +41,7 @@
constructor(
@Background private val scope: CoroutineScope,
private val glanceableHubTransitions: GlanceableHubTransitions,
+ private val keyguardInteractor: KeyguardInteractor,
override val transitionRepository: KeyguardTransitionRepository,
transitionInteractor: KeyguardTransitionInteractor,
private val powerInteractor: PowerInteractor,
@@ -58,6 +60,8 @@
}
listenForHubToLockscreen()
listenForHubToDozing()
+ listenForHubToPrimaryBouncer()
+ listenForHubToAlternateBouncer()
}
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
@@ -75,10 +79,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 {
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/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/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/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/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/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..b127c51 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;
@@ -117,7 +118,11 @@
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;
@@ -125,6 +130,7 @@
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;
@@ -140,6 +146,9 @@
import java.util.List;
import java.util.function.Consumer;
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.CoroutineScope;
+
/**
* Visual presentation of the volume dialog.
*
@@ -303,6 +312,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,
@@ -319,11 +332,18 @@
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);
@@ -839,6 +859,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);
@@ -1480,6 +1501,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 +1559,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 +2387,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 +2496,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 +2531,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 +2541,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 +2576,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 +2612,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 +2623,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..f180a94 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -22,16 +22,20 @@
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;
@@ -49,6 +53,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 = {
@@ -90,7 +97,11 @@
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,
@@ -106,7 +117,11 @@
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/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/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..892be35 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() }
@@ -278,6 +280,7 @@
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
+ communalInteractor = communalInteractor,
powerInteractor = powerInteractor,
)
.apply { start() }
@@ -288,6 +291,7 @@
bgDispatcher = kosmos.testDispatcher,
mainDispatcher = kosmos.testDispatcher,
glanceableHubTransitions = glanceableHubTransitions,
+ keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
powerInteractor = powerInteractor,
@@ -1023,6 +1027,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 +1128,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 +1146,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
@@ -1640,6 +1716,54 @@
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()
+ }
+
private fun createKeyguardInteractor(): KeyguardInteractor {
return KeyguardInteractorFactory.create(
featureFlags = featureFlags,
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/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 8c823b2..d839da1 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,6 +64,7 @@
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;
@@ -72,6 +74,7 @@
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 +82,7 @@
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 dagger.Lazy;
@@ -97,6 +101,8 @@
import java.util.Arrays;
import java.util.function.Predicate;
+import kotlinx.coroutines.Dispatchers;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -138,7 +144,6 @@
DevicePostureController mPostureController;
@Mock
private Lazy<SecureSettings> mLazySecureSettings;
-
private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
new CsdWarningDialog.Factory() {
@Override
@@ -146,6 +151,8 @@
return mCsdWarningDialog;
}
};
+ @Mock
+ private VibratorHelper mVibratorHelper;
private int mLongestHideShowAnimationDuration = 250;
private FakeSettings mSecureSettings;
@@ -180,6 +187,8 @@
when(mLazySecureSettings.get()).thenReturn(mSecureSettings);
+ when(mVibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(new int[]{0});
+
mDialog = new VolumeDialogImpl(
getContext(),
mVolumeDialogController,
@@ -195,7 +204,11 @@
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/tests/utils/src/android/app/KeyguardManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/app/KeyguardManagerKosmos.kt
new file mode 100644
index 0000000..e5121d5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/app/KeyguardManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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
+
+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/tests/utils/src/android/os/HandlerKosmos.kt b/packages/SystemUI/tests/utils/src/android/os/HandlerKosmos.kt
new file mode 100644
index 0000000..4e2683b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/os/HandlerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os
+
+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/tests/utils/src/android/service/dream/DreamManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/service/dream/DreamManagerKosmos.kt
new file mode 100644
index 0000000..fb51f0f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/service/dream/DreamManagerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.dream
+
+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/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
new file mode 100644
index 0000000..d9ea5e9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.internal.widget
+
+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/tests/utils/src/com/android/systemui/ActivityIntentHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/ActivityIntentHelperKosmos.kt
new file mode 100644
index 0000000..7185b7c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/ActivityIntentHelperKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.activityIntentHelper by Kosmos.Fixture { ActivityIntentHelper(applicationContext) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
new file mode 100644
index 0000000..128f58b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.animation
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.activityLaunchAnimator by Kosmos.Fixture { ActivityLaunchAnimator() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt
new file mode 100644
index 0000000..b7d6f3a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.assist
+
+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/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/tests/utils/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryKosmos.kt
new file mode 100644
index 0000000..46f7355
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.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/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/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
new file mode 100644
index 0000000..7cc5d6b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.shade
+
+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/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
new file mode 100644
index 0000000..1ceab68
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.shade
+
+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/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt
new file mode 100644
index 0000000..4dcd220
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.shade.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.shadeAnimationRepository by Kosmos.Fixture { ShadeAnimationRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
new file mode 100644
index 0000000..57b272f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+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/tests/utils/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractorKosmos.kt
new file mode 100644
index 0000000..17b4603
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.notifications.domain.interactor
+
+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/tests/utils/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryKosmos.kt
new file mode 100644
index 0000000..552b09e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.settings.data.repository
+
+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/tests/utils/src/com/android/systemui/statusbar/NotificationClickNotifierKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationClickNotifierKosmos.kt
new file mode 100644
index 0000000..7b912ae
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationClickNotifierKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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
+
+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/tests/utils/src/com/android/systemui/statusbar/NotificationPresenterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationPresenterKosmos.kt
new file mode 100644
index 0000000..8d30049
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationPresenterKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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
+
+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/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt
new file mode 100644
index 0000000..554bdbe
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+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/tests/utils/src/com/android/systemui/statusbar/NotificationShadeWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationShadeWindowControllerKosmos.kt
new file mode 100644
index 0000000..e8ca3b8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationShadeWindowControllerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+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/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt
new file mode 100644
index 0000000..c337ac2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification
+
+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/tests/utils/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.kt
new file mode 100644
index 0000000..c3db34b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification
+
+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/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreKosmos.kt
new file mode 100644
index 0000000..1f45fbb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection
+
+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/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt
new file mode 100644
index 0000000..358d251
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.coordinator
+
+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/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProviderKosmos.kt
new file mode 100644
index 0000000..a5c9561
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProviderKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.provider
+
+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/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/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLoggerKosmos.kt
new file mode 100644
index 0000000..31cfc97
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+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/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackKosmos.kt
new file mode 100644
index 0000000..475d7fa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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 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/policy/KeyguardStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
new file mode 100644
index 0000000..0e909c4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.policy
+
+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/tests/utils/src/com/android/systemui/wmshell/BubblesManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wmshell/BubblesManagerKosmos.kt
new file mode 100644
index 0000000..1d05d62
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wmshell/BubblesManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.wmshell
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.bubblesManager by Kosmos.Fixture { mock<BubblesManager>() }
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..3164e08 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -211,6 +211,7 @@
"com_android_wm_shell_flags_lib",
"com.android.server.utils_aconfig-java",
"service-jobscheduler-deviceidle.flags-aconfig-java",
+ "backup_flags_lib",
"policy_flags_lib",
],
javac_shard_size: 50,
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/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/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/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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 609a703..46f5523 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4336,11 +4336,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);
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/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index f0c8437..9c60fbb 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 TvAdManagerService.
+ // surface is not used in TvInteractiveAppManagerService.
surface.release();
}
Binder.restoreCallingIdentity(identity);
@@ -1106,67 +1106,6 @@
}
}
- @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..bcb4559 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -1726,7 +1726,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/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/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/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/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/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();