Merge changes Ifc4e989b,I51bda79d into main
* changes:
Tutorial correctly handling META key event
Adding logger for touchpad and keyboard tutorials
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index df01aa8..f483691 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3450,7 +3450,7 @@
}
public static interface VirtualDeviceManager.ActivityListener {
- method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onActivityLaunchBlocked(int, @NonNull android.content.ComponentName, int, @Nullable android.content.IntentSender);
+ method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onActivityLaunchBlocked(int, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle, @Nullable android.content.IntentSender);
method public void onDisplayEmpty(int);
method @Deprecated public void onTopActivityChanged(int, @NonNull android.content.ComponentName);
method public default void onTopActivityChanged(int, @NonNull android.content.ComponentName, int);
@@ -3530,7 +3530,7 @@
field @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
field @FlaggedApi("android.companion.virtual.flags.dynamic_policy") public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3
field public static final int POLICY_TYPE_AUDIO = 1; // 0x1
- field @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public static final int POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR = 6; // 0x6
+ field @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public static final int POLICY_TYPE_BLOCKED_ACTIVITY = 6; // 0x6
field @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final int POLICY_TYPE_CAMERA = 5; // 0x5
field @FlaggedApi("android.companion.virtual.flags.cross_device_clipboard") public static final int POLICY_TYPE_CLIPBOARD = 4; // 0x4
field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
@@ -4598,6 +4598,8 @@
method @NonNull public String getPackageName();
method @NonNull public android.content.pm.VersionedPackage getVersionRolledBackFrom();
method @NonNull public android.content.pm.VersionedPackage getVersionRolledBackTo();
+ method @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") public boolean isApex();
+ method @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") public boolean isApkInApex();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.rollback.PackageRollbackInfo> CREATOR;
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index db979a5..e99ba84 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1578,6 +1578,22 @@
public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
/**
+ * {@link #extras} key: {@link Icon} of an image used as an overlay Icon on
+ * {@link Notification#mLargeIcon} for {@link EnRouteStyle} notifications.
+ * This extra is an {@code Icon}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static final String EXTRA_ENROUTE_OVERLAY_ICON = "android.enrouteOverlayIcon";
+
+ /**
+ * {@link #extras} key: text used as a sub-text for the largeIcon of
+ * {@link EnRouteStyle} notification. This extra is a {@code CharSequence}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static final String EXTRA_ENROUTE_LARGE_ICON_SUBTEXT = "android.enrouteLargeIconSubText";
+ /**
* {@link #extras} key: whether the notification should be colorized as
* supplied to {@link Builder#setColorized(boolean)}.
*/
@@ -3039,6 +3055,10 @@
visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON, Icon.class));
}
+ if (Flags.apiRichOngoing()) {
+ visitIconUri(visitor, extras.getParcelable(EXTRA_ENROUTE_OVERLAY_ICON, Icon.class));
+ }
+
if (mBubbleMetadata != null) {
visitIconUri(visitor, mBubbleMetadata.getIcon());
}
@@ -10979,6 +10999,144 @@
}
/**
+ * TODO(b/360827871): Make EnRouteStyle public.
+ * A style used to represent the progress of a real-world journey with a known destination.
+ * For example:
+ * <ul>
+ * <li>Delivery tracking</li>
+ * <li>Ride progress</li>
+ * <li>Flight tracking</li>
+ * </ul>
+ *
+ * The exact fields from {@link Notification} that are shown with this style may vary by
+ * the surface where this update appears, but the following fields are recommended:
+ * <ul>
+ * <li>{@link Notification.Builder#setContentTitle}</li>
+ * <li>{@link Notification.Builder#setContentText}</li>
+ * <li>{@link Notification.Builder#setSubText}</li>
+ * <li>{@link Notification.Builder#setLargeIcon}</li>
+ * <li>{@link Notification.Builder#setProgress}</li>
+ * <li>{@link Notification.Builder#setWhen} - This should be the future time of the next,
+ * final, or most important stop on this journey.</li>
+ * </ul>
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static class EnRouteStyle extends Notification.Style {
+
+ @Nullable
+ private Icon mOverlayIcon = null;
+
+ @Nullable
+ private CharSequence mLargeIconSubText = null;
+
+ public EnRouteStyle() {
+ }
+
+ /**
+ * Returns the overlay icon to be displayed on {@link Notification#mLargeIcon}.
+ * @see EnRouteStyle#setOverlayIcon
+ */
+ @Nullable
+ public Icon getOverlayIcon() {
+ return mOverlayIcon;
+ }
+
+ /**
+ * Optional icon to be displayed on {@link Notification#mLargeIcon}.
+ *
+ * This image will be cropped to a circle and will obscure
+ * a semicircle of the right side of the large icon.
+ */
+ @NonNull
+ public EnRouteStyle setOverlayIcon(@Nullable Icon overlayIcon) {
+ mOverlayIcon = overlayIcon;
+ return this;
+ }
+
+ /**
+ * Returns the sub-text for {@link Notification#mLargeIcon}.
+ * @see EnRouteStyle#setLargeIconSubText
+ */
+ @Nullable
+ public CharSequence getLargeIconSubText() {
+ return mLargeIconSubText;
+ }
+
+ /**
+ * Optional text which generally related to
+ * the {@link Notification.Builder#setLargeIcon} or {@link #setOverlayIcon} or both.
+ */
+ @NonNull
+ public EnRouteStyle setLargeIconSubText(@Nullable CharSequence largeIconSubText) {
+ mLargeIconSubText = stripStyling(largeIconSubText);
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean areNotificationsVisiblyDifferent(Style other) {
+ if (other == null || getClass() != other.getClass()) {
+ return true;
+ }
+
+ final EnRouteStyle enRouteStyle = (EnRouteStyle) other;
+ return !Objects.equals(mOverlayIcon, enRouteStyle.mOverlayIcon)
+ || !Objects.equals(mLargeIconSubText, enRouteStyle.mLargeIconSubText);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void addExtras(Bundle extras) {
+ super.addExtras(extras);
+ extras.putParcelable(EXTRA_ENROUTE_OVERLAY_ICON, mOverlayIcon);
+ extras.putCharSequence(EXTRA_ENROUTE_LARGE_ICON_SUBTEXT, mLargeIconSubText);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ protected void restoreFromExtras(Bundle extras) {
+ super.restoreFromExtras(extras);
+ mOverlayIcon = extras.getParcelable(EXTRA_ENROUTE_OVERLAY_ICON, Icon.class);
+ mLargeIconSubText = extras.getCharSequence(EXTRA_ENROUTE_LARGE_ICON_SUBTEXT);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void purgeResources() {
+ super.purgeResources();
+ if (mOverlayIcon != null) {
+ mOverlayIcon.convertToAshmem();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void reduceImageSizes(Context context) {
+ super.reduceImageSizes(context);
+ if (mOverlayIcon != null) {
+ final Resources resources = context.getResources();
+ final boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
+
+ int rightIconSize = resources.getDimensionPixelSize(isLowRam
+ ? R.dimen.notification_right_icon_size_low_ram
+ : R.dimen.notification_right_icon_size);
+ mOverlayIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
+ }
+ }
+ }
+
+ /**
* Notification style for custom views that are decorated by the system
*
* <p>Instead of providing a notification that is completely custom, a developer can set this
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 326d7ce..789c99d 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -753,9 +753,14 @@
/**
* Sets whether or not notifications posted to this channel can interrupt the user in
- * {@link android.app.NotificationManager.Policy#INTERRUPTION_FILTER_PRIORITY} mode.
+ * {@link android.app.NotificationManager#INTERRUPTION_FILTER_PRIORITY} mode.
*
- * Only modifiable by the system and notification ranker.
+ * <p>Apps with Do Not Disturb policy access (see
+ * {@link NotificationManager#isNotificationPolicyAccessGranted()}) can set up their own
+ * channels this way, but only if the channel hasn't been updated by the user since its
+ * creation.
+ *
+ * <p>Otherwise, this value is only modifiable by the system and the notification ranker.
*/
public void setBypassDnd(boolean bypassDnd) {
this.mBypassDnd = bypassDnd;
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 3d1a785..83f9ff7 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1440,10 +1440,36 @@
* Informs the notification manager that the state of an {@link AutomaticZenRule} has changed.
* Use this method to put the system into Do Not Disturb mode or request that it exits Do Not
* Disturb mode. The calling app must own the provided {@link android.app.AutomaticZenRule}.
- * <p>
- * This method can be used in conjunction with or as a replacement to
- * {@link android.service.notification.ConditionProviderService#notifyCondition(Condition)}.
- * </p>
+ *
+ * <p>This method can be used in conjunction with or as a replacement to
+ * {@link android.service.notification.ConditionProviderService#notifyCondition(Condition)}.
+ *
+ * <p>The condition change may be ignored if the user has activated or deactivated the rule
+ * manually -- the user can "override" the rule <em>this time</em>, with the rule resuming its
+ * normal operation for the next cycle. When this has happened, the supplied condition will be
+ * applied only once the automatic state is in agreement with the user-provided state. For
+ * example, assume that the {@link AutomaticZenRule} corresponds to a "Driving Mode" with
+ * automatic driving detection.
+ *
+ * <ol>
+ * <li>App detects driving and notifies the system that the rule should be active, calling
+ * this method with a {@link Condition} with {@link Condition#STATE_TRUE}).
+ * <li>User deactivates ("snoozes") the rule for some reason. This overrides the
+ * app-provided condition state.
+ * <li>App is still detecting driving, so again calls with {@link Condition#STATE_TRUE}.
+ * This is ignored by the system, as the user override prevails.
+ * <li>Some time later, the app detects that driving stopped, so the rule should be
+ * inactive, and calls with {@link Condition#STATE_FALSE}). This doesn't change the actual
+ * rule state (it was already inactive due to the user's override), but clears the override.
+ * <li>Some time later, the app detects that driving has started again, and notifies that
+ * the rule should be active (calling with {@link Condition#STATE_TRUE} again). The rule is
+ * activated.
+ * </ol>
+ *
+ * <p>Note that the behavior at step #3 is different if the app also specifies
+ * {@link Condition#SOURCE_USER_ACTION} as the {@link Condition#source} -- rule state updates
+ * coming from user actions are not ignored.
+ *
* @param id The id of the rule whose state should change
* @param condition The new state of this rule
*/
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index bf21549..7801201 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -27,7 +27,6 @@
*
* <p>App function is a specific piece of functionality that an app offers to the system. These
* functionalities can be integrated into various system features.
- *
*/
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
@SystemService(Context.APP_FUNCTION_SERVICE)
diff --git a/core/java/android/app/appfunctions/IAppFunctionManager.aidl b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
index 018bc75..14944f0 100644
--- a/core/java/android/app/appfunctions/IAppFunctionManager.aidl
+++ b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
@@ -16,9 +16,22 @@
package android.app.appfunctions;
+import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.IExecuteAppFunctionCallback;
+
/**
* Interface between an app and the server implementation service (AppFunctionManagerService).
* @hide
*/
oneway interface IAppFunctionManager {
+ /**
+ * Executes an app function provided by {@link AppFunctionService} through the system.
+ *
+ * @param request the request to execute an app function.
+ * @param callback the callback to report the result.
+ */
+ void executeAppFunction(
+ in ExecuteAppFunctionAidlRequest request,
+ in IExecuteAppFunctionCallback callback
+ );
}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
index 564fb02..7c674f9 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.content.IntentSender;
+import android.os.UserHandle;
/**
* Interface to listen for activity changes in a virtual device.
@@ -48,9 +49,9 @@
*
* @param displayId The display ID on which the activity tried to launch.
* @param componentName The component name of the blocked activity.
- * @param userId The user ID associated with the blocked activity.
+ * @param user The user associated with the blocked activity.
* @param intentSender The original sender of the intent.
*/
- void onActivityLaunchBlocked(int displayId, in ComponentName componentName, int userId,
+ void onActivityLaunchBlocked(int displayId, in ComponentName componentName, in UserHandle user,
in IntentSender intentSender);
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 19eb497..9636cd4 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -19,7 +19,7 @@
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
-import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_BLOCKED_ACTIVITY;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
@@ -65,6 +65,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.view.WindowManager;
@@ -136,14 +137,14 @@
@Override
public void onActivityLaunchBlocked(int displayId, ComponentName componentName,
- @UserIdInt int userId, IntentSender intentSender) {
+ UserHandle user, IntentSender intentSender) {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mActivityListenersLock) {
for (int i = 0; i < mActivityListeners.size(); i++) {
mActivityListeners.valueAt(i)
.onActivityLaunchBlocked(
- displayId, componentName, userId, intentSender);
+ displayId, componentName, user, intentSender);
}
}
} finally {
@@ -292,7 +293,7 @@
case POLICY_TYPE_RECENTS:
case POLICY_TYPE_CLIPBOARD:
case POLICY_TYPE_ACTIVITY:
- case POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR:
+ case POLICY_TYPE_BLOCKED_ACTIVITY:
break;
default:
throw new IllegalArgumentException("Device policy " + policyType
@@ -595,10 +596,10 @@
}
public void onActivityLaunchBlocked(int displayId, ComponentName componentName,
- @UserIdInt int userId, IntentSender intentSender) {
+ UserHandle user, IntentSender intentSender) {
mExecutor.execute(() ->
mActivityListener.onActivityLaunchBlocked(
- displayId, componentName, userId, intentSender));
+ displayId, componentName, user, intentSender));
}
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index d07fb25..c2300e0 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -67,6 +67,7 @@
import android.os.Binder;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Log;
import android.view.Display;
@@ -1263,7 +1264,7 @@
*
* @param displayId The display ID on which the activity tried to launch.
* @param componentName The component name of the blocked activity.
- * @param userId The user ID associated with the blocked activity.
+ * @param user The user associated with the blocked activity.
* @param intentSender The original sender of the intent. May be {@code null} if the sender
* expects an activity result to be reported. In that case
* {@link android.app.Activity#RESULT_CANCELED} was already reported back because the
@@ -1275,7 +1276,7 @@
*/
@FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
default void onActivityLaunchBlocked(int displayId, @NonNull ComponentName componentName,
- @UserIdInt int userId, @Nullable IntentSender intentSender) {}
+ @NonNull UserHandle user, @Nullable IntentSender intentSender) {}
}
/**
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index c1fc51d..03b72bd 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -160,7 +160,7 @@
*/
@IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO,
POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CAMERA,
- POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR})
+ POLICY_TYPE_BLOCKED_ACTIVITY})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface PolicyType {}
@@ -172,7 +172,7 @@
* @hide
*/
@IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY,
- POLICY_TYPE_CLIPBOARD, POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR})
+ POLICY_TYPE_CLIPBOARD, POLICY_TYPE_BLOCKED_ACTIVITY})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface DynamicPolicyType {}
@@ -242,7 +242,7 @@
* @see VirtualDeviceManager.VirtualDevice#removeActivityPolicyExemption
*/
// TODO(b/333443509): Update the documentation of custom policy and link to the new policy
- // POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR
+ // POLICY_TYPE_BLOCKED_ACTIVITY
@FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
public static final int POLICY_TYPE_ACTIVITY = 3;
@@ -292,7 +292,7 @@
*/
// TODO(b/333443509): Link to POLICY_TYPE_ACTIVITY
@FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
- public static final int POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR = 6;
+ public static final int POLICY_TYPE_BLOCKED_ACTIVITY = 6;
private final int mLockState;
@NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
@@ -1206,7 +1206,7 @@
}
if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) {
- mDevicePolicies.delete(POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR);
+ mDevicePolicies.delete(POLICY_TYPE_BLOCKED_ACTIVITY);
}
if ((mAudioPlaybackSessionId != AUDIO_SESSION_ID_GENERATE
diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java
index 8df7c37..21122a1 100644
--- a/core/java/android/content/rollback/PackageRollbackInfo.java
+++ b/core/java/android/content/rollback/PackageRollbackInfo.java
@@ -16,6 +16,7 @@
package android.content.rollback;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.pm.PackageManager;
@@ -145,7 +146,10 @@
mPendingRestores.remove(ri);
}
- /** @hide */
+ /**
+ * True if the package is an apex else false.
+ */
+ @FlaggedApi(android.crashrecovery.flags.Flags.FLAG_ENABLE_CRASHRECOVERY)
public boolean isApex() {
return mIsApex;
}
@@ -154,7 +158,11 @@
public @PackageManager.RollbackDataPolicy int getRollbackDataPolicy() {
return mRollbackDataPolicy;
}
- /** @hide */
+
+ /**
+ * True if the package is apk-in-apex else false.
+ */
+ @FlaggedApi(android.crashrecovery.flags.Flags.FLAG_ENABLE_CRASHRECOVERY)
public boolean isApkInApex() {
return mIsApkInApex;
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 1e7f70b..48d2785 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -80,6 +80,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -354,7 +355,14 @@
mCameraId = cameraId;
if (Flags.singleThreadExecutor()) {
mDeviceCallback = new ClientStateCallback(executor, callback);
- mDeviceExecutor = Executors.newSingleThreadExecutor();
+ mDeviceExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread thread = Executors.defaultThreadFactory().newThread(r);
+ thread.setName("CameraDeviceExecutor");
+ return thread;
+ }
+ });
} else {
mDeviceCallback = callback;
mDeviceExecutor = executor;
@@ -2272,6 +2280,19 @@
// TODO: Handle CameraCharacteristics access from CaptureResult correctly.
result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
+ Map<String, CameraCharacteristics> physicalIdToChars = getPhysicalIdToChars();
+ for (PhysicalCaptureResultInfo oneResultInfo : physicalResults) {
+ String physicalId = oneResultInfo.getCameraId();
+ CameraMetadataNative physicalResult = oneResultInfo.getCameraMetadata();
+ CameraCharacteristics ch = physicalIdToChars.get(physicalId);
+ if (ch != null) {
+ physicalResult.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
+ ch.get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
+ } else {
+ Log.e(TAG, "Unable to find characteristics for physical camera "
+ + physicalId);
+ }
+ }
final CaptureCallbackHolder holder =
CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 623196b..492b825 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -71,7 +71,7 @@
POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS,
})
@Retention(RetentionPolicy.SOURCE)
- public static @interface PowerComponent {
+ public @interface PowerComponent {
}
public static final int POWER_COMPONENT_ANY = -1;
@@ -132,6 +132,16 @@
}
/**
+ * An integer that is either one of @PowerComponent constants or a custom component ID
+ * between FIRST_CUSTOM_POWER_COMPONENT_ID and LAST_CUSTOM_POWER_COMPONENT_ID.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PowerComponentId {
+ }
+
+ /**
* Identifiers of models used for power estimation.
*
* @hide
@@ -178,8 +188,8 @@
public @interface ProcessState {
}
+ public static final int PROCESS_STATE_ANY = -1;
public static final int PROCESS_STATE_UNSPECIFIED = 0;
- public static final int PROCESS_STATE_ANY = PROCESS_STATE_UNSPECIFIED;
public static final int PROCESS_STATE_FOREGROUND = 1;
public static final int PROCESS_STATE_BACKGROUND = 2;
public static final int PROCESS_STATE_FOREGROUND_SERVICE = 3;
@@ -216,16 +226,14 @@
};
static final int COLUMN_INDEX_BATTERY_CONSUMER_TYPE = 0;
- static final int COLUMN_COUNT = 1;
-
/**
* Identifiers of consumed power aggregations per SCREEN state.
*
* @hide
*/
@IntDef(prefix = {"SCREEN_STATE_"}, value = {
- SCREEN_STATE_UNSPECIFIED,
SCREEN_STATE_ANY,
+ SCREEN_STATE_UNSPECIFIED,
SCREEN_STATE_ON,
SCREEN_STATE_OTHER,
})
@@ -233,8 +241,10 @@
public @interface ScreenState {
}
+ static final int COLUMN_COUNT = 1;
+
+ public static final int SCREEN_STATE_ANY = 0;
public static final int SCREEN_STATE_UNSPECIFIED = 0;
- public static final int SCREEN_STATE_ANY = SCREEN_STATE_UNSPECIFIED;
public static final int SCREEN_STATE_ON = 1;
public static final int SCREEN_STATE_OTHER = 2; // Off, doze etc
@@ -255,8 +265,8 @@
* @hide
*/
@IntDef(prefix = {"POWER_STATE_"}, value = {
- POWER_STATE_UNSPECIFIED,
POWER_STATE_ANY,
+ POWER_STATE_UNSPECIFIED,
POWER_STATE_BATTERY,
POWER_STATE_OTHER,
})
@@ -264,8 +274,8 @@
public @interface PowerState {
}
+ public static final int POWER_STATE_ANY = 0;
public static final int POWER_STATE_UNSPECIFIED = 0;
- public static final int POWER_STATE_ANY = POWER_STATE_UNSPECIFIED;
public static final int POWER_STATE_BATTERY = 1;
public static final int POWER_STATE_OTHER = 2; // Plugged in, or on wireless charger, etc.
@@ -284,18 +294,18 @@
* Identifies power attribution dimensions that a caller is interested in.
*/
public static final class Dimensions {
- public final @PowerComponent int powerComponent;
+ public final @PowerComponentId int powerComponentId;
public final @ProcessState int processState;
public final @ScreenState int screenState;
public final @PowerState int powerState;
- public Dimensions(@PowerComponent int powerComponent, @ProcessState int processState) {
- this(powerComponent, processState, SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED);
+ public Dimensions(@PowerComponentId int powerComponentId, @ProcessState int processState) {
+ this(powerComponentId, processState, SCREEN_STATE_ANY, POWER_STATE_ANY);
}
- public Dimensions(@PowerComponent int powerComponent, int processState,
+ public Dimensions(@PowerComponentId int powerComponentId, int processState,
@ScreenState int screenState, @PowerState int powerState) {
- this.powerComponent = powerComponent;
+ this.powerComponentId = powerComponentId;
this.processState = processState;
this.screenState = screenState;
this.powerState = powerState;
@@ -305,11 +315,16 @@
public String toString() {
boolean dimensionSpecified = false;
StringBuilder sb = new StringBuilder();
- if (powerComponent != POWER_COMPONENT_ANY) {
- sb.append("powerComponent=").append(sPowerComponentNames[powerComponent]);
+ if (powerComponentId != POWER_COMPONENT_ANY) {
+ sb.append("powerComponent=");
+ if (powerComponentId < POWER_COMPONENT_COUNT) {
+ sb.append(sPowerComponentNames[powerComponentId]);
+ } else {
+ sb.append("CUSTOM/").append(powerComponentId);
+ }
dimensionSpecified = true;
}
- if (processState != PROCESS_STATE_UNSPECIFIED) {
+ if (processState != PROCESS_STATE_ANY) {
if (dimensionSpecified) {
sb.append(", ");
}
@@ -353,7 +368,7 @@
* in the same BatteryUsageStats.
*/
public static final class Key {
- public final @PowerComponent int powerComponent;
+ public final @PowerComponentId int powerComponentId;
public final @ProcessState int processState;
public final @ScreenState int screenState;
public final @PowerState int powerState;
@@ -362,10 +377,10 @@
final int mPowerColumnIndex;
final int mDurationColumnIndex;
- private Key(@PowerComponent int powerComponent, @ProcessState int processState,
+ private Key(@PowerComponentId int powerComponentId, @ProcessState int processState,
@ScreenState int screenState, @PowerState int powerState, int powerModelColumnIndex,
int powerColumnIndex, int durationColumnIndex) {
- this.powerComponent = powerComponent;
+ this.powerComponentId = powerComponentId;
this.processState = processState;
this.screenState = screenState;
this.powerState = powerState;
@@ -379,9 +394,13 @@
* Returns true if this key should be included in an enumeration parameterized with
* the supplied dimensions.
*/
- boolean matches(@PowerComponent int powerComponent, @ProcessState int processState,
+ boolean matches(@PowerComponentId int powerComponent, @ProcessState int processState,
@ScreenState int screenState, @PowerState int powerState) {
- if (powerComponent != POWER_COMPONENT_ANY && this.powerComponent != powerComponent) {
+ if (powerComponent != POWER_COMPONENT_ANY && this.powerComponentId != powerComponent) {
+ return false;
+ }
+ if (this.processState == PROCESS_STATE_UNSPECIFIED) {
+ // PROCESS_STATE_UNSPECIFIED is used for storing a precomputed total
return false;
}
if (processState != PROCESS_STATE_ANY && this.processState != processState) {
@@ -401,7 +420,7 @@
public boolean equals(Object o) {
// Skipping null and class check for performance
final Key key = (Key) o;
- return powerComponent == key.powerComponent
+ return powerComponentId == key.powerComponentId
&& processState == key.processState
&& screenState == key.screenState
&& powerState == key.powerState;
@@ -409,7 +428,7 @@
@Override
public int hashCode() {
- int result = powerComponent;
+ int result = powerComponentId;
result = 31 * result + processState;
result = 31 * result + screenState;
result = 31 * result + powerState;
@@ -419,11 +438,15 @@
/**
* Returns a string suitable for use in dumpsys.
*/
- public static String toString(@PowerComponent int powerComponent,
+ public static String toString(@PowerComponentId int powerComponent,
@ProcessState int processState, @ScreenState int screenState,
@PowerState int powerState) {
StringBuilder sb = new StringBuilder();
- sb.append(powerComponentIdToString(powerComponent));
+ if (powerComponent < POWER_COMPONENT_COUNT) {
+ sb.append(powerComponentIdToString(powerComponent));
+ } else {
+ sb.append("CUSTOM/").append(powerComponent);
+ }
if (processState != PROCESS_STATE_UNSPECIFIED) {
sb.append(':');
sb.append(processStateToString(processState));
@@ -441,7 +464,7 @@
@Override
public String toString() {
- return toString(powerComponent, processState, screenState, powerState);
+ return toString(powerComponentId, processState, screenState, powerState);
}
}
@@ -459,6 +482,13 @@
}
/**
+ * Returns the name of the specified power component, e.g. "CPU", "GPU" etc.
+ */
+ public String getPowerComponentName(@PowerComponentId int powerComponent) {
+ return mData.layout.getPowerComponentName(powerComponent);
+ }
+
+ /**
* Total power consumed by this consumer, in mAh.
*/
public double getConsumedPower() {
@@ -480,10 +510,18 @@
}
/**
+ * Returns indexes of all included power components.
+ */
+ @PowerComponentId
+ public int[] getPowerComponentIds() {
+ return mData.layout.powerComponentIds;
+ }
+
+ /**
* Returns keys for various power values attributed to the specified component
* held by this BatteryUsageStats object.
*/
- public Key[] getKeys(@PowerComponent int componentId) {
+ public Key[] getKeys(@PowerComponentId int componentId) {
return mData.layout.getKeys(componentId);
}
@@ -491,7 +529,7 @@
* Returns the key for the power attributed to the specified component,
* for all values of other dimensions such as process state.
*/
- public Key getKey(@PowerComponent int componentId) {
+ public Key getKey(@PowerComponentId int componentId) {
return mData.layout.getKey(componentId, PROCESS_STATE_UNSPECIFIED, SCREEN_STATE_UNSPECIFIED,
POWER_STATE_UNSPECIFIED);
}
@@ -499,7 +537,7 @@
/**
* Returns the key for the power attributed to the specified component and process state.
*/
- public Key getKey(@PowerComponent int componentId, @ProcessState int processState) {
+ public Key getKey(@PowerComponentId int componentId, @ProcessState int processState) {
return mData.layout.getKey(componentId, processState, SCREEN_STATE_UNSPECIFIED,
POWER_STATE_UNSPECIFIED);
}
@@ -511,9 +549,9 @@
* {@link BatteryConsumer#POWER_COMPONENT_CPU}.
* @return Amount of consumed power in mAh.
*/
- public double getConsumedPower(@PowerComponent int componentId) {
- return mPowerComponents.getConsumedPower(componentId, PROCESS_STATE_UNSPECIFIED,
- SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED);
+ public double getConsumedPower(@PowerComponentId int componentId) {
+ return mPowerComponents.getConsumedPower(componentId, PROCESS_STATE_ANY,
+ SCREEN_STATE_ANY, POWER_STATE_ANY);
}
/**
@@ -533,7 +571,7 @@
* @param componentId The ID of the power component, e.g.
* {@link BatteryConsumer#POWER_COMPONENT_CPU}.
*/
- public @PowerModel int getPowerModel(@BatteryConsumer.PowerComponent int componentId) {
+ public @PowerModel int getPowerModel(@PowerComponentId int componentId) {
return mPowerComponents.getPowerModel(
mData.layout.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED,
SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED));
@@ -554,9 +592,12 @@
*
* @param componentId The ID of the custom power component.
* @return Amount of consumed power in mAh.
+ *
+ * @deprecated Use getConsumedPower instead
*/
+ @Deprecated
public double getConsumedPowerForCustomComponent(int componentId) {
- return mPowerComponents.getConsumedPowerForCustomComponent(componentId);
+ return getConsumedPower(componentId);
}
public int getCustomPowerComponentCount() {
@@ -580,8 +621,9 @@
* {@link UidBatteryConsumer#POWER_COMPONENT_CPU}.
* @return Amount of time in milliseconds.
*/
- public long getUsageDurationMillis(@PowerComponent int componentId) {
- return mPowerComponents.getUsageDurationMillis(getKey(componentId));
+ public long getUsageDurationMillis(@PowerComponentId int componentId) {
+ return mPowerComponents.getUsageDurationMillis(componentId, PROCESS_STATE_ANY,
+ SCREEN_STATE_ANY, POWER_STATE_ANY);
}
/**
@@ -598,17 +640,6 @@
}
/**
- * Returns the amount of usage time attributed to the specified custom component
- * since BatteryStats reset.
- *
- * @param componentId The ID of the custom power component.
- * @return Amount of time in milliseconds.
- */
- public long getUsageDurationForCustomComponentMillis(int componentId) {
- return mPowerComponents.getUsageDurationForCustomComponentMillis(componentId);
- }
-
- /**
* Returns the name of the specified component. Intended for logging and debugging.
*/
public static String powerComponentIdToString(@BatteryConsumer.PowerComponent int componentId) {
@@ -826,13 +857,12 @@
public final boolean processStateDataIncluded;
public final boolean screenStateDataIncluded;
public final boolean powerStateDataIncluded;
+ public final @PowerComponentId int[] powerComponentIds;
public final Key[] keys;
public final SparseArray<Key> indexedKeys;
public final int totalConsumedPowerColumnIndex;
- public final int firstCustomConsumedPowerColumn;
- public final int firstCustomUsageDurationColumn;
public final int columnCount;
- private Key[][] mPerComponentKeys;
+ private SparseArray<Key[]> mPerComponentKeys;
private BatteryConsumerDataLayout(int firstColumn, String[] customPowerComponentNames,
boolean powerModelsIncluded, boolean includeProcessStateData,
@@ -844,6 +874,15 @@
this.screenStateDataIncluded = includeScreenState;
this.powerStateDataIncluded = includePowerState;
+ powerComponentIds = new int[POWER_COMPONENT_COUNT + customPowerComponentCount];
+ int id = 0;
+ for (int componentId = 0; componentId < POWER_COMPONENT_COUNT; componentId++) {
+ powerComponentIds[id++] = componentId;
+ }
+ for (int i = 0; i < customPowerComponentCount; i++) {
+ powerComponentIds[id++] = FIRST_CUSTOM_POWER_COMPONENT_ID + i;
+ }
+
int columnIndex = firstColumn;
totalConsumedPowerColumnIndex = columnIndex++;
@@ -857,35 +896,41 @@
if (!includePowerState && powerState != POWER_STATE_UNSPECIFIED) {
continue;
}
- for (int componentId = 0; componentId < POWER_COMPONENT_COUNT; componentId++) {
+ for (int i = 0; i < powerComponentIds.length; i++) {
columnIndex = addKeys(keyList, powerModelsIncluded, includeProcessStateData,
- componentId, screenState, powerState, columnIndex);
+ powerComponentIds[i], screenState, powerState, columnIndex);
}
}
}
- firstCustomConsumedPowerColumn = columnIndex;
- columnIndex += customPowerComponentCount;
-
- firstCustomUsageDurationColumn = columnIndex;
- columnIndex += customPowerComponentCount;
-
columnCount = columnIndex;
keys = keyList.toArray(KEY_ARRAY);
indexedKeys = new SparseArray<>(keys.length);
for (int i = 0; i < keys.length; i++) {
Key key = keys[i];
- int index = keyIndex(key.powerComponent, key.processState, key.screenState,
- key.powerState);
- indexedKeys.put(index, key);
+ indexedKeys.put(keyIndex(key.powerComponentId, key.processState, key.screenState,
+ key.powerState), key);
+ }
+ }
+
+ public String getPowerComponentName(@PowerComponentId int powerComponentId) {
+ if (powerComponentId < POWER_COMPONENT_COUNT) {
+ return BatteryConsumer.powerComponentIdToString(powerComponentId);
+ } else if (powerComponentId >= FIRST_CUSTOM_POWER_COMPONENT_ID && powerComponentId
+ < FIRST_CUSTOM_POWER_COMPONENT_ID + customPowerComponentCount) {
+ return customPowerComponentNames[powerComponentId
+ - FIRST_CUSTOM_POWER_COMPONENT_ID];
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported power component " + powerComponentId);
}
}
private int addKeys(List<Key> keys, boolean powerModelsIncluded,
- boolean includeProcessStateData, int componentId,
+ boolean includeProcessStateData, @PowerComponentId int componentId,
int screenState, int powerState, int columnIndex) {
- keys.add(new Key(componentId, PROCESS_STATE_ANY, screenState, powerState,
+ keys.add(new Key(componentId, PROCESS_STATE_UNSPECIFIED, screenState, powerState,
powerModelsIncluded
? columnIndex++
: POWER_MODEL_NOT_INCLUDED, // power model
@@ -896,14 +941,13 @@
// Declare Keys for all process states, if needed
if (includeProcessStateData) {
boolean isSupported = SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE
- .binarySearch(componentId) >= 0;
+ .binarySearch(componentId) >= 0
+ || componentId >= FIRST_CUSTOM_POWER_COMPONENT_ID;
if (isSupported) {
- for (int processState = 0; processState < PROCESS_STATE_COUNT;
- processState++) {
- if (processState == PROCESS_STATE_UNSPECIFIED) {
+ for (int processState = 0; processState < PROCESS_STATE_COUNT; processState++) {
+ if (processState == PROCESS_STATE_UNSPECIFIED) { // Already added above
continue;
}
-
keys.add(new Key(componentId, processState, screenState, powerState,
powerModelsIncluded
? columnIndex++
@@ -917,12 +961,12 @@
return columnIndex;
}
- Key getKey(@PowerComponent int componentId, @ProcessState int processState,
+ Key getKey(@PowerComponentId int componentId, @ProcessState int processState,
@ScreenState int screenState, @PowerState int powerState) {
return indexedKeys.get(keyIndex(componentId, processState, screenState, powerState));
}
- Key getKeyOrThrow(@PowerComponent int componentId, @ProcessState int processState,
+ Key getKeyOrThrow(@PowerComponentId int componentId, @ProcessState int processState,
@ScreenState int screenState, @PowerState int powerState) {
Key key = getKey(componentId, processState, screenState, powerState);
if (key == null) {
@@ -933,21 +977,21 @@
return key;
}
- public Key[] getKeys(@PowerComponent int componentId) {
+ public Key[] getKeys(@PowerComponentId int componentId) {
synchronized (this) {
if (mPerComponentKeys == null) {
- mPerComponentKeys = new Key[BatteryConsumer.POWER_COMPONENT_COUNT][];
+ mPerComponentKeys = new SparseArray<>(powerComponentIds.length);
}
- Key[] componentKeys = mPerComponentKeys[componentId];
+ Key[] componentKeys = mPerComponentKeys.get(componentId);
if (componentKeys == null) {
ArrayList<Key> out = new ArrayList<>();
for (Key key : keys) {
- if (key.powerComponent == componentId) {
+ if (key.powerComponentId == componentId) {
out.add(key);
}
}
componentKeys = out.toArray(new Key[out.size()]);
- mPerComponentKeys[componentId] = componentKeys;
+ mPerComponentKeys.put(componentId, componentKeys);
}
return componentKeys;
}
@@ -991,18 +1035,18 @@
}
@Nullable
- public Key[] getKeys(@PowerComponent int componentId) {
+ public Key[] getKeys(@PowerComponentId int componentId) {
return mData.layout.getKeys(componentId);
}
@Nullable
- public Key getKey(@PowerComponent int componentId, @ProcessState int processState) {
+ public Key getKey(@PowerComponentId int componentId, @ProcessState int processState) {
return mData.layout.getKey(componentId, processState, SCREEN_STATE_UNSPECIFIED,
POWER_STATE_UNSPECIFIED);
}
@Nullable
- public Key getKey(@PowerComponent int componentId, @ProcessState int processState,
+ public Key getKey(@PowerComponentId int componentId, @ProcessState int processState,
@ScreenState int screenState, @PowerState int powerState) {
return mData.layout.getKey(componentId, processState, screenState, powerState);
}
@@ -1015,7 +1059,7 @@
* @param componentPower Amount of consumed power in mAh.
*/
@NonNull
- public T setConsumedPower(@PowerComponent int componentId, double componentPower) {
+ public T setConsumedPower(@PowerComponentId int componentId, double componentPower) {
return setConsumedPower(componentId, componentPower, POWER_MODEL_POWER_PROFILE);
}
@@ -1028,7 +1072,7 @@
*/
@SuppressWarnings("unchecked")
@NonNull
- public T setConsumedPower(@PowerComponent int componentId, double componentPower,
+ public T setConsumedPower(@PowerComponentId int componentId, double componentPower,
@PowerModel int powerModel) {
mPowerComponentsBuilder.setConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
componentPower, powerModel);
@@ -1037,7 +1081,7 @@
@SuppressWarnings("unchecked")
@NonNull
- public T addConsumedPower(@PowerComponent int componentId, double componentPower,
+ public T addConsumedPower(@PowerComponentId int componentId, double componentPower,
@PowerModel int powerModel) {
mPowerComponentsBuilder.addConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
componentPower, powerModel);
@@ -1059,26 +1103,6 @@
}
/**
- * Sets the amount of drain attributed to the specified custom drain type.
- *
- * @param componentId The ID of the custom power component.
- * @param componentPower Amount of consumed power in mAh.
- */
- @SuppressWarnings("unchecked")
- @NonNull
- public T setConsumedPowerForCustomComponent(int componentId, double componentPower) {
- mPowerComponentsBuilder.setConsumedPowerForCustomComponent(componentId, componentPower);
- return (T) this;
- }
-
- @SuppressWarnings("unchecked")
- @NonNull
- public T addConsumedPowerForCustomComponent(int componentId, double componentPower) {
- mPowerComponentsBuilder.addConsumedPowerForCustomComponent(componentId, componentPower);
- return (T) this;
- }
-
- /**
* Sets the amount of time used by the specified component, e.g. CPU, WiFi etc.
*
* @param componentId The ID of the power component, e.g.
@@ -1087,7 +1111,7 @@
*/
@SuppressWarnings("unchecked")
@NonNull
- public T setUsageDurationMillis(@UidBatteryConsumer.PowerComponent int componentId,
+ public T setUsageDurationMillis(@PowerComponentId int componentId,
long componentUsageTimeMillis) {
mPowerComponentsBuilder
.setUsageDurationMillis(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
@@ -1095,7 +1119,6 @@
return (T) this;
}
-
@SuppressWarnings("unchecked")
@NonNull
public T setUsageDurationMillis(Key key, long componentUsageTimeMillis) {
@@ -1104,21 +1127,6 @@
}
/**
- * Sets the amount of time used by the specified custom component.
- *
- * @param componentId The ID of the custom power component.
- * @param componentUsageTimeMillis Amount of time in microseconds.
- */
- @SuppressWarnings("unchecked")
- @NonNull
- public T setUsageDurationForCustomComponentMillis(int componentId,
- long componentUsageTimeMillis) {
- mPowerComponentsBuilder.setUsageDurationForCustomComponentMillis(componentId,
- componentUsageTimeMillis);
- return (T) this;
- }
-
- /**
* Returns the total power accumulated by this builder so far. It may change
* by the time the {@code build()} method is called.
*/
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index e039953..1fef602 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -95,7 +95,6 @@
static final String XML_TAG_USER = "user";
static final String XML_TAG_POWER_COMPONENTS = "power_components";
static final String XML_TAG_COMPONENT = "component";
- static final String XML_TAG_CUSTOM_COMPONENT = "custom_component";
static final String XML_ATTR_ID = "id";
static final String XML_ATTR_UID = "uid";
static final String XML_ATTR_USER_ID = "user_id";
@@ -610,96 +609,109 @@
final BatteryConsumer appsConsumer = getAggregateBatteryConsumer(
AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
- for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
- componentId++) {
- final double devicePowerMah = deviceConsumer.getConsumedPower(componentId);
- final double appsPowerMah = appsConsumer.getConsumedPower(componentId);
+ for (@BatteryConsumer.PowerComponentId int powerComponent :
+ mBatteryConsumerDataLayout.powerComponentIds) {
+ final double devicePowerMah = deviceConsumer.getConsumedPower(powerComponent);
+ final double appsPowerMah = appsConsumer.getConsumedPower(powerComponent);
if (devicePowerMah == 0 && appsPowerMah == 0) {
continue;
}
- printPowerComponent(pw, prefix, BatteryConsumer.powerComponentIdToString(componentId),
- devicePowerMah, appsPowerMah,
- BatteryConsumer.POWER_MODEL_UNDEFINED,
- deviceConsumer.getUsageDurationMillis(componentId));
+ printPowerComponent(pw, prefix,
+ mBatteryConsumerDataLayout.getPowerComponentName(powerComponent),
+ devicePowerMah, appsPowerMah, BatteryConsumer.POWER_MODEL_UNDEFINED,
+ deviceConsumer.getUsageDurationMillis(powerComponent));
}
- for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
- componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
- + mCustomPowerComponentNames.length;
- componentId++) {
- final double devicePowerMah =
- deviceConsumer.getConsumedPowerForCustomComponent(componentId);
- final double appsPowerMah =
- appsConsumer.getConsumedPowerForCustomComponent(componentId);
- if (devicePowerMah == 0 && appsPowerMah == 0) {
- continue;
+ String prefixPlus = prefix + " ";
+ if (mIncludesPowerStateData && !mIncludesScreenStateData) {
+ for (@BatteryConsumer.PowerState int powerState = 0;
+ powerState < BatteryConsumer.POWER_STATE_COUNT;
+ powerState++) {
+ if (powerState != BatteryConsumer.POWER_STATE_UNSPECIFIED) {
+ dumpPowerComponents(pw, BatteryConsumer.SCREEN_STATE_ANY, powerState,
+ prefixPlus);
+ }
}
-
- printPowerComponent(pw, prefix, deviceConsumer.getCustomPowerComponentName(componentId),
- devicePowerMah, appsPowerMah,
- BatteryConsumer.POWER_MODEL_UNDEFINED,
- deviceConsumer.getUsageDurationForCustomComponentMillis(componentId));
- }
-
- if (mIncludesScreenStateData || mIncludesPowerStateData) {
- String prefixPlus = prefix + " ";
- StringBuilder stateLabel = new StringBuilder();
- int screenState = BatteryConsumer.SCREEN_STATE_UNSPECIFIED;
- int powerState = BatteryConsumer.POWER_STATE_UNSPECIFIED;
- for (BatteryConsumer.Key key : mBatteryConsumerDataLayout.keys) {
- if (key.processState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
- continue;
+ } else if (!mIncludesPowerStateData && mIncludesScreenStateData) {
+ for (@BatteryConsumer.ScreenState int screenState = 0;
+ screenState < BatteryConsumer.SCREEN_STATE_COUNT;
+ screenState++) {
+ if (screenState != BatteryConsumer.SCREEN_STATE_UNSPECIFIED) {
+ dumpPowerComponents(pw, screenState, BatteryConsumer.POWER_STATE_ANY,
+ prefixPlus);
}
-
- if (key.screenState == BatteryConsumer.SCREEN_STATE_UNSPECIFIED
- && key.powerState == BatteryConsumer.POWER_STATE_UNSPECIFIED) {
- // Totals already printed earlier in this method
- continue;
- }
-
- final double devicePowerMah = deviceConsumer.getConsumedPower(key);
- final double appsPowerMah = appsConsumer.getConsumedPower(key);
- if (devicePowerMah == 0 && appsPowerMah == 0) {
- continue;
- }
-
- if (key.screenState != screenState || key.powerState != powerState) {
- screenState = key.screenState;
- powerState = key.powerState;
-
- boolean empty = true;
- stateLabel.setLength(0);
- stateLabel.append(" (");
- if (powerState != BatteryConsumer.POWER_STATE_UNSPECIFIED) {
- stateLabel.append(BatteryConsumer.powerStateToString(powerState));
- empty = false;
- }
- if (screenState != BatteryConsumer.SCREEN_STATE_UNSPECIFIED) {
- if (!empty) {
- stateLabel.append(", ");
+ }
+ } else if (mIncludesPowerStateData && mIncludesScreenStateData) {
+ for (@BatteryConsumer.PowerState int powerState = 0;
+ powerState < BatteryConsumer.POWER_STATE_COUNT;
+ powerState++) {
+ if (powerState != BatteryConsumer.POWER_STATE_UNSPECIFIED) {
+ for (@BatteryConsumer.ScreenState int screenState = 0;
+ screenState < BatteryConsumer.SCREEN_STATE_COUNT; screenState++) {
+ if (screenState != BatteryConsumer.SCREEN_STATE_UNSPECIFIED) {
+ dumpPowerComponents(pw, screenState, powerState, prefixPlus);
}
- stateLabel.append("screen ").append(
- BatteryConsumer.screenStateToString(screenState));
- empty = false;
- }
- if (!empty) {
- stateLabel.append(")");
- pw.println(stateLabel);
}
}
- String label = BatteryConsumer.powerComponentIdToString(key.powerComponent);
- printPowerComponent(pw, prefixPlus, label, devicePowerMah, appsPowerMah,
- mIncludesPowerModels ? deviceConsumer.getPowerModel(key)
- : BatteryConsumer.POWER_MODEL_UNDEFINED,
- deviceConsumer.getUsageDurationMillis(key));
}
}
+
dumpSortedBatteryConsumers(pw, prefix, getUidBatteryConsumers());
dumpSortedBatteryConsumers(pw, prefix, getUserBatteryConsumers());
pw.println();
}
+ private void dumpPowerComponents(PrintWriter pw,
+ @BatteryConsumer.ScreenState int screenState,
+ @BatteryConsumer.PowerState int powerState, String prefix) {
+ final BatteryConsumer deviceConsumer = getAggregateBatteryConsumer(
+ AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+ final BatteryConsumer appsConsumer = getAggregateBatteryConsumer(
+ AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
+
+ boolean labelPrinted = false;
+ for (@BatteryConsumer.PowerComponentId int powerComponent :
+ mBatteryConsumerDataLayout.powerComponentIds) {
+ BatteryConsumer.Dimensions dimensions = new BatteryConsumer.Dimensions(
+ powerComponent, BatteryConsumer.PROCESS_STATE_ANY, screenState, powerState);
+ final double devicePowerMah = deviceConsumer.getConsumedPower(dimensions);
+ final double appsPowerMah = appsConsumer.getConsumedPower(dimensions);
+ if (devicePowerMah == 0 && appsPowerMah == 0) {
+ continue;
+ }
+
+ if (!labelPrinted) {
+ boolean empty = true;
+ StringBuilder stateLabel = new StringBuilder();
+ stateLabel.append(" (");
+ if (powerState != BatteryConsumer.POWER_STATE_ANY) {
+ stateLabel.append(BatteryConsumer.powerStateToString(powerState));
+ empty = false;
+ }
+ if (screenState != BatteryConsumer.SCREEN_STATE_ANY) {
+ if (!empty) {
+ stateLabel.append(", ");
+ }
+ stateLabel.append("screen ")
+ .append(BatteryConsumer.screenStateToString(screenState));
+ empty = false;
+ }
+ if (!empty) {
+ stateLabel.append(")");
+ pw.println(stateLabel);
+ labelPrinted = true;
+ }
+ }
+ printPowerComponent(pw, prefix,
+ mBatteryConsumerDataLayout.getPowerComponentName(powerComponent),
+ devicePowerMah, appsPowerMah,
+ mIncludesPowerModels ? deviceConsumer.getPowerModel(powerComponent)
+ : BatteryConsumer.POWER_MODEL_UNDEFINED,
+ deviceConsumer.getUsageDurationMillis(dimensions));
+ }
+ }
+
private void printPowerComponent(PrintWriter pw, String prefix, String label,
double devicePowerMah, double appsPowerMah, int powerModel, long durationMs) {
StringBuilder sb = new StringBuilder();
@@ -951,12 +963,14 @@
/**
* Returns true if this Builder is configured to hold data for the specified
- * custom power component ID.
+ * power component index.
*/
- public boolean isSupportedCustomPowerComponent(int componentId) {
- return componentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ public boolean isSupportedPowerComponent(
+ @BatteryConsumer.PowerComponentId int componentId) {
+ return componentId < BatteryConsumer.POWER_COMPONENT_COUNT
+ || (componentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
&& componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
- + mBatteryConsumerDataLayout.customPowerComponentCount;
+ + mBatteryConsumerDataLayout.customPowerComponentCount);
}
/**
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index d0ed297..a12606b 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -86,7 +86,7 @@
private final long mFromTimestamp;
private final long mToTimestamp;
private final double mMinConsumedPowerThreshold;
- private final @BatteryConsumer.PowerComponent int[] mPowerComponents;
+ private final @BatteryConsumer.PowerComponentId int[] mPowerComponents;
private BatteryUsageStatsQuery(@NonNull Builder builder) {
mFlags = builder.mFlags;
@@ -139,6 +139,7 @@
* Returns the power components that should be estimated or null if all power components
* are being requested.
*/
+ @BatteryConsumer.PowerComponentId
public int[] getPowerComponents() {
return mPowerComponents;
}
@@ -228,7 +229,7 @@
private long mFromTimestamp;
private long mToTimestamp;
private double mMinConsumedPowerThreshold = 0;
- private @BatteryConsumer.PowerComponent int[] mPowerComponents;
+ private @BatteryConsumer.PowerComponentId int[] mPowerComponents;
/**
* Builds a read-only BatteryUsageStatsQuery object.
@@ -294,7 +295,7 @@
* is all power components.
*/
public Builder includePowerComponents(
- @BatteryConsumer.PowerComponent int[] powerComponents) {
+ @BatteryConsumer.PowerComponentId int[] powerComponents) {
mPowerComponents = powerComponents;
return this;
}
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index f22e1ea..9200db3 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -60,14 +60,14 @@
* Total power consumed by this consumer, aggregated over the specified dimensions, in mAh.
*/
public double getConsumedPower(@NonNull BatteryConsumer.Dimensions dimensions) {
- return getConsumedPower(dimensions.powerComponent, dimensions.processState,
+ return getConsumedPower(dimensions.powerComponentId, dimensions.processState,
dimensions.screenState, dimensions.powerState);
}
/**
* Total power consumed by this consumer, aggregated over the specified dimensions, in mAh.
*/
- public double getConsumedPower(@BatteryConsumer.PowerComponent int powerComponent,
+ public double getConsumedPower(@BatteryConsumer.PowerComponentId int powerComponent,
@BatteryConsumer.ProcessState int processState,
@BatteryConsumer.ScreenState int screenState,
@BatteryConsumer.PowerState int powerState) {
@@ -76,85 +76,64 @@
return mData.getDouble(mData.layout.totalConsumedPowerColumnIndex);
}
- if (powerComponent != POWER_COMPONENT_ANY
- && ((mData.layout.screenStateDataIncluded && screenState != SCREEN_STATE_ANY)
- || (mData.layout.powerStateDataIncluded && powerState != POWER_STATE_ANY))) {
- BatteryConsumer.Key key = mData.layout.getKey(powerComponent,
- processState, screenState, powerState);
- if (key != null) {
- return mData.getDouble(key.mPowerColumnIndex);
- }
+ if (!mData.layout.processStateDataIncluded && !(processState == PROCESS_STATE_UNSPECIFIED
+ || processState == PROCESS_STATE_ANY)) {
return 0;
}
- if (mData.layout.processStateDataIncluded || mData.layout.screenStateDataIncluded
- || mData.layout.powerStateDataIncluded) {
- double total = 0;
- for (BatteryConsumer.Key key : mData.layout.keys) {
- if (key.processState != PROCESS_STATE_UNSPECIFIED
- && key.matches(powerComponent, processState, screenState, powerState)) {
- total += mData.getDouble(key.mPowerColumnIndex);
- }
- }
- if (total != 0) {
- return total;
- }
- }
-
- BatteryConsumer.Key key = mData.layout.getKey(powerComponent, processState,
- SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED);
- if (key != null) {
+ BatteryConsumer.Key key = mData.layout.getKey(powerComponent,
+ mData.layout.processStateDataIncluded && processState != PROCESS_STATE_ANY
+ ? processState : PROCESS_STATE_UNSPECIFIED,
+ mData.layout.screenStateDataIncluded && screenState != SCREEN_STATE_ANY
+ ? screenState : SCREEN_STATE_UNSPECIFIED,
+ mData.layout.powerStateDataIncluded && powerState != POWER_STATE_ANY
+ ? powerState : POWER_STATE_UNSPECIFIED);
+ if (key != null && mData.hasValue(key.mPowerColumnIndex)) {
return mData.getDouble(key.mPowerColumnIndex);
- } else {
- return 0;
}
+
+ double total = 0;
+ for (BatteryConsumer.Key k : mData.layout.keys) {
+ if (k.matches(powerComponent, processState, screenState, powerState)) {
+ total += mData.getDouble(k.mPowerColumnIndex);
+ }
+ }
+ return total;
}
/**
* Total usage duration by this consumer, aggregated over the specified dimensions, in ms.
*/
public long getUsageDurationMillis(@NonNull BatteryConsumer.Dimensions dimensions) {
- return getUsageDurationMillis(dimensions.powerComponent, dimensions.processState,
+ return getUsageDurationMillis(dimensions.powerComponentId, dimensions.processState,
dimensions.screenState, dimensions.powerState);
}
/**
* Total usage duration by this consumer, aggregated over the specified dimensions, in ms.
*/
- public long getUsageDurationMillis(@BatteryConsumer.PowerComponent int powerComponent,
+ public long getUsageDurationMillis(@BatteryConsumer.PowerComponentId int powerComponent,
@BatteryConsumer.ProcessState int processState,
@BatteryConsumer.ScreenState int screenState,
@BatteryConsumer.PowerState int powerState) {
- if ((mData.layout.screenStateDataIncluded && screenState != SCREEN_STATE_ANY)
- || (mData.layout.powerStateDataIncluded && powerState != POWER_STATE_ANY)) {
- BatteryConsumer.Key key = mData.layout.getKey(powerComponent,
- processState, screenState, powerState);
- if (key != null) {
- return mData.getLong(key.mDurationColumnIndex);
- }
- return 0;
- }
-
- if (mData.layout.screenStateDataIncluded || mData.layout.powerStateDataIncluded) {
- long total = 0;
- for (BatteryConsumer.Key key : mData.layout.keys) {
- if (key.processState != PROCESS_STATE_UNSPECIFIED
- && key.matches(powerComponent, processState, screenState, powerState)) {
- total += mData.getLong(key.mDurationColumnIndex);
- }
- }
- if (total != 0) {
- return total;
- }
- }
-
- BatteryConsumer.Key key = mData.layout.getKey(powerComponent, processState,
- SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED);
- if (key != null) {
+ BatteryConsumer.Key key = mData.layout.getKey(powerComponent,
+ mData.layout.processStateDataIncluded && processState != PROCESS_STATE_ANY
+ ? processState : PROCESS_STATE_UNSPECIFIED,
+ mData.layout.screenStateDataIncluded && screenState != SCREEN_STATE_ANY
+ ? screenState : SCREEN_STATE_UNSPECIFIED,
+ mData.layout.powerStateDataIncluded && powerState != POWER_STATE_ANY
+ ? powerState : POWER_STATE_UNSPECIFIED);
+ if (key != null && mData.hasValue(key.mDurationColumnIndex)) {
return mData.getLong(key.mDurationColumnIndex);
- } else {
- return 0;
}
+
+ long total = 0;
+ for (BatteryConsumer.Key k : mData.layout.keys) {
+ if (k.matches(powerComponent, processState, screenState, powerState)) {
+ total += mData.getLong(k.mDurationColumnIndex);
+ }
+ }
+ return total;
}
/**
@@ -168,39 +147,12 @@
if (mData.hasValue(key.mPowerColumnIndex)) {
return mData.getDouble(key.mPowerColumnIndex);
}
- return getConsumedPower(key.powerComponent, key.processState, key.screenState,
+ return getConsumedPower(key.powerComponentId, key.processState, key.screenState,
key.powerState);
}
- /**
- * Returns the amount of drain attributed to the specified custom drain type.
- *
- * @param componentId The ID of the custom power component.
- * @return Amount of consumed power in mAh.
- */
- public double getConsumedPowerForCustomComponent(int componentId) {
- final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
- if (index >= 0 && index < mData.layout.customPowerComponentCount) {
- return mData.getDouble(mData.layout.firstCustomConsumedPowerColumn + index);
- } else {
- throw new IllegalArgumentException(
- "Unsupported custom power component ID: " + componentId);
- }
- }
-
public String getCustomPowerComponentName(int componentId) {
- final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
- if (index >= 0 && index < mData.layout.customPowerComponentCount) {
- try {
- return mData.layout.customPowerComponentNames[index];
- } catch (ArrayIndexOutOfBoundsException e) {
- throw new IllegalArgumentException(
- "Unsupported custom power component ID: " + componentId);
- }
- } else {
- throw new IllegalArgumentException(
- "Unsupported custom power component ID: " + componentId);
- }
+ return mData.layout.getPowerComponentName(componentId);
}
@BatteryConsumer.PowerModel
@@ -224,63 +176,26 @@
return mData.getLong(key.mDurationColumnIndex);
}
- return getUsageDurationMillis(key.powerComponent, key.processState, key.screenState,
+ return getUsageDurationMillis(key.powerComponentId, key.processState, key.screenState,
key.powerState);
}
- /**
- * Returns the amount of usage time attributed to the specified custom component.
- *
- * @param componentId The ID of the custom power component.
- * @return Amount of time in milliseconds.
- */
- public long getUsageDurationForCustomComponentMillis(int componentId) {
- final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
- if (index >= 0 && index < mData.layout.customPowerComponentCount) {
- return mData.getLong(mData.layout.firstCustomUsageDurationColumn + index);
- } else {
- throw new IllegalArgumentException(
- "Unsupported custom power component ID: " + componentId);
- }
- }
-
void dump(PrintWriter pw, @BatteryConsumer.ScreenState int screenState,
@BatteryConsumer.PowerState int powerState, boolean skipEmptyComponents) {
StringBuilder sb = new StringBuilder();
- for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
- componentId++) {
- dump(sb, componentId, PROCESS_STATE_ANY, screenState, powerState, skipEmptyComponents);
+ for (@BatteryConsumer.PowerComponentId int id : mData.layout.powerComponentIds) {
+ dump(sb, id, PROCESS_STATE_ANY, screenState, powerState, skipEmptyComponents);
if (mData.layout.processStateDataIncluded) {
for (int processState = 0; processState < BatteryConsumer.PROCESS_STATE_COUNT;
processState++) {
if (processState == PROCESS_STATE_UNSPECIFIED) {
continue;
}
- dump(sb, componentId, processState, screenState, powerState,
- skipEmptyComponents);
+ dump(sb, id, processState, screenState, powerState, skipEmptyComponents);
}
}
}
- // TODO(b/352835319): take into account screen and power states
- if (screenState == SCREEN_STATE_ANY && powerState == POWER_STATE_ANY) {
- final int customComponentCount = mData.layout.customPowerComponentCount;
- for (int customComponentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
- customComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
- + customComponentCount;
- customComponentId++) {
- final double customComponentPower =
- getConsumedPowerForCustomComponent(customComponentId);
- if (skipEmptyComponents && customComponentPower == 0) {
- continue;
- }
- sb.append(getCustomPowerComponentName(customComponentId));
- sb.append("=");
- sb.append(BatteryStats.formatCharge(customComponentPower));
- sb.append(" ");
- }
- }
-
// Remove trailing spaces
while (!sb.isEmpty() && Character.isWhitespace(sb.charAt(sb.length() - 1))) {
sb.setLength(sb.length() - 1);
@@ -289,25 +204,25 @@
pw.println(sb);
}
- private void dump(StringBuilder sb, @BatteryConsumer.PowerComponent int powerComponent,
+ private void dump(StringBuilder sb, @BatteryConsumer.PowerComponentId int powerComponent,
@BatteryConsumer.ProcessState int processState,
@BatteryConsumer.ScreenState int screenState,
@BatteryConsumer.PowerState int powerState, boolean skipEmptyComponents) {
- final double componentPower = getConsumedPower(powerComponent, processState, screenState,
+ final double power = getConsumedPower(powerComponent, processState, screenState,
powerState);
final long durationMs = getUsageDurationMillis(powerComponent, processState, screenState,
powerState);
- if (skipEmptyComponents && componentPower == 0 && durationMs == 0) {
+ if (skipEmptyComponents && power == 0 && durationMs == 0) {
return;
}
- sb.append(BatteryConsumer.powerComponentIdToString(powerComponent));
- if (processState != PROCESS_STATE_UNSPECIFIED) {
+ sb.append(mData.layout.getPowerComponentName(powerComponent));
+ if (processState != PROCESS_STATE_ANY) {
sb.append(':');
sb.append(BatteryConsumer.processStateToString(processState));
}
sb.append("=");
- sb.append(BatteryStats.formatCharge(componentPower));
+ sb.append(BatteryStats.formatCharge(power));
if (durationMs != 0) {
sb.append(" (");
@@ -334,15 +249,14 @@
private boolean writeStatsProtoImpl(@Nullable ProtoOutputStream proto) {
boolean interestingData = false;
- for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
- componentId++) {
+ for (@BatteryConsumer.PowerComponentId int componentId : mData.layout.powerComponentIds) {
final BatteryConsumer.Key[] keys = mData.layout.getKeys(componentId);
for (BatteryConsumer.Key key : keys) {
final long powerDeciCoulombs = convertMahToDeciCoulombs(
- getConsumedPower(key.powerComponent, key.processState, key.screenState,
+ getConsumedPower(key.powerComponentId, key.processState, key.screenState,
key.powerState));
- final long durationMs = getUsageDurationMillis(key.powerComponent, key.processState,
- key.screenState, key.powerState);
+ final long durationMs = getUsageDurationMillis(key.powerComponentId,
+ key.processState, key.screenState, key.powerState);
if (powerDeciCoulombs == 0 && durationMs == 0) {
// No interesting data. Make sure not to even write the COMPONENT int.
@@ -356,7 +270,7 @@
return true;
}
- if (key.processState == PROCESS_STATE_ANY) {
+ if (key.processState == PROCESS_STATE_UNSPECIFIED) {
writePowerComponentUsage(proto,
BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS,
componentId, powerDeciCoulombs, durationMs);
@@ -366,27 +280,6 @@
}
}
}
- for (int idx = 0; idx < mData.layout.customPowerComponentCount; idx++) {
- final int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + idx;
- final long powerDeciCoulombs =
- convertMahToDeciCoulombs(getConsumedPowerForCustomComponent(componentId));
- final long durationMs = getUsageDurationForCustomComponentMillis(componentId);
-
- if (powerDeciCoulombs == 0 && durationMs == 0) {
- // No interesting data. Make sure not to even write the COMPONENT int.
- continue;
- }
-
- interestingData = true;
- if (proto == null) {
- // We're just asked whether there is data, not to actually write it. And there is.
- return true;
- }
-
- writePowerComponentUsage(proto,
- BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS,
- componentId, powerDeciCoulombs, durationMs);
- }
return interestingData;
}
@@ -427,8 +320,9 @@
proto.end(slicesToken);
}
- private void writePowerComponentUsage(ProtoOutputStream proto, long tag, int componentId,
- long powerDeciCoulombs, long durationMs) {
+ private void writePowerComponentUsage(ProtoOutputStream proto, long tag,
+ @BatteryConsumer.PowerComponentId int componentId, long powerDeciCoulombs,
+ long durationMs) {
final long token = proto.start(tag);
proto.write(
BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
@@ -460,7 +354,7 @@
}
serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
- serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, key.powerComponent);
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, key.powerComponentId);
if (key.processState != PROCESS_STATE_UNSPECIFIED) {
serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_PROCESS_STATE,
key.processState);
@@ -485,32 +379,11 @@
}
serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
}
-
- final int customComponentEnd = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
- + mData.layout.customPowerComponentCount;
- for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
- componentId < customComponentEnd;
- componentId++) {
- final double powerMah = getConsumedPowerForCustomComponent(componentId);
- final long durationMs = getUsageDurationForCustomComponentMillis(componentId);
- if (powerMah == 0 && durationMs == 0) {
- continue;
- }
-
- serializer.startTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT);
- serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
- if (powerMah != 0) {
- serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
- }
- if (durationMs != 0) {
- serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
- }
- serializer.endTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT);
- }
-
serializer.endTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
}
+ // No longer part of the BatteryUsageStats XML format. Preserved for compatibility
+ private static final String XML_TAG_CUSTOM_COMPONENT_COMPAT = "custom_component";
static void parseXml(TypedXmlPullParser parser, PowerComponents.Builder builder)
throws XmlPullParserException, IOException {
@@ -525,7 +398,8 @@
&& eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
switch (parser.getName()) {
- case BatteryUsageStats.XML_TAG_COMPONENT: {
+ case BatteryUsageStats.XML_TAG_COMPONENT:
+ case XML_TAG_CUSTOM_COMPONENT_COMPAT: {
int componentId = -1;
int processState = PROCESS_STATE_UNSPECIFIED;
int screenState = SCREEN_STATE_UNSPECIFIED;
@@ -564,27 +438,6 @@
builder.setUsageDurationMillis(key, durationMs);
break;
}
- case BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT: {
- int componentId = -1;
- double powerMah = 0;
- long durationMs = 0;
- for (int i = 0; i < parser.getAttributeCount(); i++) {
- switch (parser.getAttributeName(i)) {
- case BatteryUsageStats.XML_ATTR_ID:
- componentId = parser.getAttributeInt(i);
- break;
- case BatteryUsageStats.XML_ATTR_POWER:
- powerMah = parser.getAttributeDouble(i);
- break;
- case BatteryUsageStats.XML_ATTR_DURATION:
- durationMs = parser.getAttributeLong(i);
- break;
- }
- }
- builder.setConsumedPowerForCustomComponent(componentId, powerMah);
- builder.setUsageDurationForCustomComponentMillis(componentId, durationMs);
- break;
- }
}
}
eventType = parser.next();
@@ -631,36 +484,6 @@
return this;
}
- /**
- * Sets the amount of drain attributed to the specified custom drain type.
- *
- * @param componentId The ID of the custom power component.
- * @param componentPower Amount of consumed power in mAh.
- */
- @NonNull
- public Builder setConsumedPowerForCustomComponent(int componentId, double componentPower) {
- final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
- if (index < 0 || index >= mData.layout.customPowerComponentCount) {
- throw new IllegalArgumentException(
- "Unsupported custom power component ID: " + componentId);
- }
- mData.putDouble(mData.layout.firstCustomConsumedPowerColumn + index, componentPower);
- return this;
- }
-
- @NonNull
- public Builder addConsumedPowerForCustomComponent(int componentId, double componentPower) {
- final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
- if (index < 0 || index >= mData.layout.customPowerComponentCount) {
- throw new IllegalArgumentException(
- "Unsupported custom power component ID: " + componentId);
- }
- mData.putDouble(mData.layout.firstCustomConsumedPowerColumn + index,
- mData.getDouble(mData.layout.firstCustomConsumedPowerColumn + index)
- + componentPower);
- return this;
- }
-
@NonNull
public Builder setUsageDurationMillis(BatteryConsumer.Key key,
long componentUsageDurationMillis) {
@@ -668,26 +491,6 @@
return this;
}
- /**
- * Sets the amount of time used by the specified custom component.
- *
- * @param componentId The ID of the custom power component.
- * @param componentUsageDurationMillis Amount of time in milliseconds.
- */
- @NonNull
- public Builder setUsageDurationForCustomComponentMillis(int componentId,
- long componentUsageDurationMillis) {
- final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
- if (index < 0 || index >= mData.layout.customPowerComponentCount) {
- throw new IllegalArgumentException(
- "Unsupported custom power component ID: " + componentId);
- }
-
- mData.putLong(mData.layout.firstCustomUsageDurationColumn + index,
- componentUsageDurationMillis);
- return this;
- }
-
public void addPowerAndDuration(PowerComponents.Builder other) {
addPowerAndDuration(other.mData);
}
@@ -706,19 +509,23 @@
}
for (BatteryConsumer.Key key : mData.layout.keys) {
- BatteryConsumer.Key otherKey = otherData.layout.getKey(key.powerComponent,
+ BatteryConsumer.Key otherKey = otherData.layout.getKey(key.powerComponentId,
key.processState, key.screenState, key.powerState);
if (otherKey == null) {
continue;
}
-
- mData.putDouble(key.mPowerColumnIndex,
- mData.getDouble(key.mPowerColumnIndex)
- + otherData.getDouble(otherKey.mPowerColumnIndex));
- mData.putLong(key.mDurationColumnIndex,
- mData.getLong(key.mDurationColumnIndex)
- + otherData.getLong(otherKey.mDurationColumnIndex));
-
+ if (mData.hasValue(key.mPowerColumnIndex)
+ || otherData.hasValue(otherKey.mPowerColumnIndex)) {
+ mData.putDouble(key.mPowerColumnIndex,
+ mData.getDouble(key.mPowerColumnIndex)
+ + otherData.getDouble(otherKey.mPowerColumnIndex));
+ }
+ if (mData.hasValue(key.mDurationColumnIndex)
+ || otherData.hasValue(otherKey.mDurationColumnIndex)) {
+ mData.putLong(key.mDurationColumnIndex,
+ mData.getLong(key.mDurationColumnIndex)
+ + otherData.getLong(otherKey.mDurationColumnIndex));
+ }
if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
continue;
}
@@ -742,21 +549,6 @@
BatteryConsumer.POWER_MODEL_UNDEFINED);
}
}
-
- for (int i = mData.layout.customPowerComponentCount - 1; i >= 0; i--) {
- final int powerColumnIndex = mData.layout.firstCustomConsumedPowerColumn + i;
- final int otherPowerColumnIndex =
- otherData.layout.firstCustomConsumedPowerColumn + i;
- mData.putDouble(powerColumnIndex,
- mData.getDouble(powerColumnIndex) + otherData.getDouble(
- otherPowerColumnIndex));
-
- final int usageColumnIndex = mData.layout.firstCustomUsageDurationColumn + i;
- final int otherDurationColumnIndex =
- otherData.layout.firstCustomUsageDurationColumn + i;
- mData.putLong(usageColumnIndex, mData.getLong(usageColumnIndex)
- + otherData.getLong(otherDurationColumnIndex));
- }
}
/**
@@ -765,15 +557,12 @@
*/
public double getTotalPower() {
double totalPowerMah = 0;
- for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
- componentId++) {
- totalPowerMah += mData.getDouble(
- mData.layout.getKeyOrThrow(componentId, PROCESS_STATE_ANY, SCREEN_STATE_ANY,
- POWER_STATE_ANY).mPowerColumnIndex);
- }
- for (int i = 0; i < mData.layout.customPowerComponentCount; i++) {
- totalPowerMah += mData.getDouble(
- mData.layout.firstCustomConsumedPowerColumn + i);
+ for (BatteryConsumer.Key key : mData.layout.keys) {
+ if (key.processState == PROCESS_STATE_UNSPECIFIED
+ && key.screenState == SCREEN_STATE_UNSPECIFIED
+ && key.powerState == POWER_STATE_UNSPECIFIED) {
+ totalPowerMah += mData.getDouble(key.mPowerColumnIndex);
+ }
}
return totalPowerMah;
}
@@ -783,7 +572,7 @@
*/
@NonNull
public PowerComponents build() {
- for (BatteryConsumer.Key key: mData.layout.keys) {
+ for (BatteryConsumer.Key key : mData.layout.keys) {
if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) {
mData.putInt(key.mPowerModelColumnIndex,
@@ -798,9 +587,7 @@
}
}
- if (mData.getDouble(mData.layout.totalConsumedPowerColumnIndex) == 0) {
- mData.putDouble(mData.layout.totalConsumedPowerColumnIndex, getTotalPower());
- }
+ mData.putDouble(mData.layout.totalConsumedPowerColumnIndex, getTotalPower());
return new PowerComponents(this);
}
}
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 013ec5f..17d2790 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -28,9 +28,7 @@
import android.util.Log;
import android.view.WindowManager;
-import java.lang.ref.WeakReference;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
/**
@@ -54,51 +52,43 @@
// An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
// requests to the {@link DreamOverlayService}
private static class OverlayClient extends IDreamOverlayClient.Stub {
- private final WeakReference<DreamOverlayService> mService;
+ private final DreamOverlayService mService;
private boolean mShowComplications;
private ComponentName mDreamComponent;
IDreamOverlayCallback mDreamOverlayCallback;
- OverlayClient(WeakReference<DreamOverlayService> service) {
+ OverlayClient(DreamOverlayService service) {
mService = service;
}
- private void applyToDream(Consumer<DreamOverlayService> consumer) {
- final DreamOverlayService service = mService.get();
-
- if (service != null) {
- consumer.accept(service);
- }
- }
-
@Override
public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback,
String dreamComponent, boolean shouldShowComplications) throws RemoteException {
mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
mShowComplications = shouldShowComplications;
mDreamOverlayCallback = callback;
- applyToDream(dreamOverlayService -> dreamOverlayService.startDream(this, params));
+ mService.startDream(this, params);
}
@Override
public void wakeUp() {
- applyToDream(dreamOverlayService -> dreamOverlayService.wakeUp(this));
+ mService.wakeUp(this);
}
@Override
public void endDream() {
- applyToDream(dreamOverlayService -> dreamOverlayService.endDream(this));
+ mService.endDream(this);
}
@Override
public void comeToFront() {
- applyToDream(dreamOverlayService -> dreamOverlayService.comeToFront(this));
+ mService.comeToFront(this);
}
@Override
public void onWakeRequested() {
if (Flags.dreamWakeRedirect()) {
- applyToDream(DreamOverlayService::onWakeRequested);
+ mService.onWakeRequested();
}
}
@@ -171,24 +161,17 @@
});
}
- private static class DreamOverlay extends IDreamOverlay.Stub {
- private final WeakReference<DreamOverlayService> mService;
-
- DreamOverlay(DreamOverlayService service) {
- mService = new WeakReference<>(service);
- }
-
+ private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
@Override
public void getClient(IDreamOverlayClientCallback callback) {
try {
- callback.onDreamOverlayClient(new OverlayClient(mService));
+ callback.onDreamOverlayClient(
+ new OverlayClient(DreamOverlayService.this));
} catch (RemoteException e) {
Log.e(TAG, "could not send client to callback", e);
}
}
- }
-
- private final IDreamOverlay mDreamOverlay = new DreamOverlay(this);
+ };
public DreamOverlayService() {
}
@@ -212,12 +195,6 @@
}
}
- @Override
- public void onDestroy() {
- mCurrentClient = null;
- super.onDestroy();
- }
-
@Nullable
@Override
public final IBinder onBind(@NonNull Intent intent) {
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index fc6c2e8..57acc71 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -187,6 +187,13 @@
@Retention(RetentionPolicy.SOURCE)
public @interface ConfigOrigin {}
+ /**
+ * Prefix for the ids of implicit Zen rules. Implicit rules are those created automatically
+ * on behalf of apps that call {@link NotificationManager#setNotificationPolicy} or
+ * {@link NotificationManager#setInterruptionFilter}.
+ */
+ private static final String IMPLICIT_RULE_ID_PREFIX = "implicit_"; // + pkg_name
+
public static final int SOURCE_ANYONE = Policy.PRIORITY_SENDERS_ANY;
public static final int SOURCE_CONTACT = Policy.PRIORITY_SENDERS_CONTACTS;
public static final int SOURCE_STAR = Policy.PRIORITY_SENDERS_STARRED;
@@ -2492,6 +2499,16 @@
// ==== End built-in system conditions ====
+ /** Generate the rule id for the implicit rule for the specified package. */
+ public static String implicitRuleId(String forPackage) {
+ return IMPLICIT_RULE_ID_PREFIX + forPackage;
+ }
+
+ /** Returns whether the rule id corresponds to an implicit rule. */
+ public static boolean isImplicitRuleId(@NonNull String ruleId) {
+ return ruleId.startsWith(IMPLICIT_RULE_ID_PREFIX);
+ }
+
private static int[] tryParseHourAndMinute(String value) {
if (TextUtils.isEmpty(value)) return null;
final int i = value.indexOf('.');
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index d72207d..ee5bd65 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -48,6 +48,8 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.graphics.Insets;
import android.graphics.drawable.Drawable;
import android.metrics.LogMaker;
import android.os.Build;
@@ -60,6 +62,7 @@
import android.util.Log;
import android.util.Slog;
import android.view.View;
+import android.view.WindowInsets;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
@@ -117,6 +120,12 @@
}
@Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ setMiniresolverPadding();
+ }
+
+ @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mInjector = createInjector();
@@ -333,8 +342,7 @@
icon.setImageDrawable(
getAppIcon(target, launchIntent, targetUserId, pmForTargetUser));
- View buttonContainer = findViewById(R.id.button_bar_container);
- buttonContainer.setPadding(0, 0, 0, buttonContainer.getPaddingBottom());
+ setMiniresolverPadding();
((TextView) findViewById(R.id.open_cross_profile)).setText(
resolverTitle);
@@ -675,6 +683,17 @@
&& android.multiuser.Flags.enablePrivateSpaceIntentRedirection();
}
+ private void setMiniresolverPadding() {
+ Insets systemWindowInsets =
+ getWindowManager().getCurrentWindowMetrics().getWindowInsets().getInsets(
+ WindowInsets.Type.systemBars());
+
+ View buttonContainer = findViewById(R.id.button_bar_container);
+ buttonContainer.setPadding(0, 0, 0,
+ systemWindowInsets.bottom + getResources().getDimensionPixelOffset(
+ R.dimen.resolver_button_bar_spacing));
+ }
+
@VisibleForTesting
protected Injector createInjector() {
return new InjectorImpl();
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 488e06f..aafef6c 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -100,6 +100,7 @@
* to; or a custom power component ID (if the value
* is >= {@link BatteryConsumer#FIRST_CUSTOM_POWER_COMPONENT_ID}).
*/
+ @BatteryConsumer.PowerComponentId
public final int powerComponentId;
public final String name;
@@ -142,9 +143,10 @@
extras);
}
- public Descriptor(int customPowerComponentId, String name, int statsArrayLength,
- @Nullable SparseArray<String> stateLabels, int stateStatsArrayLength,
- int uidStatsArrayLength, @NonNull PersistableBundle extras) {
+ public Descriptor(@BatteryConsumer.PowerComponentId int powerComponentId, String name,
+ int statsArrayLength, @Nullable SparseArray<String> stateLabels,
+ int stateStatsArrayLength, int uidStatsArrayLength,
+ @NonNull PersistableBundle extras) {
if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) {
throw new IllegalArgumentException(
"statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH);
@@ -157,7 +159,7 @@
throw new IllegalArgumentException(
"uidStatsArrayLength is too high. Max = " + MAX_UID_STATS_ARRAY_LENGTH);
}
- this.powerComponentId = customPowerComponentId;
+ this.powerComponentId = powerComponentId;
this.name = name;
this.statsArrayLength = statsArrayLength;
this.stateLabels = stateLabels != null ? stateLabels : new SparseArray<>();
diff --git a/core/res/res/values-watch/themes_material.xml b/core/res/res/values-watch/themes_material.xml
index 674b3bc..001a0fc 100644
--- a/core/res/res/values-watch/themes_material.xml
+++ b/core/res/res/values-watch/themes_material.xml
@@ -43,11 +43,13 @@
<!-- Override behaviour to set the theme colours for dialogs, keep them the same. -->
<style name="ThemeOverlay.Material.Dialog" parent="ThemeOverlay.Material.BaseDialog">
<item name="android:windowFullscreen">true</item>
+ <item name="backgroundDimEnabled">false</item>
</style>
<!-- Force the background and floating colours to be the default colours. -->
<style name="Theme.Material.Dialog" parent="Theme.Material.BaseDialog">
<item name="android:windowFullscreen">true</item>
+ <item name="backgroundDimEnabled">false</item>
<item name="colorBackground">@color/background_material_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
@@ -56,6 +58,7 @@
<!-- Force the background and floating colours to be the default colours. -->
<style name="Theme.Material.Dialog.Alert" parent="Theme.Material.Dialog.BaseAlert">
<item name="android:windowFullscreen">true</item>
+ <item name="backgroundDimEnabled">false</item>
<item name="colorBackground">@color/background_material_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
@@ -64,6 +67,7 @@
<!-- Force the background and floating colours to be the default colours. -->
<style name="Theme.Material.Light.Dialog" parent="Theme.Material.Light.BaseDialog">
<item name="android:windowFullscreen">true</item>
+ <item name="backgroundDimEnabled">false</item>
<item name="colorBackground">@color/background_material_light</item>
<item name="colorBackgroundFloating">@color/background_floating_material_light</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_light</item>
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png
index e02c89a..c729442 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png
index e02c89a..c729442 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
new file mode 100644
index 0000000..9fdde128
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.Context
+import android.content.pm.LauncherApps
+import android.content.pm.ShortcutInfo
+import android.content.res.Resources
+import android.graphics.Color
+import android.os.Handler
+import android.os.UserManager
+import android.view.IWindowManager
+import android.view.WindowManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.internal.protolog.ProtoLog
+import com.android.internal.statusbar.IStatusBarService
+import com.android.launcher3.icons.BubbleIconFactory
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.WindowManagerShellWrapper
+import com.android.wm.shell.bubbles.properties.BubbleProperties
+import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayInsetsController
+import com.android.wm.shell.common.FloatingContentCoordinator
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.common.TaskStackListenerImpl
+import com.android.wm.shell.shared.TransactionPool
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellController
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewTransitions
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/** Test inflating bubbles with [BubbleViewInfoTask]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleViewInfoTaskTest {
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private lateinit var metadataFlagListener: Bubbles.BubbleMetadataFlagListener
+ private lateinit var iconFactory: BubbleIconFactory
+ private lateinit var bubbleController: BubbleController
+ private lateinit var mainExecutor: TestExecutor
+ private lateinit var bgExecutor: TestExecutor
+ private lateinit var bubbleStackView: BubbleStackView
+ private lateinit var bubblePositioner: BubblePositioner
+ private lateinit var expandedViewManager: BubbleExpandedViewManager
+
+ private val bubbleTaskViewFactory = BubbleTaskViewFactory {
+ BubbleTaskView(mock<TaskView>(), directExecutor())
+ }
+
+ @Before
+ fun setUp() {
+ ProtoLog.REQUIRE_PROTOLOGTOOL = false
+ metadataFlagListener = Bubbles.BubbleMetadataFlagListener {}
+ iconFactory =
+ BubbleIconFactory(
+ context,
+ 60,
+ 30,
+ Color.RED,
+ context.resources.getDimensionPixelSize(R.dimen.importance_ring_stroke_width)
+ )
+
+ mainExecutor = TestExecutor()
+ bgExecutor = TestExecutor()
+ val windowManager = context.getSystemService(WindowManager::class.java)
+ val shellInit = ShellInit(mainExecutor)
+ val shellCommandHandler = ShellCommandHandler()
+ val shellController =
+ ShellController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ mock<DisplayInsetsController>(),
+ mainExecutor
+ )
+ bubblePositioner = BubblePositioner(context, windowManager)
+ val bubbleData =
+ BubbleData(
+ context,
+ mock<BubbleLogger>(),
+ bubblePositioner,
+ BubbleEducationController(context),
+ mainExecutor,
+ bgExecutor
+ )
+
+ val surfaceSynchronizer = { obj: Runnable -> obj.run() }
+
+ val bubbleDataRepository =
+ BubbleDataRepository(
+ mock<LauncherApps>(),
+ mainExecutor,
+ bgExecutor,
+ BubblePersistentRepository(context)
+ )
+
+ bubbleController =
+ BubbleController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ shellController,
+ bubbleData,
+ surfaceSynchronizer,
+ FloatingContentCoordinator(),
+ bubbleDataRepository,
+ mock<IStatusBarService>(),
+ windowManager,
+ WindowManagerShellWrapper(mainExecutor),
+ mock<UserManager>(),
+ mock<LauncherApps>(),
+ mock<BubbleLogger>(),
+ mock<TaskStackListenerImpl>(),
+ mock<ShellTaskOrganizer>(),
+ bubblePositioner,
+ mock<DisplayController>(),
+ null,
+ null,
+ mainExecutor,
+ mock<Handler>(),
+ bgExecutor,
+ mock<TaskViewTransitions>(),
+ mock<Transitions>(),
+ SyncTransactionQueue(TransactionPool(), mainExecutor),
+ mock<IWindowManager>(),
+ mock<BubbleProperties>()
+ )
+
+ val bubbleStackViewManager = BubbleStackViewManager.fromBubbleController(bubbleController)
+ bubbleStackView =
+ BubbleStackView(
+ context,
+ bubbleStackViewManager,
+ bubblePositioner,
+ bubbleData,
+ surfaceSynchronizer,
+ FloatingContentCoordinator(),
+ bubbleController,
+ mainExecutor
+ )
+ expandedViewManager = BubbleExpandedViewManager.fromBubbleController(bubbleController)
+ }
+
+ @Test
+ fun start_runsOnExecutors() {
+ val bubble = createBubbleWithShortcut()
+ val task = createBubbleViewInfoTask(bubble)
+
+ task.start()
+
+ assertThat(bubble.isInflated).isFalse()
+ assertThat(bubble.expandedView).isNull()
+ assertThat(task.isFinished).isFalse()
+
+ bgExecutor.flushAll()
+ assertThat(bubble.isInflated).isFalse()
+ assertThat(bubble.expandedView).isNull()
+ assertThat(task.isFinished).isFalse()
+
+ mainExecutor.flushAll()
+ assertThat(bubble.isInflated).isTrue()
+ assertThat(bubble.expandedView).isNotNull()
+ assertThat(task.isFinished).isTrue()
+ }
+
+ @Test
+ fun startSync_runsImmediately() {
+ val bubble = createBubbleWithShortcut()
+ val task = createBubbleViewInfoTask(bubble)
+
+ task.startSync()
+ assertThat(bubble.isInflated).isTrue()
+ assertThat(bubble.expandedView).isNotNull()
+ assertThat(task.isFinished).isTrue()
+ }
+
+ @Test
+ fun start_calledTwice_throwsIllegalStateException() {
+ val bubble = createBubbleWithShortcut()
+ val task = createBubbleViewInfoTask(bubble)
+ task.start()
+ Assert.assertThrows(IllegalStateException::class.java) { task.start() }
+ }
+
+ @Test
+ fun startSync_calledTwice_throwsIllegalStateException() {
+ val bubble = createBubbleWithShortcut()
+ val task = createBubbleViewInfoTask(bubble)
+ task.startSync()
+ Assert.assertThrows(IllegalStateException::class.java) { task.startSync() }
+ }
+
+ @Test
+ fun start_callbackNotified() {
+ val bubble = createBubbleWithShortcut()
+ var bubbleFromCallback: Bubble? = null
+ val callback = BubbleViewInfoTask.Callback { b: Bubble? -> bubbleFromCallback = b }
+ val task = createBubbleViewInfoTask(bubble, callback)
+ task.start()
+ bgExecutor.flushAll()
+ mainExecutor.flushAll()
+ assertThat(bubbleFromCallback).isSameInstanceAs(bubble)
+ }
+
+ @Test
+ fun startSync_callbackNotified() {
+ val bubble = createBubbleWithShortcut()
+ var bubbleFromCallback: Bubble? = null
+ val callback = BubbleViewInfoTask.Callback { b: Bubble? -> bubbleFromCallback = b }
+ val task = createBubbleViewInfoTask(bubble, callback)
+ task.startSync()
+ assertThat(bubbleFromCallback).isSameInstanceAs(bubble)
+ }
+
+ @Test
+ fun cancel_beforeBackgroundWorkStarts_bubbleNotInflated() {
+ val bubble = createBubbleWithShortcut()
+ val task = createBubbleViewInfoTask(bubble)
+ task.start()
+
+ // Cancel before allowing background or main executor to run
+ task.cancel()
+ bgExecutor.flushAll()
+ mainExecutor.flushAll()
+
+ assertThat(bubble.isInflated).isFalse()
+ assertThat(bubble.expandedView).isNull()
+ assertThat(task.isFinished).isTrue()
+ }
+
+ @Test
+ fun cancel_afterBackgroundWorkBeforeMainThreadWork_bubbleNotInflated() {
+ val bubble = createBubbleWithShortcut()
+ val task = createBubbleViewInfoTask(bubble)
+ task.start()
+
+ // Cancel after background executor runs, but before main executor runs
+ bgExecutor.flushAll()
+ task.cancel()
+ mainExecutor.flushAll()
+
+ assertThat(bubble.isInflated).isFalse()
+ assertThat(bubble.expandedView).isNull()
+ assertThat(task.isFinished).isTrue()
+ }
+
+ @Test
+ fun cancel_beforeStart_bubbleNotInflated() {
+ val bubble = createBubbleWithShortcut()
+ val task = createBubbleViewInfoTask(bubble)
+ task.cancel()
+ task.start()
+ bgExecutor.flushAll()
+ mainExecutor.flushAll()
+
+ assertThat(task.isFinished).isTrue()
+ assertThat(bubble.isInflated).isFalse()
+ assertThat(bubble.expandedView).isNull()
+ }
+
+ private fun createBubbleWithShortcut(): Bubble {
+ val shortcutInfo = ShortcutInfo.Builder(context, "mockShortcutId").build()
+ return Bubble(
+ "mockKey",
+ shortcutInfo,
+ 1000,
+ Resources.ID_NULL,
+ "mockTitle",
+ 0 /* taskId */,
+ "mockLocus",
+ true /* isDismissible */,
+ mainExecutor,
+ bgExecutor,
+ metadataFlagListener
+ )
+ }
+
+ private fun createBubbleViewInfoTask(
+ bubble: Bubble,
+ callback: BubbleViewInfoTask.Callback? = null
+ ): BubbleViewInfoTask {
+ return BubbleViewInfoTask(
+ bubble,
+ context,
+ expandedViewManager,
+ bubbleTaskViewFactory,
+ bubblePositioner,
+ bubbleStackView,
+ null /* layerView */,
+ iconFactory,
+ false /* skipInflation */,
+ callback,
+ mainExecutor,
+ bgExecutor
+ )
+ }
+
+ private class TestExecutor : ShellExecutor {
+
+ private val runnables: MutableList<Runnable> = mutableListOf()
+
+ override fun execute(runnable: Runnable) {
+ runnables.add(runnable)
+ }
+
+ override fun executeDelayed(runnable: Runnable, delayMillis: Long) {
+ execute(runnable)
+ }
+
+ override fun removeCallbacks(runnable: Runnable?) {}
+
+ override fun hasCallback(runnable: Runnable?): Boolean = false
+
+ fun flushAll() {
+ while (runnables.isNotEmpty()) {
+ runnables.removeAt(0).run()
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 021d3c3..3e758bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -568,11 +568,11 @@
@Nullable BubbleBarLayerView layerView,
BubbleIconFactory iconFactory,
boolean skipInflation) {
+ ProtoLog.v(WM_SHELL_BUBBLES, "Inflate bubble key=%s", getKey());
if (Flags.bubbleViewInfoExecutors()) {
- if (mInflationTask != null && mInflationTask.getStatus() != FINISHED) {
- mInflationTask.cancel(true /* mayInterruptIfRunning */);
+ if (mInflationTask != null && !mInflationTask.isFinished()) {
+ mInflationTask.cancel();
}
- // TODO(b/353894869): switch to executors
mInflationTask = new BubbleViewInfoTask(this,
context,
expandedViewManager,
@@ -583,11 +583,12 @@
iconFactory,
skipInflation,
callback,
- mMainExecutor);
+ mMainExecutor,
+ mBgExecutor);
if (mInflateSynchronously) {
- mInflationTask.onPostExecute(mInflationTask.doInBackground());
+ mInflationTask.startSync();
} else {
- mInflationTask.execute();
+ mInflationTask.start();
}
} else {
if (mInflationTaskLegacy != null && mInflationTaskLegacy.getStatus() != FINISHED) {
@@ -625,7 +626,7 @@
if (mInflationTask == null) {
return;
}
- mInflationTask.cancel(true /* mayInterruptIfRunning */);
+ mInflationTask.cancel();
} else {
if (mInflationTaskLegacy == null) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 03a2efd..13855f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -20,6 +20,7 @@
import static com.android.wm.shell.bubbles.BadgedImageView.WHITE_SCRIM_ALPHA;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,13 +35,13 @@
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
-import android.os.AsyncTask;
import android.util.Log;
import android.util.PathParser;
import android.view.LayoutInflater;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
+import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
@@ -50,15 +51,14 @@
import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Simple task to inflate views & load necessary info to display a bubble.
*/
-// TODO(b/353894869): switch to executors
-public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask.BubbleViewInfo> {
+public class BubbleViewInfoTask {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleViewInfoTask" : TAG_BUBBLES;
-
/**
* Callback to find out when the bubble has been inflated & necessary data loaded.
*/
@@ -69,17 +69,22 @@
void onBubbleViewsReady(Bubble bubble);
}
- private Bubble mBubble;
- private WeakReference<Context> mContext;
- private WeakReference<BubbleExpandedViewManager> mExpandedViewManager;
- private WeakReference<BubbleTaskViewFactory> mTaskViewFactory;
- private WeakReference<BubblePositioner> mPositioner;
- private WeakReference<BubbleStackView> mStackView;
- private WeakReference<BubbleBarLayerView> mLayerView;
- private BubbleIconFactory mIconFactory;
- private boolean mSkipInflation;
- private Callback mCallback;
- private Executor mMainExecutor;
+ private final Bubble mBubble;
+ private final WeakReference<Context> mContext;
+ private final WeakReference<BubbleExpandedViewManager> mExpandedViewManager;
+ private final WeakReference<BubbleTaskViewFactory> mTaskViewFactory;
+ private final WeakReference<BubblePositioner> mPositioner;
+ private final WeakReference<BubbleStackView> mStackView;
+ private final WeakReference<BubbleBarLayerView> mLayerView;
+ private final BubbleIconFactory mIconFactory;
+ private final boolean mSkipInflation;
+ private final Callback mCallback;
+ private final Executor mMainExecutor;
+ private final Executor mBgExecutor;
+
+ private final AtomicBoolean mStarted = new AtomicBoolean();
+ private final AtomicBoolean mCancelled = new AtomicBoolean();
+ private final AtomicBoolean mFinished = new AtomicBoolean();
/**
* Creates a task to load information for the provided {@link Bubble}. Once all info
@@ -95,7 +100,8 @@
BubbleIconFactory factory,
boolean skipInflation,
Callback c,
- Executor mainExecutor) {
+ Executor mainExecutor,
+ Executor bgExecutor) {
mBubble = b;
mContext = new WeakReference<>(context);
mExpandedViewManager = new WeakReference<>(expandedViewManager);
@@ -107,40 +113,123 @@
mSkipInflation = skipInflation;
mCallback = c;
mMainExecutor = mainExecutor;
+ mBgExecutor = bgExecutor;
}
- @Override
- protected BubbleViewInfo doInBackground(Void... voids) {
+ /**
+ * Load bubble view info in background using {@code bgExecutor} specified in constructor.
+ * <br>
+ * Use {@link #cancel()} to stop the task.
+ *
+ * @throws IllegalStateException if the task is already started
+ */
+ public void start() {
+ verifyCanStart();
+ if (mCancelled.get()) {
+ // We got cancelled even before start was called. Exit early
+ mFinished.set(true);
+ return;
+ }
+ mBgExecutor.execute(() -> {
+ if (mCancelled.get()) {
+ // We got cancelled while background executor was busy and this was waiting
+ mFinished.set(true);
+ return;
+ }
+ BubbleViewInfo viewInfo = loadViewInfo();
+ if (mCancelled.get()) {
+ // Do not schedule anything on main executor if we got cancelled.
+ // Loading view info involves inflating views and it is possible we get cancelled
+ // during it.
+ mFinished.set(true);
+ return;
+ }
+ mMainExecutor.execute(() -> {
+ // Before updating view info check that we did not get cancelled while waiting
+ // main executor to pick up the work
+ if (!mCancelled.get()) {
+ updateViewInfo(viewInfo);
+ }
+ mFinished.set(true);
+ });
+ });
+ }
+
+ private void verifyCanStart() {
+ if (mStarted.getAndSet(true)) {
+ throw new IllegalStateException("Task already started");
+ }
+ }
+
+ /**
+ * Load bubble view info synchronously.
+ *
+ * @throws IllegalStateException if the task is already started
+ */
+ public void startSync() {
+ verifyCanStart();
+ if (mCancelled.get()) {
+ mFinished.set(true);
+ return;
+ }
+ updateViewInfo(loadViewInfo());
+ mFinished.set(true);
+ }
+
+ /**
+ * Cancel the task. Stops the task from running if called before {@link #start()} or
+ * {@link #startSync()}
+ */
+ public void cancel() {
+ mCancelled.set(true);
+ }
+
+ /**
+ * Return {@code true} when the task has completed loading the view info.
+ */
+ public boolean isFinished() {
+ return mFinished.get();
+ }
+
+ @Nullable
+ private BubbleViewInfo loadViewInfo() {
if (!verifyState()) {
// If we're in an inconsistent state, then switched modes and should just bail now.
return null;
}
+ ProtoLog.v(WM_SHELL_BUBBLES, "Task loading bubble view info key=%s", mBubble.getKey());
if (mLayerView.get() != null) {
- return BubbleViewInfo.populateForBubbleBar(mContext.get(), mExpandedViewManager.get(),
- mTaskViewFactory.get(), mPositioner.get(), mLayerView.get(), mIconFactory,
- mBubble, mSkipInflation);
+ return BubbleViewInfo.populateForBubbleBar(mContext.get(), mTaskViewFactory.get(),
+ mLayerView.get(), mIconFactory, mBubble, mSkipInflation);
} else {
- return BubbleViewInfo.populate(mContext.get(), mExpandedViewManager.get(),
- mTaskViewFactory.get(), mPositioner.get(), mStackView.get(), mIconFactory,
- mBubble, mSkipInflation);
+ return BubbleViewInfo.populate(mContext.get(), mTaskViewFactory.get(),
+ mPositioner.get(), mStackView.get(), mIconFactory, mBubble, mSkipInflation);
}
}
- @Override
- protected void onPostExecute(BubbleViewInfo viewInfo) {
- if (isCancelled() || viewInfo == null) {
+ private void updateViewInfo(@Nullable BubbleViewInfo viewInfo) {
+ if (viewInfo == null || !verifyState()) {
return;
}
+ ProtoLog.v(WM_SHELL_BUBBLES, "Task updating bubble view info key=%s", mBubble.getKey());
+ if (!mBubble.isInflated()) {
+ if (viewInfo.expandedView != null) {
+ ProtoLog.v(WM_SHELL_BUBBLES, "Task initializing expanded view key=%s",
+ mBubble.getKey());
+ viewInfo.expandedView.initialize(mExpandedViewManager.get(), mStackView.get(),
+ mPositioner.get(), false /* isOverflow */, viewInfo.taskView);
+ } else if (viewInfo.bubbleBarExpandedView != null) {
+ ProtoLog.v(WM_SHELL_BUBBLES, "Task initializing bubble bar expanded view key=%s",
+ mBubble.getKey());
+ viewInfo.bubbleBarExpandedView.initialize(mExpandedViewManager.get(),
+ mPositioner.get(), false /* isOverflow */, viewInfo.taskView);
+ }
+ }
- mMainExecutor.execute(() -> {
- if (!verifyState()) {
- return;
- }
- mBubble.setViewInfo(viewInfo);
- if (mCallback != null) {
- mCallback.onBubbleViewsReady(mBubble);
- }
- });
+ mBubble.setViewInfo(viewInfo);
+ if (mCallback != null) {
+ mCallback.onBubbleViewsReady(mBubble);
+ }
}
private boolean verifyState() {
@@ -158,6 +247,9 @@
public static class BubbleViewInfo {
// TODO(b/273312602): for foldables it might make sense to populate all of the views
+ // Only set if views where inflated as part of the task
+ @Nullable BubbleTaskView taskView;
+
// Always populated
ShortcutInfo shortcutInfo;
String appName;
@@ -177,9 +269,7 @@
@Nullable
public static BubbleViewInfo populateForBubbleBar(Context c,
- BubbleExpandedViewManager expandedViewManager,
BubbleTaskViewFactory taskViewFactory,
- BubblePositioner positioner,
BubbleBarLayerView layerView,
BubbleIconFactory iconFactory,
Bubble b,
@@ -187,12 +277,11 @@
BubbleViewInfo info = new BubbleViewInfo();
if (!skipInflation && !b.isInflated()) {
- BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory);
+ ProtoLog.v(WM_SHELL_BUBBLES, "Task inflating bubble bar views key=%s", b.getKey());
+ info.taskView = b.getOrCreateBubbleTaskView(taskViewFactory);
LayoutInflater inflater = LayoutInflater.from(c);
info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
- info.bubbleBarExpandedView.initialize(
- expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView);
}
if (!populateCommonInfo(info, c, b, iconFactory)) {
@@ -206,7 +295,6 @@
@VisibleForTesting
@Nullable
public static BubbleViewInfo populate(Context c,
- BubbleExpandedViewManager expandedViewManager,
BubbleTaskViewFactory taskViewFactory,
BubblePositioner positioner,
BubbleStackView stackView,
@@ -217,17 +305,15 @@
// View inflation: only should do this once per bubble
if (!skipInflation && !b.isInflated()) {
+ ProtoLog.v(WM_SHELL_BUBBLES, "Task inflating bubble views key=%s", b.getKey());
LayoutInflater inflater = LayoutInflater.from(c);
info.imageView = (BadgedImageView) inflater.inflate(
R.layout.bubble_view, stackView, false /* attachToRoot */);
info.imageView.initialize(positioner);
- BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory);
+ info.taskView = b.getOrCreateBubbleTaskView(taskViewFactory);
info.expandedView = (BubbleExpandedView) inflater.inflate(
R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
- info.expandedView.initialize(
- expandedViewManager, stackView, positioner, false /* isOverflow */,
- bubbleTaskView);
}
if (!populateCommonInfo(info, c, b, iconFactory)) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index 8035e91..4ac066e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -74,7 +74,6 @@
private lateinit var bubbleStackView: BubbleStackView
private lateinit var bubbleBarLayerView: BubbleBarLayerView
private lateinit var bubblePositioner: BubblePositioner
- private lateinit var expandedViewManager: BubbleExpandedViewManager
private val bubbleTaskViewFactory = BubbleTaskViewFactory {
BubbleTaskView(mock<TaskView>(), mock<Executor>())
@@ -155,7 +154,6 @@
bubbleController,
mainExecutor
)
- expandedViewManager = BubbleExpandedViewManager.fromBubbleController(bubbleController)
bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData)
}
@@ -165,7 +163,6 @@
val info =
BubbleViewInfoTask.BubbleViewInfo.populate(
context,
- expandedViewManager,
bubbleTaskViewFactory,
bubblePositioner,
bubbleStackView,
@@ -193,9 +190,7 @@
val info =
BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
context,
- expandedViewManager,
bubbleTaskViewFactory,
- bubblePositioner,
bubbleBarLayerView,
iconFactory,
bubble,
@@ -229,9 +224,7 @@
val info =
BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
context,
- expandedViewManager,
bubbleTaskViewFactory,
- bubblePositioner,
bubbleBarLayerView,
iconFactory,
bubble,
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 1c5049e..3cc0ad2 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -90,23 +90,24 @@
mDisplayManager = displayManager;
}
- /**
- * Register a listener to receive notifications about when the {@link MediaProjection} or
- * captured content changes state.
- *
- * <p>The callback must be registered before invoking
- * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback,
- * Handler)} to ensure that any notifications on the callback are not missed. The client must
- * implement {@link Callback#onStop()} and clean up any resources it is holding, e.g. the
- * {@link VirtualDisplay} and {@link Surface}.
- *
- * @param callback The callback to call.
- * @param handler The handler on which the callback should be invoked, or
- * null if the callback should be invoked on the calling thread's looper.
- * @throws NullPointerException If the given callback is null.
- * @see #unregisterCallback
- */
- public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
+ /**
+ * Register a listener to receive notifications about when the {@link MediaProjection} or captured
+ * content changes state.
+ *
+ * <p>The callback must be registered before invoking {@link #createVirtualDisplay(String, int,
+ * int, int, int, Surface, VirtualDisplay.Callback, Handler)} to ensure that any notifications on
+ * the callback are not missed. The client must implement {@link Callback#onStop()} and clean up
+ * any resources it is holding, e.g. the {@link VirtualDisplay} and {@link Surface}. This should
+ * also update any application UI indicating the MediaProjection status as MediaProjection has
+ * stopped.
+ *
+ * @param callback The callback to call.
+ * @param handler The handler on which the callback should be invoked, or null if the callback
+ * should be invoked on the calling thread's looper.
+ * @throws NullPointerException If the given callback is null.
+ * @see #unregisterCallback
+ */
+ public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
try {
final Callback c = Objects.requireNonNull(callback);
if (handler == null) {
@@ -158,74 +159,67 @@
return createVirtualDisplay(builder, callback, handler);
}
- /**
- * Creates a {@link android.hardware.display.VirtualDisplay} to capture the
- * contents of the screen.
- *
- * <p>To correctly clean up resources associated with a capture, the application must register a
- * {@link Callback} before invocation. The app must override {@link Callback#onStop()} to clean
- * up (by invoking{@link VirtualDisplay#release()}, {@link Surface#release()} and related
- * resources).
- *
- * @param name The name of the virtual display, must be non-empty.
- * @param width The width of the virtual display in pixels. Must be greater than 0.
- * @param height The height of the virtual display in pixels. Must be greater than 0.
- * @param dpi The density of the virtual display in dpi. Must be greater than 0.
- * @param surface The surface to which the content of the virtual display should be rendered,
- * or null if there is none initially.
- * @param flags A combination of virtual display flags. See {@link DisplayManager} for the
- * full list of flags. Note that
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION}
- * is always enabled. The following flags may be overridden, depending on how
- * the component with {android.Manifest.permission.MANAGE_MEDIA_PROJECTION}
- * handles the user's consent:
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR},
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC}.
- * @param callback Callback invoked when the virtual display's state changes, or null.
- * @param handler The {@link android.os.Handler} on which the callback should be invoked, or
- * null if the callback should be invoked on the calling thread's main
- * {@link android.os.Looper}.
- * @throws IllegalStateException If the target SDK is {@link
- * android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and up, and
- * if no {@link Callback} is registered.
- * @throws SecurityException In any of the following scenarios:
- * <ol>
- * <li>If attempting to create a new virtual display
- * associated with this MediaProjection instance after it has
- * been stopped by invoking {@link #stop()}.
- * <li>If attempting to create a new virtual display
- * associated with this MediaProjection instance after a
- * {@link MediaProjection.Callback#onStop()} callback has been
- * received due to the user or the system stopping the
- * MediaProjection session.
- * <li>If the target SDK is {@link
- * android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and up,
- * and if this instance has already taken a recording through
- * {@code #createVirtualDisplay}, but {@link #stop()} wasn't
- * invoked to end the recording.
- * <li>If the target SDK is {@link
- * android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and up,
- * and if {@link MediaProjectionManager#getMediaProjection}
- * was invoked more than once to get this
- * {@code MediaProjection} instance.
- * </ol>
- * In cases 2 & 3, no exception is thrown if the target SDK is
- * less than
- * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U}.
- * Instead, recording doesn't begin until the user re-grants
- * consent in the dialog.
- * @return The created {@link VirtualDisplay}, or {@code null} if no {@link VirtualDisplay}
- * could be created.
- * @see VirtualDisplay
- * @see VirtualDisplay.Callback
- */
- @SuppressWarnings("RequiresPermission")
- @Nullable
- public VirtualDisplay createVirtualDisplay(@NonNull String name,
- int width, int height, int dpi, @VirtualDisplayFlag int flags,
- @Nullable Surface surface, @Nullable VirtualDisplay.Callback callback,
- @Nullable Handler handler) {
+ /**
+ * Creates a {@link android.hardware.display.VirtualDisplay} to capture the contents of the
+ * screen.
+ *
+ * <p>To correctly clean up resources associated with a capture, the application must register a
+ * {@link Callback} before invocation. The app must override {@link Callback#onStop()} to clean up
+ * resources (by invoking{@link VirtualDisplay#release()}, {@link Surface#release()} and related
+ * resources) and to update any available UI regarding the MediaProjection status.
+ *
+ * @param name The name of the virtual display, must be non-empty.
+ * @param width The width of the virtual display in pixels. Must be greater than 0.
+ * @param height The height of the virtual display in pixels. Must be greater than 0.
+ * @param dpi The density of the virtual display in dpi. Must be greater than 0.
+ * @param surface The surface to which the content of the virtual display should be rendered, or
+ * null if there is none initially.
+ * @param flags A combination of virtual display flags. See {@link DisplayManager} for the full
+ * list of flags. Note that {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION} is always
+ * enabled. The following flags may be overridden, depending on how the component with
+ * {android.Manifest.permission.MANAGE_MEDIA_PROJECTION} handles the user's consent: {@link
+ * DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}, {@link
+ * DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}, {@link
+ * DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC}.
+ * @param callback Callback invoked when the virtual display's state changes, or null.
+ * @param handler The {@link android.os.Handler} on which the callback should be invoked, or null
+ * if the callback should be invoked on the calling thread's main {@link android.os.Looper}.
+ * @throws IllegalStateException If the target SDK is {@link
+ * android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and up, and if no {@link Callback} is
+ * registered.
+ * @throws SecurityException In any of the following scenarios:
+ * <ol>
+ * <li>If attempting to create a new virtual display associated with this MediaProjection
+ * instance after it has been stopped by invoking {@link #stop()}.
+ * <li>If attempting to create a new virtual display associated with this MediaProjection
+ * instance after a {@link MediaProjection.Callback#onStop()} callback has been received
+ * due to the user or the system stopping the MediaProjection session.
+ * <li>If the target SDK is {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and
+ * up, and if this instance has already taken a recording through {@code
+ * #createVirtualDisplay}, but {@link #stop()} wasn't invoked to end the recording.
+ * <li>If the target SDK is {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and
+ * up, and if {@link MediaProjectionManager#getMediaProjection} was invoked more than
+ * once to get this {@code MediaProjection} instance.
+ * </ol>
+ * In cases 2 & 3, no exception is thrown if the target SDK is less than {@link
+ * android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U}. Instead, recording doesn't begin until
+ * the user re-grants consent in the dialog.
+ * @return The created {@link VirtualDisplay}, or {@code null} if no {@link VirtualDisplay} could
+ * be created.
+ * @see VirtualDisplay
+ * @see VirtualDisplay.Callback
+ */
+ @SuppressWarnings("RequiresPermission")
+ @Nullable
+ public VirtualDisplay createVirtualDisplay(
+ @NonNull String name,
+ int width,
+ int height,
+ int dpi,
+ @VirtualDisplayFlag int flags,
+ @Nullable Surface surface,
+ @Nullable VirtualDisplay.Callback callback,
+ @Nullable Handler handler) {
if (shouldMediaProjectionRequireCallback()) {
if (mCallbacks.isEmpty()) {
final IllegalStateException e = new IllegalStateException(
@@ -322,14 +316,20 @@
* Called when the MediaProjection session is no longer valid.
*
* <p>Once a MediaProjection has been stopped, it's up to the application to release any
- * resources it may be holding (e.g. releasing the {@link VirtualDisplay} and
- * {@link Surface}).
+ * resources it may be holding (e.g. releasing the {@link VirtualDisplay} and {@link
+ * Surface}). If the application is displaying any UI indicating the MediaProjection state
+ * it should be updated to indicate that MediaProjection is no longer active.
*
- * <p>After this callback any call to
- * {@link MediaProjection#createVirtualDisplay} will fail, even if no such
- * {@link VirtualDisplay} was ever created for this MediaProjection session.
+ * <p>MediaProjection stopping can be a result of the system stopping the ongoing
+ * MediaProjection due to various reasons, such as another MediaProjection session starting.
+ * MediaProjection may also stop due to the user explicitly stopping ongoing MediaProjection
+ * via any available system-level UI.
+ *
+ * <p>After this callback any call to {@link MediaProjection#createVirtualDisplay} will
+ * fail, even if no such {@link VirtualDisplay} was ever created for this MediaProjection
+ * session.
*/
- public void onStop() { }
+ public void onStop() {}
/**
* Invoked immediately after capture begins or when the size of the captured region changes,
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 7a7137a..03fd2c6 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -64,7 +64,9 @@
* holding, e.g. the {@link VirtualDisplay} and {@link Surface}. The MediaProjection may
* further no longer create any new {@link VirtualDisplay}s via {@link
* MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
- * VirtualDisplay.Callback, Handler)}.
+ * VirtualDisplay.Callback, Handler)}. Note that the `onStop()` callback can be a result of
+ * the system stopping MediaProjection due to various reasons or the user stopping the
+ * MediaProjection via UI affordances in system-level UI.
* <li>Start the screen capture session for media projection by calling {@link
* MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
* android.hardware.display.VirtualDisplay.Callback, Handler)}.
diff --git a/packages/ExternalStorageProvider/TEST_MAPPING b/packages/ExternalStorageProvider/TEST_MAPPING
new file mode 100644
index 0000000..dfa0c84
--- /dev/null
+++ b/packages/ExternalStorageProvider/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "postsubmit": [
+ {
+ "name": "ExternalStorageProviderTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/packages/ExternalStorageProvider/tests/Android.bp b/packages/ExternalStorageProvider/tests/Android.bp
index 86c62ef..097bb860 100644
--- a/packages/ExternalStorageProvider/tests/Android.bp
+++ b/packages/ExternalStorageProvider/tests/Android.bp
@@ -12,6 +12,10 @@
manifest: "AndroidManifest.xml",
+ test_suites: [
+ "general-tests",
+ ],
+
srcs: [
"src/**/*.java",
],
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
index eba9c2c..9aa0bc3 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
@@ -15,16 +15,20 @@
limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:colorControlHighlight">
<item
- android:left="?android:attr/listPreferredItemPaddingStart"
- android:right="?android:attr/listPreferredItemPaddingEnd"
- android:top="2dp">
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp"
+ android:bottom="16dp">
<shape android:shape="rectangle">
<solid
- android:color="@color/settingslib_preference_bg_color" />
+ android:color="@color/settingslib_materialColorSurfaceBright" />
<corners
android:radius="@dimen/settingslib_preference_corner_radius" />
+ <padding
+ android:bottom="16dp"/>
</shape>
</item>
-</layer-list>
\ No newline at end of file
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
index 5c60f37..554cba5 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
@@ -15,19 +15,23 @@
limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:colorControlHighlight">
<item
- android:left="?android:attr/listPreferredItemPaddingStart"
- android:right="?android:attr/listPreferredItemPaddingEnd"
- android:top="2dp">
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp"
+ android:bottom="16dp">
<shape android:shape="rectangle">
<solid
- android:color="@color/settingslib_preference_bg_color" />
+ android:color="@color/settingslib_materialColorSurfaceBright" />
<corners
android:topLeftRadius="4dp"
android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
android:topRightRadius="4dp"
android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
+ <padding
+ android:bottom="16dp"/>
</shape>
</item>
-</layer-list>
\ No newline at end of file
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml
index de64efd..f4766ee 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml
@@ -15,19 +15,20 @@
limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:colorControlHighlight">
<item
- android:left="?android:attr/listPreferredItemPaddingStart"
- android:right="?android:attr/listPreferredItemPaddingEnd"
- android:top="2dp">
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp"
+ android:bottom="16dp">
<shape android:shape="rectangle">
<solid
- android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+ android:color="@color/settingslib_materialColorSurfaceContainer" />
<corners
- android:topLeftRadius="4dp"
- android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
- android:topRightRadius="4dp"
- android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
+ android:radius="@dimen/settingslib_preference_corner_radius_selected" />
+ <padding
+ android:bottom="16dp"/>
</shape>
</item>
-</layer-list>
\ No newline at end of file
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
index dd70f4f..b89a0dd 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
@@ -15,16 +15,17 @@
limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:colorControlHighlight">
<item
- android:left="?android:attr/listPreferredItemPaddingStart"
- android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:end="?android:attr/listPreferredItemPaddingEnd"
android:top="2dp">
<shape android:shape="rectangle">
<solid
- android:color="@color/settingslib_preference_bg_color" />
+ android:color="@color/settingslib_materialColorSurfaceBright" />
<corners
android:radius="4dp" />
</shape>
</item>
-</layer-list>
\ No newline at end of file
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml
index fffc6c8..40eafc2 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml
@@ -15,16 +15,17 @@
limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:colorControlHighlight">
<item
- android:left="?android:attr/listPreferredItemPaddingStart"
- android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:end="?android:attr/listPreferredItemPaddingEnd"
android:top="2dp">
<shape android:shape="rectangle">
<solid
- android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+ android:color="@color/settingslib_materialColorSurfaceContainer" />
<corners
- android:radius="4dp" />
+ android:radius="@dimen/settingslib_preference_corner_radius_selected" />
</shape>
</item>
-</layer-list>
\ No newline at end of file
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml
index f83e3b1..f4766ee 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml
@@ -15,16 +15,20 @@
limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:colorControlHighlight">
<item
- android:left="?android:attr/listPreferredItemPaddingStart"
- android:right="?android:attr/listPreferredItemPaddingEnd"
- android:top="2dp">
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp"
+ android:bottom="16dp">
<shape android:shape="rectangle">
<solid
- android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+ android:color="@color/settingslib_materialColorSurfaceContainer" />
<corners
- android:radius="@dimen/settingslib_preference_corner_radius" />
+ android:radius="@dimen/settingslib_preference_corner_radius_selected" />
+ <padding
+ android:bottom="16dp"/>
</shape>
</item>
-</layer-list>
\ No newline at end of file
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
index ab79d18..7955e44 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
@@ -15,14 +15,15 @@
limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:colorControlHighlight">
<item
- android:left="?android:attr/listPreferredItemPaddingStart"
- android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:end="?android:attr/listPreferredItemPaddingEnd"
android:top="2dp">
<shape android:shape="rectangle">
<solid
- android:color="@color/settingslib_preference_bg_color" />
+ android:color="@color/settingslib_materialColorSurfaceBright" />
<corners
android:topLeftRadius="@dimen/settingslib_preference_corner_radius"
android:bottomLeftRadius="4dp"
@@ -30,4 +31,4 @@
android:bottomRightRadius="4dp" />
</shape>
</item>
-</layer-list>
\ No newline at end of file
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml
index 112ec73..40eafc2 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml
@@ -15,19 +15,17 @@
limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:colorControlHighlight">
<item
- android:left="?android:attr/listPreferredItemPaddingStart"
- android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:end="?android:attr/listPreferredItemPaddingEnd"
android:top="2dp">
<shape android:shape="rectangle">
<solid
- android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+ android:color="@color/settingslib_materialColorSurfaceContainer" />
<corners
- android:topLeftRadius="@dimen/settingslib_preference_corner_radius"
- android:bottomLeftRadius="4dp"
- android:topRightRadius="@dimen/settingslib_preference_corner_radius"
- android:bottomRightRadius="4dp" />
+ android:radius="@dimen/settingslib_preference_corner_radius_selected" />
</shape>
</item>
-</layer-list>
\ No newline at end of file
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml
index eda7daa..7f466f6 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml
@@ -18,6 +18,5 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:baselineAligned="false"
- android:layout_marginTop="16dp">
+ android:baselineAligned="false">
</LinearLayout>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml
index d783956..193ae61 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml
@@ -17,4 +17,5 @@
<resources>
<dimen name="settingslib_preference_corner_radius">20dp</dimen>
+ <dimen name="settingslib_preference_corner_radius_selected">28dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingFooterPreference.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingFooterPreference.java
new file mode 100644
index 0000000..2099b33
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingFooterPreference.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth.devicesettings;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+/** A data class representing a footer preference. */
+public class DeviceSettingFooterPreference extends DeviceSettingPreference implements Parcelable {
+
+ private final String mFooterText;
+ private final Bundle mExtras;
+
+ DeviceSettingFooterPreference(
+ @NonNull String footerText,
+ Bundle extras) {
+ super(DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE);
+ mFooterText = footerText;
+ mExtras = extras;
+ }
+
+ /** Read a {@link DeviceSettingFooterPreference} from {@link Parcel}. */
+ @NonNull
+ public static DeviceSettingFooterPreference readFromParcel(@NonNull Parcel in) {
+ String footerText = in.readString();
+ Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+ return new DeviceSettingFooterPreference(footerText, extras);
+ }
+
+ public static final Creator<DeviceSettingFooterPreference> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public DeviceSettingFooterPreference createFromParcel(@NonNull Parcel in) {
+ in.readInt();
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public DeviceSettingFooterPreference[] newArray(int size) {
+ return new DeviceSettingFooterPreference[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mFooterText);
+ dest.writeBundle(mExtras);
+ }
+
+ /** Builder class for {@link DeviceSettingFooterPreference}. */
+ public static final class Builder {
+ private String mFooterText = "";
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * Sets the footer text of the preference.
+ *
+ * @param footerText The footer text of the preference.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public DeviceSettingFooterPreference.Builder setFooterText(@NonNull String footerText) {
+ mFooterText = footerText;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public DeviceSettingFooterPreference.Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link DeviceSettingFooterPreference} object.
+ *
+ * @return Returns the built {@link DeviceSettingFooterPreference} object.
+ */
+ @NonNull
+ public DeviceSettingFooterPreference build() {
+ return new DeviceSettingFooterPreference(
+ mFooterText, mExtras);
+ }
+ }
+
+ /**
+ * Gets the footer text of the preference.
+ *
+ * @return The footer text.
+ */
+ @NonNull
+ public String getFooterText() {
+ return mFooterText;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreference.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreference.java
index 790939a..4b67ef7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreference.java
@@ -40,6 +40,8 @@
return ActionSwitchPreference.readFromParcel(in);
case DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE:
return MultiTogglePreference.readFromParcel(in);
+ case DeviceSettingType.DEVICE_SETTING_TYPE_FOOTER:
+ return DeviceSettingFooterPreference.readFromParcel(in);
default:
return UNKNOWN;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingType.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingType.java
index ee4d90f..441e3f8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingType.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingType.java
@@ -27,6 +27,7 @@
DeviceSettingType.DEVICE_SETTING_TYPE_UNKNOWN,
DeviceSettingType.DEVICE_SETTING_TYPE_ACTION_SWITCH,
DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE,
+ DeviceSettingType.DEVICE_SETTING_TYPE_FOOTER,
},
open = true)
public @interface DeviceSettingType {
@@ -38,4 +39,7 @@
/** Device setting type is multi-toggle preference. */
int DEVICE_SETTING_TYPE_MULTI_TOGGLE = 2;
+
+ /** Device setting type is footer preference. */
+ int DEVICE_SETTING_TYPE_FOOTER = 3;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
index c8a2e9c..127275f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
@@ -31,7 +31,6 @@
data class DeviceSettingsConfig(
val mainContentItems: List<DeviceSettingItem>,
val moreSettingsItems: List<DeviceSettingItem>,
- val moreSettingsFooter: String,
val extras: Bundle = Bundle.EMPTY,
) : Parcelable {
@@ -41,7 +40,6 @@
parcel.run {
writeTypedList(mainContentItems)
writeTypedList(moreSettingsItems)
- writeString(moreSettingsFooter)
writeBundle(extras)
}
}
@@ -61,7 +59,6 @@
arrayListOf<DeviceSettingItem>().also {
readTypedList(it, DeviceSettingItem.CREATOR)
},
- moreSettingsFooter = readString()!!,
extras = readBundle((Bundle::class.java.classLoader))!!,
)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
index ce7064c..cded014 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
@@ -25,6 +25,7 @@
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingItem
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingFooterPreference
import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreference
import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
@@ -96,8 +97,7 @@
private fun DeviceSettingsConfig.toModel(): DeviceSettingConfigModel =
DeviceSettingConfigModel(
mainItems = mainContentItems.map { it.toModel() },
- moreSettingsItems = moreSettingsItems.map { it.toModel() },
- moreSettingsPageFooter = moreSettingsFooter)
+ moreSettingsItems = moreSettingsItems.map { it.toModel() })
private fun DeviceSettingItem.toModel(): DeviceSettingConfigItemModel {
return if (!TextUtils.isEmpty(preferenceKey)) {
@@ -151,6 +151,9 @@
}
},
)
+ is DeviceSettingFooterPreference -> DeviceSettingModel.FooterPreference(
+ cachedDevice = cachedDevice,
+ id = settingId, footerText = pref.footerText)
else -> DeviceSettingModel.Unknown(cachedDevice, settingId)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
index e97f76c..4062462 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
@@ -24,8 +24,6 @@
val mainItems: List<DeviceSettingConfigItemModel>,
/** Items need to be shown in device details more settings page. */
val moreSettingsItems: List<DeviceSettingConfigItemModel>,
- /** Footer text in more settings page. */
- val moreSettingsPageFooter: String
)
/** Models a device setting item in config. */
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
index 2a63217..5fd4d06 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
@@ -52,6 +52,13 @@
val updateState: (DeviceSettingStateModel.MultiTogglePreferenceState) -> Unit
) : DeviceSettingModel
+ /** Models a footer preference. */
+ data class FooterPreference(
+ override val cachedDevice: CachedBluetoothDevice,
+ @DeviceSettingId override val id: Int,
+ val footerText: String,
+ ) : DeviceSettingModel
+
/** Models an unknown preference. */
data class Unknown(
override val cachedDevice: CachedBluetoothDevice,
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 0d4ce5b..c13b261 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -117,6 +117,21 @@
.thenComparing(ZenMode::getType, PRIORITIZED_TYPE_COMPARATOR)
.thenComparing(ZenMode::getName);
+ public enum Kind {
+ /** A "normal" mode, created by apps or users via {@code addAutomaticZenRule()}. */
+ NORMAL,
+
+ /** The special, built-in "Do Not Disturb" mode. */
+ MANUAL_DND,
+
+ /**
+ * An implicit mode, automatically created and managed by the system on behalf of apps that
+ * call {@code setInterruptionFilter()} or {@code setNotificationPolicy()} (with some
+ * exceptions).
+ */
+ IMPLICIT,
+ }
+
public enum Status {
ENABLED,
ENABLED_AND_ACTIVE,
@@ -126,8 +141,8 @@
private final String mId;
private final AutomaticZenRule mRule;
+ private final Kind mKind;
private final Status mStatus;
- private final boolean mIsManualDnd;
/**
* Initializes a {@link ZenMode}, mainly based on the information from the
@@ -137,9 +152,11 @@
* active, or the reason it was disabled) are read from the {@link ZenModeConfig.ZenRule} --
* see {@link #computeStatus}.
*/
- public ZenMode(String id, @NonNull AutomaticZenRule rule,
+ ZenMode(String id, @NonNull AutomaticZenRule rule,
@NonNull ZenModeConfig.ZenRule zenRuleExtraData) {
- this(id, rule, computeStatus(zenRuleExtraData), false);
+ this(id, rule,
+ ZenModeConfig.isImplicitRuleId(id) ? Kind.IMPLICIT : Kind.NORMAL,
+ computeStatus(zenRuleExtraData));
}
private static Status computeStatus(@NonNull ZenModeConfig.ZenRule zenRuleExtraData) {
@@ -158,13 +175,16 @@
}
}
- public static ZenMode manualDndMode(AutomaticZenRule manualRule, boolean isActive) {
+ static ZenMode manualDndMode(AutomaticZenRule manualRule, boolean isActive) {
// Manual rule is owned by the system, so we set it here
AutomaticZenRule manualRuleWithPkg = new AutomaticZenRule.Builder(manualRule)
.setPackage(PACKAGE_ANDROID)
.build();
- return new ZenMode(MANUAL_DND_MODE_ID, manualRuleWithPkg,
- isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED, true);
+ return new ZenMode(
+ MANUAL_DND_MODE_ID,
+ manualRuleWithPkg,
+ Kind.MANUAL_DND,
+ isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED);
}
/**
@@ -183,19 +203,19 @@
.setIconResId(iconResId)
.setManualInvocationAllowed(true)
.build();
- return new ZenMode(TEMP_NEW_MODE_ID, rule, Status.ENABLED, false);
+ return new ZenMode(TEMP_NEW_MODE_ID, rule, Kind.NORMAL, Status.ENABLED);
}
- private ZenMode(String id, @NonNull AutomaticZenRule rule, Status status, boolean isManualDnd) {
+ private ZenMode(String id, @NonNull AutomaticZenRule rule, Kind kind, Status status) {
mId = id;
mRule = rule;
+ mKind = kind;
mStatus = status;
- mIsManualDnd = isManualDnd;
}
/** Creates a deep copy of this object. */
public ZenMode copy() {
- return new ZenMode(mId, new AutomaticZenRule.Builder(mRule).build(), mStatus, mIsManualDnd);
+ return new ZenMode(mId, new AutomaticZenRule.Builder(mRule).build(), mKind, mStatus);
}
@NonNull
@@ -264,10 +284,32 @@
return mRule.getType() + ":" + mRule.getPackageName() + ":" + mRule.getIconResId();
}
+ /**
+ * Returns the mode icon -- which can be either app-provided (via {@code addAutomaticZenRule}),
+ * user-chosen (via the icon picker in Settings), the app's launcher icon for implicit rules
+ * (in its monochrome variant, if available), or a default icon based on the mode type.
+ */
@NonNull
public ListenableFuture<Drawable> getIcon(@NonNull Context context,
@NonNull ZenIconLoader iconLoader) {
- if (mIsManualDnd) {
+ if (mKind == Kind.MANUAL_DND) {
+ return Futures.immediateFuture(requireNonNull(
+ context.getDrawable(R.drawable.ic_do_not_disturb_on_24dp)));
+ }
+
+ return iconLoader.getIcon(context, mRule);
+ }
+
+ /**
+ * Returns an alternative mode icon. The difference with {@link #getIcon} is that it's the
+ * basic DND icon not only for Manual DND, but also for <em>implicit rules</em>. As such, it's
+ * suitable for places where showing the launcher icon of an app could be confusing, such as
+ * the status bar or lockscreen.
+ */
+ @NonNull
+ public ListenableFuture<Drawable> getLockscreenIcon(@NonNull Context context,
+ @NonNull ZenIconLoader iconLoader) {
+ if (mKind == Kind.MANUAL_DND || mKind == Kind.IMPLICIT) {
return Futures.immediateFuture(requireNonNull(
context.getDrawable(R.drawable.ic_do_not_disturb_on_24dp)));
}
@@ -373,7 +415,7 @@
}
public boolean isManualDnd() {
- return mIsManualDnd;
+ return mKind == Kind.MANUAL_DND;
}
/**
@@ -404,18 +446,18 @@
return obj instanceof ZenMode other
&& mId.equals(other.mId)
&& mRule.equals(other.mRule)
- && mStatus.equals(other.mStatus)
- && mIsManualDnd == other.mIsManualDnd;
+ && mKind.equals(other.mKind)
+ && mStatus.equals(other.mStatus);
}
@Override
public int hashCode() {
- return Objects.hash(mId, mRule, mStatus, mIsManualDnd);
+ return Objects.hash(mId, mRule, mKind, mStatus);
}
@Override
public String toString() {
- return mId + " (" + mStatus + ") -> " + mRule;
+ return mId + " (" + mKind + ", " + mStatus + ") -> " + mRule;
}
@Override
@@ -427,8 +469,8 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mId);
dest.writeParcelable(mRule, 0);
+ dest.writeString(mKind.name());
dest.writeString(mStatus.name());
- dest.writeBoolean(mIsManualDnd);
}
public static final Creator<ZenMode> CREATOR = new Creator<ZenMode>() {
@@ -438,8 +480,8 @@
in.readString(),
checkNotNull(in.readParcelable(AutomaticZenRule.class.getClassLoader(),
AutomaticZenRule.class)),
- Status.valueOf(in.readString()),
- in.readBoolean());
+ Kind.valueOf(in.readString()),
+ Status.valueOf(in.readString()));
}
@Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingFooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingFooterPreferenceTest.java
new file mode 100644
index 0000000..cc2f788
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingFooterPreferenceTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class DeviceSettingFooterPreferenceTest {
+
+ @Test
+ public void getMethods() {
+ DeviceSettingFooterPreference preference =
+ new DeviceSettingFooterPreference.Builder()
+ .setFooterText("footer_text")
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ assertThat(preference.getFooterText()).isEqualTo("footer_text");
+ assertThat(preference.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void parcelOperation() {
+ DeviceSettingFooterPreference preference =
+ new DeviceSettingFooterPreference.Builder()
+ .setFooterText("footer_text")
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ DeviceSettingFooterPreference fromParcel = writeAndRead(preference);
+
+ assertThat(fromParcel.getFooterText()).isEqualTo(preference.getFooterText());
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(preference.getExtras().getString("key1"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private DeviceSettingFooterPreference writeAndRead(DeviceSettingFooterPreference preference) {
+ Parcel parcel = Parcel.obtain();
+ preference.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ DeviceSettingFooterPreference fromParcel =
+ DeviceSettingFooterPreference.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
index 9568d66..7223e90 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
@@ -50,7 +50,6 @@
null,
Bundle(),
)),
- moreSettingsFooter = "footer",
extras = Bundle().apply { putString("key1", "value1") },
)
@@ -72,7 +71,6 @@
.containsExactly("class_name_2")
assertThat(fromParcel.moreSettingsItems.stream().map { it.intentAction }.toList())
.containsExactly("intent_action_2")
- assertThat(fromParcel.moreSettingsFooter).isEqualTo(config.moreSettingsFooter)
}
private fun writeAndRead(item: DeviceSettingsConfig): DeviceSettingsConfig {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
index 4c5ee9e..061d515 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -97,9 +97,7 @@
`when`(cachedDevice.address).thenReturn(BLUETOOTH_ADDRESS)
`when`(
bluetoothDevice.getMetadata(
- DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS
- )
- )
+ DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
.thenReturn(BLUETOOTH_DEVICE_METADATA.toByteArray())
`when`(configService.queryLocalInterface(anyString())).thenReturn(configService)
@@ -122,8 +120,7 @@
connection.onServiceConnected(
ComponentName(
SETTING_PROVIDER_SERVICE_PACKAGE_NAME_1,
- SETTING_PROVIDER_SERVICE_CLASS_NAME_1
- ),
+ SETTING_PROVIDER_SERVICE_CLASS_NAME_1),
settingProviderService1,
)
SETTING_PROVIDER_SERVICE_INTENT_ACTION_2 ->
@@ -168,9 +165,7 @@
`when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
`when`(
bluetoothDevice.getMetadata(
- DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS
- )
- )
+ DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
.thenReturn("".toByteArray())
var config: DeviceSettingConfigModel? = null
@@ -178,10 +173,7 @@
delay(1000)
verify(bluetoothAdapter)
.addOnMetadataChangedListener(
- eq(bluetoothDevice),
- any(),
- metadataChangeCaptor.capture()
- )
+ eq(bluetoothDevice), any(), metadataChangeCaptor.capture())
metadataChangeCaptor.value.onMetadataChanged(
bluetoothDevice,
DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
@@ -189,9 +181,7 @@
)
`when`(
bluetoothDevice.getMetadata(
- DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS
- )
- )
+ DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
.thenReturn(BLUETOOTH_DEVICE_METADATA.toByteArray())
job.join()
@@ -303,10 +293,8 @@
DeviceSettingState.Builder()
.setSettingId(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
.setPreferenceState(
- ActionSwitchPreferenceState.Builder().setChecked(false).build()
- )
- .build()
- )
+ ActionSwitchPreferenceState.Builder().setChecked(false).build())
+ .build())
}
}
@@ -337,10 +325,8 @@
DeviceSettingState.Builder()
.setSettingId(DeviceSettingId.DEVICE_SETTING_ID_ANC)
.setPreferenceState(
- MultiTogglePreferenceState.Builder().setState(2).build()
- )
- .build()
- )
+ MultiTogglePreferenceState.Builder().setState(2).build())
+ .build())
}
}
@@ -353,7 +339,8 @@
val pref = serviceResponse.preference as ActionSwitchPreference
assertThat(actual.title).isEqualTo(pref.title)
assertThat(actual.summary).isEqualTo(pref.summary)
- assertThat(actual.icon).isEqualTo(DeviceSettingIcon.BitmapIcon(pref.icon!!))
+ assertThat(actual.icon)
+ .isEqualTo(pref.icon?.let { DeviceSettingIcon.BitmapIcon(it) })
assertThat(actual.isAllowedChangingState).isEqualTo(pref.isAllowedChangingState)
if (pref.hasSwitch()) {
assertThat(actual.switchState!!.checked).isEqualTo(pref.checked)
@@ -378,7 +365,8 @@
private fun assertToggle(actual: ToggleModel, serviceResponse: ToggleInfo) {
assertThat(actual.label).isEqualTo(serviceResponse.label)
- assertThat(actual.icon).isEqualTo(serviceResponse.icon)
+ assertThat((actual.icon as DeviceSettingIcon.BitmapIcon).bitmap)
+ .isEqualTo(serviceResponse.icon)
}
private fun assertConfig(
@@ -393,7 +381,6 @@
for (i in 0..<actual.moreSettingsItems.size) {
assertConfigItem(actual.moreSettingsItems[i], serviceResponse.moreSettingsItems[i])
}
- assertThat(actual.moreSettingsPageFooter).isEqualTo(serviceResponse.moreSettingsFooter)
}
private fun assertConfigItem(
@@ -437,15 +424,13 @@
DeviceSettingId.DEVICE_SETTING_ID_HEADER,
SETTING_PROVIDER_SERVICE_PACKAGE_NAME_1,
SETTING_PROVIDER_SERVICE_CLASS_NAME_1,
- SETTING_PROVIDER_SERVICE_INTENT_ACTION_1
- )
+ SETTING_PROVIDER_SERVICE_INTENT_ACTION_1)
val DEVICE_SETTING_ITEM_2 =
DeviceSettingItem(
DeviceSettingId.DEVICE_SETTING_ID_ANC,
SETTING_PROVIDER_SERVICE_PACKAGE_NAME_2,
SETTING_PROVIDER_SERVICE_CLASS_NAME_2,
- SETTING_PROVIDER_SERVICE_INTENT_ACTION_2
- )
+ SETTING_PROVIDER_SERVICE_INTENT_ACTION_2)
val DEVICE_SETTING_1 =
DeviceSetting.Builder()
.setSettingId(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
@@ -454,8 +439,7 @@
.setTitle("title1")
.setHasSwitch(true)
.setAllowedChangingState(true)
- .build()
- )
+ .build())
.build()
val DEVICE_SETTING_2 =
DeviceSetting.Builder()
@@ -468,22 +452,18 @@
ToggleInfo.Builder()
.setLabel("label1")
.setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
- .build()
- )
+ .build())
.addToggleInfo(
ToggleInfo.Builder()
.setLabel("label2")
.setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
- .build()
- )
- .build()
- )
+ .build())
+ .build())
.build()
val DEVICE_SETTING_CONFIG =
DeviceSettingsConfig(
listOf(DEVICE_SETTING_ITEM_1),
listOf(DEVICE_SETTING_ITEM_2),
- "footer"
)
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index bab4bc3b..f533e77 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -30,7 +30,14 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
import android.app.AutomaticZenRule;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Parcel;
import android.service.notification.Condition;
@@ -40,9 +47,12 @@
import com.android.internal.R;
+import com.google.common.util.concurrent.ListenableFuture;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.List;
@@ -60,6 +70,13 @@
.setZenPolicy(ZEN_POLICY)
.build();
+ private static final String IMPLICIT_RULE_ID = ZenModeConfig.implicitRuleId("some.package");
+ private static final AutomaticZenRule IMPLICIT_ZEN_RULE =
+ new AutomaticZenRule.Builder("Implicit", Uri.parse("implicit/some.package"))
+ .setPackage("some.package")
+ .setType(TYPE_OTHER)
+ .build();
+
@Test
public void testBasicMethods() {
ZenMode zenMode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, true));
@@ -265,6 +282,79 @@
assertUnparceledIsEqualToOriginal("custom_manual",
ZenMode.newCustomManual("New mode", R.drawable.ic_zen_mode_type_immersive));
+
+ assertUnparceledIsEqualToOriginal("implicit",
+ new ZenMode(IMPLICIT_RULE_ID, IMPLICIT_ZEN_RULE,
+ zenConfigRuleFor(IMPLICIT_ZEN_RULE, false)));
+ }
+
+ @Test
+ public void getIcon_normalMode_loadsIconNormally() {
+ ZenIconLoader iconLoader = mock(ZenIconLoader.class);
+ ZenMode mode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, false));
+
+ ListenableFuture<Drawable> unused = mode.getIcon(RuntimeEnvironment.getApplication(),
+ iconLoader);
+
+ verify(iconLoader).getIcon(any(), eq(ZEN_RULE));
+ }
+
+ @Test
+ public void getIcon_manualDnd_returnsFixedIcon() {
+ ZenIconLoader iconLoader = mock(ZenIconLoader.class);
+
+ ListenableFuture<Drawable> future = TestModeBuilder.MANUAL_DND_INACTIVE.getIcon(
+ RuntimeEnvironment.getApplication(), iconLoader);
+
+ assertThat(future.isDone()).isTrue();
+ verify(iconLoader, never()).getIcon(any(), any());
+ }
+
+ @Test
+ public void getIcon_implicitMode_loadsIconNormally() {
+ ZenIconLoader iconLoader = mock(ZenIconLoader.class);
+ ZenMode mode = new ZenMode(IMPLICIT_RULE_ID, IMPLICIT_ZEN_RULE,
+ zenConfigRuleFor(IMPLICIT_ZEN_RULE, false));
+
+ ListenableFuture<Drawable> unused = mode.getIcon(RuntimeEnvironment.getApplication(),
+ iconLoader);
+
+ verify(iconLoader).getIcon(any(), eq(IMPLICIT_ZEN_RULE));
+ }
+
+ @Test
+ public void getLockscreenIcon_normalMode_loadsIconNormally() {
+ ZenIconLoader iconLoader = mock(ZenIconLoader.class);
+ ZenMode mode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, false));
+
+ ListenableFuture<Drawable> unused = mode.getLockscreenIcon(
+ RuntimeEnvironment.getApplication(), iconLoader);
+
+ verify(iconLoader).getIcon(any(), eq(ZEN_RULE));
+ }
+
+ @Test
+ public void getLockscreenIcon_manualDnd_returnsFixedIcon() {
+ ZenIconLoader iconLoader = mock(ZenIconLoader.class);
+
+ ListenableFuture<Drawable> future = TestModeBuilder.MANUAL_DND_INACTIVE.getLockscreenIcon(
+ RuntimeEnvironment.getApplication(), iconLoader);
+
+ assertThat(future.isDone()).isTrue();
+ verify(iconLoader, never()).getIcon(any(), any());
+ }
+
+ @Test
+ public void getLockscreenIcon_implicitMode_returnsFixedIcon() {
+ ZenIconLoader iconLoader = mock(ZenIconLoader.class);
+ ZenMode mode = new ZenMode(IMPLICIT_RULE_ID, IMPLICIT_ZEN_RULE,
+ zenConfigRuleFor(IMPLICIT_ZEN_RULE, false));
+
+ ListenableFuture<Drawable> future = mode.getLockscreenIcon(
+ RuntimeEnvironment.getApplication(), iconLoader);
+
+ assertThat(future.isDone()).isTrue();
+ verify(iconLoader, never()).getIcon(any(), any());
}
private static void assertUnparceledIsEqualToOriginal(String type, ZenMode original) {
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 3aa89ee..c1bb55c 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -893,6 +893,7 @@
],
static_libs: [
"RoboTestLibraries",
+ "androidx.compose.runtime_runtime",
],
libs: [
"android.test.runner",
@@ -929,6 +930,7 @@
],
static_libs: [
"RoboTestLibraries",
+ "androidx.compose.runtime_runtime",
],
libs: [
"android.test.runner",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 3895595..6412276 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -62,7 +62,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -325,13 +324,4 @@
// enabled.
mController.onViewAttached();
}
-
- @Test
- public void destroy_cleansUpState() {
- mController.destroy();
- verify(mStateController).removeCallback(any());
- verify(mAmbientStatusBarViewController).destroy();
- verify(mComplicationHostViewController).destroy();
- verify(mLowLightTransitionCoordinator).setLowLightEnterListener(ArgumentMatchers.isNull());
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index da82b5f..89ec3cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -596,9 +596,6 @@
// are created.
verify(mDreamOverlayComponent).getDreamOverlayContainerViewController()
verify(mAmbientTouchComponent).getTouchMonitor()
-
- // Verify DreamOverlayContainerViewController is destroyed.
- verify(mDreamOverlayContainerViewController).destroy()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt
index bc142e6..6395448 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt
@@ -16,120 +16,108 @@
package com.android.systemui.gesture.domain
+import android.app.ActivityManager
import android.content.ComponentName
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.navigationbar.gestural.data.respository.GestureRepository
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.navigationbar.gestural.data.gestureRepository
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor
+import com.android.systemui.shared.system.activityManagerWrapper
+import com.android.systemui.shared.system.taskStackChangeListeners
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.resetMain
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
-import org.junit.After
-import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
-import org.mockito.kotlin.any
-import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
-import org.mockito.kotlin.verify
+import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@SmallTest
class GestureInteractorTest : SysuiTestCase() {
@Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ private val kosmos = testKosmos()
- val dispatcher = StandardTestDispatcher()
+ val dispatcher = kosmos.testDispatcher
+ val repository = spy(kosmos.gestureRepository)
val testScope = TestScope(dispatcher)
- @Mock private lateinit var gestureRepository: GestureRepository
+ private val underTest by lazy { createInteractor() }
- private val underTest by lazy {
- GestureInteractor(gestureRepository, testScope.backgroundScope)
+ private fun createInteractor(): GestureInteractor {
+ return GestureInteractor(
+ repository,
+ dispatcher,
+ kosmos.backgroundCoroutineContext,
+ testScope,
+ kosmos.activityManagerWrapper,
+ kosmos.taskStackChangeListeners
+ )
}
- @Before
- fun setup() {
- Dispatchers.setMain(dispatcher)
- whenever(gestureRepository.gestureBlockedActivities).thenReturn(MutableStateFlow(setOf()))
- }
+ private fun setTopActivity(componentName: ComponentName) {
+ val task = mock<ActivityManager.RunningTaskInfo>()
+ task.topActivity = componentName
+ whenever(kosmos.activityManagerWrapper.runningTask).thenReturn(task)
- @After
- fun tearDown() {
- Dispatchers.resetMain()
+ kosmos.taskStackChangeListeners.listenerImpl.onTaskStackChanged()
}
@Test
fun addBlockedActivity_testCombination() =
testScope.runTest {
val globalComponent = mock<ComponentName>()
- whenever(gestureRepository.gestureBlockedActivities)
- .thenReturn(MutableStateFlow(setOf(globalComponent)))
+ repository.addGestureBlockedActivity(globalComponent)
+
val localComponent = mock<ComponentName>()
+
+ val blocked by collectLastValue(underTest.topActivityBlocked)
+
underTest.addGestureBlockedActivity(localComponent, GestureInteractor.Scope.Local)
- val lastSeen by collectLastValue(underTest.gestureBlockedActivities)
- testScope.runCurrent()
- verify(gestureRepository, never()).addGestureBlockedActivity(any())
- assertThat(lastSeen).hasSize(2)
- assertThat(lastSeen).containsExactly(globalComponent, localComponent)
+
+ assertThat(blocked).isFalse()
+
+ setTopActivity(localComponent)
+
+ assertThat(blocked).isTrue()
+ }
+
+ @Test
+ fun initialization_testEmit() =
+ testScope.runTest {
+ val globalComponent = mock<ComponentName>()
+ repository.addGestureBlockedActivity(globalComponent)
+ setTopActivity(globalComponent)
+
+ val interactor = createInteractor()
+
+ val blocked by collectLastValue(interactor.topActivityBlocked)
+ assertThat(blocked).isTrue()
}
@Test
fun addBlockedActivityLocally_onlyAffectsLocalInteractor() =
testScope.runTest {
- val component = mock<ComponentName>()
- underTest.addGestureBlockedActivity(component, GestureInteractor.Scope.Local)
- val lastSeen by collectLastValue(underTest.gestureBlockedActivities)
- testScope.runCurrent()
- verify(gestureRepository, never()).addGestureBlockedActivity(any())
- assertThat(lastSeen).contains(component)
- }
+ val interactor1 = createInteractor()
+ val interactor1Blocked by collectLastValue(interactor1.topActivityBlocked)
+ val interactor2 = createInteractor()
+ val interactor2Blocked by collectLastValue(interactor2.topActivityBlocked)
- @Test
- fun removeBlockedActivityLocally_onlyAffectsLocalInteractor() =
- testScope.runTest {
- val component = mock<ComponentName>()
- underTest.addGestureBlockedActivity(component, GestureInteractor.Scope.Local)
- val lastSeen by collectLastValue(underTest.gestureBlockedActivities)
- testScope.runCurrent()
- underTest.removeGestureBlockedActivity(component, GestureInteractor.Scope.Local)
- testScope.runCurrent()
- verify(gestureRepository, never()).removeGestureBlockedActivity(any())
- assertThat(lastSeen).isEmpty()
- }
+ val localComponent = mock<ComponentName>()
- @Test
- fun addBlockedActivity_invokesRepository() =
- testScope.runTest {
- val component = mock<ComponentName>()
- underTest.addGestureBlockedActivity(component, GestureInteractor.Scope.Global)
- runCurrent()
- val captor = argumentCaptor<ComponentName>()
- verify(gestureRepository).addGestureBlockedActivity(captor.capture())
- assertThat(captor.firstValue).isEqualTo(component)
- }
+ interactor1.addGestureBlockedActivity(localComponent, GestureInteractor.Scope.Local)
+ setTopActivity(localComponent)
- @Test
- fun removeBlockedActivity_invokesRepository() =
- testScope.runTest {
- val component = mock<ComponentName>()
- underTest.removeGestureBlockedActivity(component, GestureInteractor.Scope.Global)
- runCurrent()
- val captor = argumentCaptor<ComponentName>()
- verify(gestureRepository).removeGestureBlockedActivity(captor.capture())
- assertThat(captor.firstValue).isEqualTo(component)
+ assertThat(interactor1Blocked).isTrue()
+ assertThat(interactor2Blocked).isFalse()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
index 976dc52..7d57220 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
@@ -17,8 +17,14 @@
package com.android.systemui.lifecycle
import android.view.View
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -26,6 +32,8 @@
import com.android.systemui.util.Assert
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -149,6 +157,48 @@
assertThat(viewModel.isActivated).isTrue()
}
+
+ @Test
+ fun hydratedStateOf() {
+ val keepAliveMutable = mutableStateOf(true)
+ val upstreamStateFlow = MutableStateFlow(true)
+ val upstreamFlow = upstreamStateFlow.map { !it }
+ composeRule.setContent {
+ val keepAlive by keepAliveMutable
+ if (keepAlive) {
+ val viewModel = rememberViewModel {
+ FakeSysUiViewModel(
+ upstreamFlow = upstreamFlow,
+ upstreamStateFlow = upstreamStateFlow,
+ )
+ }
+
+ Column {
+ Text(
+ "upstreamStateFlow=${viewModel.stateBackedByStateFlow}",
+ Modifier.testTag("upstreamStateFlow")
+ )
+ Text(
+ "upstreamFlow=${viewModel.stateBackedByFlow}",
+ Modifier.testTag("upstreamFlow")
+ )
+ }
+ }
+ }
+
+ composeRule.waitForIdle()
+ composeRule
+ .onNode(hasTestTag("upstreamStateFlow"))
+ .assertTextEquals("upstreamStateFlow=true")
+ composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=false")
+
+ composeRule.runOnUiThread { upstreamStateFlow.value = false }
+ composeRule.waitForIdle()
+ composeRule
+ .onNode(hasTestTag("upstreamStateFlow"))
+ .assertTextEquals("upstreamStateFlow=false")
+ composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=true")
+ }
}
private class FakeViewModel : SysUiViewModel() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index 54b7d25..827236c5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -152,7 +152,7 @@
}
with(tiles?.elementAt(1)!!) {
assertThat(this.text).isEqualTo("Active with manual")
- assertThat(this.subtext).isEqualTo("trigger description")
+ assertThat(this.subtext).isEqualTo("On • trigger description")
assertThat(this.enabled).isEqualTo(true)
}
with(tiles?.elementAt(2)!!) {
@@ -274,6 +274,123 @@
}
@Test
+ fun tiles_calculatesSubtext() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tiles)
+
+ repository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setName("With description, inactive")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription("When the going gets tough")
+ .setActive(false)
+ .build(),
+ TestModeBuilder()
+ .setName("With description, active")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription("When in Rome")
+ .setActive(true)
+ .build(),
+ TestModeBuilder()
+ .setName("With description, needs setup")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription("When you find yourself in a hole")
+ .setEnabled(false, /* byUser= */ false)
+ .build(),
+ TestModeBuilder()
+ .setName("Without description, inactive")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription(null)
+ .setActive(false)
+ .build(),
+ TestModeBuilder()
+ .setName("Without description, active")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription(null)
+ .setActive(true)
+ .build(),
+ TestModeBuilder()
+ .setName("Without description, needs setup")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription(null)
+ .setEnabled(false, /* byUser= */ false)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(tiles!!).hasSize(6)
+ assertThat(tiles!![0].subtext).isEqualTo("When the going gets tough")
+ assertThat(tiles!![1].subtext).isEqualTo("On • When in Rome")
+ assertThat(tiles!![2].subtext).isEqualTo("Set up")
+ assertThat(tiles!![3].subtext).isEqualTo("Off")
+ assertThat(tiles!![4].subtext).isEqualTo("On")
+ assertThat(tiles!![5].subtext).isEqualTo("Set up")
+ }
+
+ @Test
+ fun tiles_calculatesContentDescription() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tiles)
+
+ repository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setName("With description, inactive")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription("When the going gets tough")
+ .setActive(false)
+ .build(),
+ TestModeBuilder()
+ .setName("With description, active")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription("When in Rome")
+ .setActive(true)
+ .build(),
+ TestModeBuilder()
+ .setName("With description, needs setup")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription("When you find yourself in a hole")
+ .setEnabled(false, /* byUser= */ false)
+ .build(),
+ TestModeBuilder()
+ .setName("Without description, inactive")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription(null)
+ .setActive(false)
+ .build(),
+ TestModeBuilder()
+ .setName("Without description, active")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription(null)
+ .setActive(true)
+ .build(),
+ TestModeBuilder()
+ .setName("Without description, needs setup")
+ .setManualInvocationAllowed(true)
+ .setTriggerDescription(null)
+ .setEnabled(false, /* byUser= */ false)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(tiles!!).hasSize(6)
+ assertThat(tiles!![0].contentDescription)
+ .isEqualTo("With description, inactive\nOff\nWhen the going gets tough")
+ assertThat(tiles!![1].contentDescription)
+ .isEqualTo("With description, active\nOn\nWhen in Rome")
+ assertThat(tiles!![2].contentDescription)
+ .isEqualTo("With description, needs setup\nSet up")
+ assertThat(tiles!![3].contentDescription)
+ .isEqualTo("Without description, inactive\nOff")
+ assertThat(tiles!![4].contentDescription).isEqualTo("Without description, active\nOn")
+ assertThat(tiles!![5].contentDescription)
+ .isEqualTo("Without description, needs setup\nSet up")
+ }
+
+ @Test
fun onClick_togglesTileState() =
testScope.runTest {
val tiles by collectLastValue(underTest.tiles)
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8a2e767..18b7073 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1106,6 +1106,9 @@
<!-- Priority modes: label for an active mode [CHAR LIMIT=35] -->
<string name="zen_mode_on">On</string>
+ <!-- Priority modes: label for an active mode, with details [CHAR LIMIT=10] -->
+ <string name="zen_mode_on_with_details">On • <xliff:g id="trigger_description" example="Mon-Fri, 23:00-7:00">%1$s</xliff:g></string>
+
<!-- Priority modes: label for an inactive mode [CHAR LIMIT=35] -->
<string name="zen_mode_off">Off</string>
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
index d27e72a..190bc15 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
@@ -122,9 +122,4 @@
* @param session
*/
void onSessionStart(TouchSession session);
-
- /**
- * Called when the handler is being torn down.
- */
- default void onDestroy() {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index 1be6f9e..efa55e9 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -581,10 +581,6 @@
mBoundsFlow.cancel(new CancellationException());
}
- for (TouchHandler handler : mHandlers) {
- handler.onDestroy();
- }
-
mInitialized = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 24ac542..b45ebd8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -44,7 +44,6 @@
import com.android.systemui.statusbar.CrossFadeHelper
import javax.inject.Inject
import javax.inject.Named
-import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.launch
/** Controller for dream overlay animations. */
@@ -85,62 +84,51 @@
private var mCurrentBlurRadius: Float = 0f
- private var mLifecycleFlowHandle: DisposableHandle? = null
-
fun init(view: View) {
this.view = view
- mLifecycleFlowHandle =
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
- dreamViewModel.dreamOverlayTranslationY.collect { px ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int ->
- setElementsTranslationYAtPosition(px, position)
- },
- POSITION_TOP or POSITION_BOTTOM
- )
- }
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ dreamViewModel.dreamOverlayTranslationY.collect { px ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int -> setElementsTranslationYAtPosition(px, position) },
+ POSITION_TOP or POSITION_BOTTOM
+ )
}
+ }
- launch {
- dreamViewModel.dreamOverlayTranslationX.collect { px ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int ->
- setElementsTranslationXAtPosition(px, position)
- },
- POSITION_TOP or POSITION_BOTTOM
- )
- }
+ launch {
+ dreamViewModel.dreamOverlayTranslationX.collect { px ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int -> setElementsTranslationXAtPosition(px, position) },
+ POSITION_TOP or POSITION_BOTTOM
+ )
}
+ }
- launch {
- dreamViewModel.dreamOverlayAlpha.collect { alpha ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int ->
- setElementsAlphaAtPosition(
- alpha = alpha,
- position = position,
- fadingOut = true,
- )
- },
- POSITION_TOP or POSITION_BOTTOM
- )
- }
+ launch {
+ dreamViewModel.dreamOverlayAlpha.collect { alpha ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsAlphaAtPosition(
+ alpha = alpha,
+ position = position,
+ fadingOut = true,
+ )
+ },
+ POSITION_TOP or POSITION_BOTTOM
+ )
}
+ }
- launch {
- dreamViewModel.transitionEnded.collect { _ ->
- mOverlayStateController.setExitAnimationsRunning(false)
- }
+ launch {
+ dreamViewModel.transitionEnded.collect { _ ->
+ mOverlayStateController.setExitAnimationsRunning(false)
}
}
}
- }
-
- fun destroy() {
- mLifecycleFlowHandle?.dispose()
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index bf6d266..76c7d23 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -59,7 +59,6 @@
import com.android.systemui.util.ViewController;
import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.DisposableHandle;
import kotlinx.coroutines.flow.FlowKt;
import java.util.Arrays;
@@ -186,8 +185,6 @@
}
};
- private DisposableHandle mFlowHandle;
-
@Inject
public DreamOverlayContainerViewController(
DreamOverlayContainerView containerView,
@@ -255,17 +252,6 @@
}
@Override
- public void destroy() {
- mStateController.removeCallback(mDreamOverlayStateCallback);
- mStatusBarViewController.destroy();
- mComplicationHostViewController.destroy();
- mDreamOverlayAnimationsController.destroy();
- mLowLightTransitionCoordinator.setLowLightEnterListener(null);
-
- super.destroy();
- }
-
- @Override
protected void onViewAttached() {
mWakingUpFromSwipe = false;
mJitterStartTimeMillis = System.currentTimeMillis();
@@ -277,7 +263,7 @@
emptyRegion.recycle();
if (dreamHandlesBeingObscured()) {
- mFlowHandle = collectFlow(
+ collectFlow(
mView,
FlowKt.distinctUntilChanged(combineFlows(
mKeyguardTransitionInteractor.isFinishedIn(
@@ -309,10 +295,6 @@
@Override
protected void onViewDetached() {
- if (mFlowHandle != null) {
- mFlowHandle.dispose();
- mFlowHandle = null;
- }
mHandler.removeCallbacksAndMessages(null);
mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
mBouncerlessScrimController.removeCallback(mBouncerlessExpansionCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 0c1fb72..7a9537b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -70,12 +70,8 @@
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
-import kotlinx.coroutines.Job;
-
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
-import java.util.concurrent.CancellationException;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -144,8 +140,6 @@
private ComponentName mCurrentBlockedGestureDreamActivityComponent;
- private final ArrayList<Job> mFlows = new ArrayList<>();
-
/**
* This {@link LifecycleRegistry} controls when dream overlay functionality, like touch
* handling, should be active. It will automatically be paused when the dream overlay is hidden
@@ -315,12 +309,12 @@
mExecutor.execute(() -> setLifecycleStateLocked(Lifecycle.State.CREATED));
- mFlows.add(collectFlow(getLifecycle(), mCommunalInteractor.isCommunalAvailable(),
- mIsCommunalAvailableCallback));
- mFlows.add(collectFlow(getLifecycle(), communalInteractor.isCommunalVisible(),
- mCommunalVisibleConsumer));
- mFlows.add(collectFlow(getLifecycle(), keyguardInteractor.primaryBouncerShowing,
- mBouncerShowingConsumer));
+ collectFlow(getLifecycle(), mCommunalInteractor.isCommunalAvailable(),
+ mIsCommunalAvailableCallback);
+ collectFlow(getLifecycle(), communalInteractor.isCommunalVisible(),
+ mCommunalVisibleConsumer);
+ collectFlow(getLifecycle(), keyguardInteractor.primaryBouncerShowing,
+ mBouncerShowingConsumer);
}
@NonNull
@@ -345,11 +339,6 @@
public void onDestroy() {
mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
- for (Job job : mFlows) {
- job.cancel(new CancellationException());
- }
- mFlows.clear();
-
mExecutor.execute(() -> {
setLifecycleStateLocked(Lifecycle.State.DESTROYED);
@@ -572,7 +561,6 @@
if (mStarted && mWindow != null) {
try {
- mWindow.clearContentView();
mWindowManager.removeView(mWindow.getDecorView());
} catch (IllegalArgumentException e) {
Log.e(TAG, "Error removing decor view when resetting overlay", e);
@@ -583,10 +571,7 @@
mStateController.setLowLightActive(false);
mStateController.setEntryAnimationsFinished(false);
- if (mDreamOverlayContainerViewController != null) {
- mDreamOverlayContainerViewController.destroy();
- mDreamOverlayContainerViewController = null;
- }
+ mDreamOverlayContainerViewController = null;
if (mTouchMonitor != null) {
mTouchMonitor.destroy();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index 5ba780f..ee7b6f5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -33,11 +33,7 @@
import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
import com.android.systemui.statusbar.phone.CentralSurfaces;
-import kotlinx.coroutines.Job;
-
-import java.util.ArrayList;
import java.util.Optional;
-import java.util.concurrent.CancellationException;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -53,8 +49,6 @@
private final ConfigurationInteractor mConfigurationInteractor;
private Boolean mIsEnabled = false;
- private ArrayList<Job> mFlows = new ArrayList<>();
-
private int mLayoutDirection = LayoutDirection.LTR;
@VisibleForTesting
@@ -76,17 +70,17 @@
mCommunalInteractor = communalInteractor;
mConfigurationInteractor = configurationInteractor;
- mFlows.add(collectFlow(
+ collectFlow(
mLifecycle,
mCommunalInteractor.isCommunalAvailable(),
mIsCommunalAvailableCallback
- ));
+ );
- mFlows.add(collectFlow(
+ collectFlow(
mLifecycle,
mConfigurationInteractor.getLayoutDirection(),
mLayoutDirectionCallback
- ));
+ );
}
@Override
@@ -146,13 +140,4 @@
}
});
}
-
- @Override
- public void onDestroy() {
- for (Job job : mFlows) {
- job.cancel(new CancellationException());
- }
- mFlows.clear();
- TouchHandler.super.onDestroy();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
index 104b076..32c4760 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
@@ -18,13 +18,45 @@
import android.view.View
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.snapshots.StateFactoryMarker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
/** Base class for all System UI view-models. */
abstract class SysUiViewModel : BaseActivatable() {
+ @StateFactoryMarker
+ fun <T> hydratedStateOf(
+ source: StateFlow<T>,
+ ): State<T> {
+ return hydratedStateOf(
+ initialValue = source.value,
+ source = source,
+ )
+ }
+
+ @StateFactoryMarker
+ fun <T> hydratedStateOf(
+ initialValue: T,
+ source: Flow<T>,
+ ): State<T> {
+ val mutableState = mutableStateOf(initialValue)
+ addChild(
+ object : BaseActivatable() {
+ override suspend fun onActivated(): Nothing {
+ source.collect { mutableState.value = it }
+ awaitCancellation()
+ }
+ }
+ )
+ return mutableState
+ }
+
override suspend fun onActivated(): Nothing {
awaitCancellation()
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 388272f..0f82e02 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -73,8 +73,8 @@
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.policy.GestureNavigationSettingsObserver;
-import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.contextualeducation.GestureType;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor;
@@ -102,6 +102,8 @@
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.pip.Pip;
+import kotlinx.coroutines.Job;
+
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.Date;
@@ -109,6 +111,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
@@ -158,7 +161,7 @@
private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
- updateRunningActivityGesturesBlocked();
+ updateTopActivity();
}
@Override
public void onTaskCreated(int taskId, ComponentName componentName) {
@@ -222,6 +225,8 @@
private final Provider<LightBarController> mLightBarControllerProvider;
private final GestureInteractor mGestureInteractor;
+ private final ArraySet<ComponentName> mBlockedActivities = new ArraySet<>();
+ private Job mBlockedActivitiesJob = null;
private final JavaAdapter mJavaAdapter;
@@ -450,9 +455,6 @@
mJavaAdapter = javaAdapter;
mLastReportedConfig.setTo(mContext.getResources().getConfiguration());
- mJavaAdapter.alwaysCollectFlow(mGestureInteractor.getGestureBlockedActivities(),
- componentNames -> updateRunningActivityGesturesBlocked());
-
ComponentName recentsComponentName = ComponentName.unflattenFromString(
context.getString(com.android.internal.R.string.config_recentsComponentName));
if (recentsComponentName != null) {
@@ -568,12 +570,11 @@
}
}
- private void updateRunningActivityGesturesBlocked() {
+ private void updateTopActivity() {
if (edgebackGestureHandlerGetRunningTasksBackground()) {
- mBackgroundExecutor.execute(() -> mGestureBlockingActivityRunning.set(
- isGestureBlockingActivityRunning()));
+ mBackgroundExecutor.execute(() -> updateTopActivityPackageName());
} else {
- mGestureBlockingActivityRunning.set(isGestureBlockingActivityRunning());
+ updateTopActivityPackageName();
}
}
@@ -678,6 +679,11 @@
Log.e(TAG, "Failed to unregister window manager callbacks", e);
}
+ if (mBlockedActivitiesJob != null) {
+ mBlockedActivitiesJob.cancel(new CancellationException());
+ mBlockedActivitiesJob = null;
+ }
+ mBlockedActivities.clear();
} else {
mBackgroundExecutor.execute(mGestureNavigationSettingsObserver::register);
updateDisplaySize();
@@ -710,6 +716,12 @@
resetEdgeBackPlugin();
mPluginManager.addPluginListener(
this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
+
+ // Begin listening to changes in blocked activities list
+ mBlockedActivitiesJob = mJavaAdapter.alwaysCollectFlow(
+ mGestureInteractor.getTopActivityBlocked(),
+ blocked -> mGestureBlockingActivityRunning.set(blocked));
+
}
// Update the ML model resources.
updateMLModelState();
@@ -1302,7 +1314,7 @@
}
}
- private boolean isGestureBlockingActivityRunning() {
+ private void updateTopActivityPackageName() {
ActivityManager.RunningTaskInfo runningTask =
ActivityManagerWrapper.getInstance().getRunningTask();
ComponentName topActivity = runningTask == null ? null : runningTask.topActivity;
@@ -1311,8 +1323,6 @@
} else {
mPackageName = "_UNKNOWN";
}
-
- return topActivity != null && mGestureInteractor.areGesturesBlocked(topActivity);
}
public void setBackAnimation(BackAnimation backAnimation) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt
index 6dc5939..6182878 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt
@@ -17,17 +17,29 @@
package com.android.systemui.navigationbar.gestural.domain
import android.content.ComponentName
+import com.android.app.tracing.coroutines.flow.flowOn
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.navigationbar.gestural.data.respository.GestureRepository
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.shared.system.TaskStackChangeListener
+import com.android.systemui.shared.system.TaskStackChangeListeners
+import com.android.systemui.util.kotlin.combine
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/**
* {@link GestureInteractor} helps interact with gesture-related logic, including accessing the
@@ -37,7 +49,11 @@
@Inject
constructor(
private val gestureRepository: GestureRepository,
- @Application private val scope: CoroutineScope
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Background private val backgroundCoroutineContext: CoroutineContext,
+ @Application private val scope: CoroutineScope,
+ private val activityManagerWrapper: ActivityManagerWrapper,
+ private val taskStackChangeListeners: TaskStackChangeListeners,
) {
enum class Scope {
Local,
@@ -45,16 +61,38 @@
}
private val _localGestureBlockedActivities = MutableStateFlow<Set<ComponentName>>(setOf())
- /** A {@link StateFlow} for listening to changes in Activities where gestures are blocked */
- val gestureBlockedActivities: StateFlow<Set<ComponentName>>
- get() =
- combine(
- gestureRepository.gestureBlockedActivities,
- _localGestureBlockedActivities.asStateFlow()
- ) { global, local ->
- global + local
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), setOf())
+
+ private val _topActivity =
+ conflatedCallbackFlow {
+ val taskListener =
+ object : TaskStackChangeListener {
+ override fun onTaskStackChanged() {
+ trySend(Unit)
+ }
+ }
+
+ taskStackChangeListeners.registerTaskStackListener(taskListener)
+ awaitClose { taskStackChangeListeners.unregisterTaskStackListener(taskListener) }
+ }
+ .flowOn(mainDispatcher)
+ .emitOnStart()
+ .mapLatest { getTopActivity() }
+ .distinctUntilChanged()
+
+ private suspend fun getTopActivity(): ComponentName? =
+ withContext(backgroundCoroutineContext) {
+ val runningTask = activityManagerWrapper.runningTask
+ runningTask?.topActivity
+ }
+
+ val topActivityBlocked =
+ combine(
+ _topActivity,
+ gestureRepository.gestureBlockedActivities,
+ _localGestureBlockedActivities.asStateFlow()
+ ) { activity, global, local ->
+ activity != null && (global + local).contains(activity)
+ }
/**
* Adds an {@link Activity} to be blocked based on component when the topmost, focused {@link
@@ -92,12 +130,4 @@
}
}
}
-
- /**
- * Checks whether the specified {@link Activity} {@link ComponentName} is being blocked from
- * gestures.
- */
- fun areGesturesBlocked(activity: ComponentName): Boolean {
- return gestureBlockedActivities.value.contains(activity)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
index 408fc6d..32d37ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
@@ -16,9 +16,9 @@
per-file *Keyguard* = file:../keyguard/OWNERS
per-file *Notification* = set noparent
per-file *Notification* = file:notification/OWNERS
-per-file *Mode* = set noparent
+# Not setting noparent here, since *Mode* matches many other classes (e.g., *ViewModel*)
per-file *Mode* = file:notification/OWNERS
per-file *RemoteInput* = set noparent
per-file *RemoteInput* = file:notification/OWNERS
per-file *EmptyShadeView* = set noparent
-per-file *EmptyShadeView* = file:notification/OWNERS
\ No newline at end of file
+per-file *EmptyShadeView* = file:notification/OWNERS
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
index 069ae93..28e3995 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
@@ -44,4 +44,10 @@
/** Snooze the currently pinned HUN. */
fun snooze()
+
+ /** Unpin all currently pinned HUNs. */
+ fun unpinAll(userUnPinned: Boolean)
+
+ /** Release entries that were waiting for a shade expansion to complete. */
+ fun releaseAfterExpansion()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index 24b75d4..74ec7ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -148,6 +148,16 @@
fun snooze() {
headsUpRepository.snooze()
}
+
+ /** Unpin all currently pinned HUNs. */
+ fun unpinAll(userUnPinned: Boolean) {
+ headsUpRepository.unpinAll(userUnPinned)
+ }
+
+ /** Notifies that the current scene transition is idle. */
+ fun onTransitionIdle() {
+ headsUpRepository.releaseAfterExpansion()
+ }
}
class HeadsUpRowInteractor(repository: HeadsUpRowRepository)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e802076..0e4be8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1721,6 +1721,19 @@
if (mTopHeadsUpRow == null) {
return 0;
}
+ ExpandableNotificationRow row = getTopHeadsUpRow();
+ return row.getPinnedHeadsUpHeight();
+ }
+
+ private int getTopHeadsUpIntrinsicHeight() {
+ if (mTopHeadsUpRow == null) {
+ return 0;
+ }
+ ExpandableNotificationRow row = getTopHeadsUpRow();
+ return row.getIntrinsicHeight();
+ }
+
+ private ExpandableNotificationRow getTopHeadsUpRow() {
ExpandableNotificationRow row = mTopHeadsUpRow;
if (row.isChildInGroup()) {
final NotificationEntry groupSummary =
@@ -1729,7 +1742,7 @@
row = groupSummary.getRow();
}
}
- return row.getPinnedHeadsUpHeight();
+ return row;
}
/**
@@ -2511,7 +2524,7 @@
@Override
public int getTopHeadsUpHeight() {
- return getTopHeadsUpPinnedHeight();
+ return getTopHeadsUpIntrinsicHeight();
}
/**
@@ -5732,7 +5745,7 @@
return mDisallowScrollingInThisMotion;
}
- boolean isBeingDragged() {
+ public boolean isBeingDragged() {
return mIsBeingDragged;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index fa12bb9..693e8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -2053,7 +2053,6 @@
hunWantsIt = mHeadsUpTouchHelper.onInterceptTouchEvent(ev);
if (hunWantsIt) {
mView.startDraggingOnHun();
- mHeadsUpManager.unpinAll(true);
}
}
boolean swipeWantsIt = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index a30b877..950b14d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import android.util.Log
+import com.android.app.tracing.coroutines.flow.filter
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.common.ui.view.onLayoutChanged
import com.android.systemui.dagger.SysUISingleton
@@ -86,6 +87,7 @@
}
launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } }
+ launch { viewModel.shouldResetStackTop.filter { it }.collect { view.setStackTop(0f) } }
launchAndDispose {
view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index a205179..6b95e98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -43,6 +43,7 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
/** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
class NotificationScrollViewModel
@@ -117,6 +118,14 @@
.distinctUntilChanged()
.dumpWhileCollecting("expandFraction")
+ val shouldResetStackTop: Flow<Boolean> =
+ sceneInteractor.transitionState
+ .mapNotNull { state ->
+ state is ObservableTransitionState.Idle && state.currentScene == Scenes.Gone
+ }
+ .distinctUntilChanged()
+ .dumpWhileCollecting("shouldResetStackTop")
+
private operator fun SceneKey.contains(scene: SceneKey) =
sceneInteractor.isSceneInFamily(scene, this)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 2fbb23e..ffa1de7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -16,10 +16,12 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
@@ -30,7 +32,11 @@
import com.android.systemui.util.kotlin.ActivatableFlowDumperImpl
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
/**
* ViewModel used by the Notification placeholders inside the scene container to update the
@@ -40,7 +46,8 @@
@AssistedInject
constructor(
private val interactor: NotificationStackAppearanceInteractor,
- shadeInteractor: ShadeInteractor,
+ private val sceneInteractor: SceneInteractor,
+ private val shadeInteractor: ShadeInteractor,
private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
featureFlags: FeatureFlagsClassic,
dumpManager: DumpManager,
@@ -58,6 +65,20 @@
val isDebugLoggingEnabled: Boolean = SceneContainerFlag.isEnabled
override suspend fun onActivated(): Nothing {
+ coroutineScope {
+ launch {
+ shadeInteractor.isAnyExpanded
+ .filter { it }
+ .collect { headsUpNotificationInteractor.unpinAll(true) }
+ }
+
+ launch {
+ sceneInteractor.transitionState
+ .map { state -> state is ObservableTransitionState.Idle }
+ .filter { it }
+ .collect { headsUpNotificationInteractor.onTransitionIdle() }
+ }
+ }
activateFlowDumper()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
index 8d73983..dc15970 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
@@ -51,7 +51,9 @@
}
removed.forEach { key ->
val row = obtainView(key)
- parentView.generateHeadsUpAnimation(row, /* isHeadsUp= */ false)
+ if (!parentView.isBeingDragged()) {
+ parentView.generateHeadsUpAnimation(row, /* isHeadsUp= */ false)
+ }
row.markHeadsUpSeen()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 544a8a5..720b257 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -35,6 +35,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -60,6 +61,11 @@
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.time.SystemClock;
+import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.flow.StateFlow;
+import kotlinx.coroutines.flow.StateFlowKt;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
@@ -70,11 +76,6 @@
import javax.inject.Inject;
-import kotlinx.coroutines.flow.Flow;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.flow.StateFlow;
-import kotlinx.coroutines.flow.StateFlowKt;
-
/** A implementation of HeadsUpManager for phone. */
@SysUISingleton
public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
@@ -251,6 +252,12 @@
return entry != null && mSystemClock.elapsedRealtime() < entry.mPostTime;
}
+ @Override
+ public void releaseAfterExpansion() {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ onExpandingFinished();
+ }
+
public void onExpandingFinished() {
if (mReleaseOnExpandFinish) {
releaseAllImmediately();
@@ -297,6 +304,11 @@
}
}
+ @Override
+ public void unpinAll(boolean userUnPinned) {
+ super.unpinAll(userUnPinned);
+ }
+
/**
* Notifies that a remote input textbox in notification gets active or inactive.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/DeviceBasedSatelliteTableLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/DeviceBasedSatelliteTableLog.kt
new file mode 100644
index 0000000..a40d510
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/DeviceBasedSatelliteTableLog.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.dagger
+
+import javax.inject.Qualifier
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class DeviceBasedSatelliteTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index a81bfa4..4850049 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -239,6 +239,13 @@
return factory.create("VerboseDeviceBasedSatelliteInputLog", 200)
}
+ @Provides
+ @SysUISingleton
+ @DeviceBasedSatelliteTableLog
+ fun provideDeviceBasedSatelliteTableLog(factory: TableLogBufferFactory): TableLogBuffer {
+ return factory.create("DeviceBasedSatelliteTableLog", 200)
+ }
+
const val FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON =
"FirstMobileSubShowingNetworkTypeIcon"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index cc4d568..26553e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -385,7 +385,15 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean> =
- mobileConnectionsRepo.deviceServiceState.map { it?.isEmergencyOnly ?: false }
+ mobileConnectionsRepo.deviceServiceState
+ .map { it?.isEmergencyOnly ?: false }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
+ columnPrefix = LOGGING_PREFIX,
+ columnName = "deviceEmergencyOnly",
+ initialValue = false,
+ )
/** Vends out new [MobileIconInteractor] for a particular subId */
override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index 03f88c7..f1a444f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -21,7 +21,10 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog
+import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteTableLog
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
@@ -33,6 +36,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -47,6 +51,7 @@
wifiInteractor: WifiInteractor,
@Application scope: CoroutineScope,
@DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer,
+ @DeviceBasedSatelliteTableLog private val tableLog: TableLogBuffer,
) {
/** Must be observed by any UI showing Satellite iconography */
val isSatelliteAllowed =
@@ -55,6 +60,13 @@
} else {
flowOf(false)
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "",
+ columnName = COL_ALLOWED,
+ initialValue = false,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
/** See [SatelliteConnectionState] for relevant states */
@@ -65,6 +77,12 @@
flowOf(SatelliteConnectionState.Off)
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "",
+ initialValue = SatelliteConnectionState.Off,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), SatelliteConnectionState.Off)
/** 0-4 description of the connection strength */
@@ -74,6 +92,13 @@
} else {
flowOf(0)
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "",
+ columnName = COL_LEVEL,
+ initialValue = 0,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
val isSatelliteProvisioned = repo.isSatelliteProvisioned
@@ -82,19 +107,27 @@
wifiInteractor.wifiNetwork.map { it is WifiNetworkModel.Active }
private val allConnectionsOos =
- iconsInteractor.icons.aggregateOver(
- selector = { intr ->
- combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) {
- isInService,
- isEmergencyOnly,
- isNtn ->
- !isInService && !isEmergencyOnly && !isNtn
- }
- },
- defaultValue = true, // no connections == everything is OOS
- ) { isOosAndNotEmergencyAndNotSatellite ->
- isOosAndNotEmergencyAndNotSatellite.all { it }
- }
+ iconsInteractor.icons
+ .aggregateOver(
+ selector = { intr ->
+ combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) {
+ isInService,
+ isEmergencyOnly,
+ isNtn ->
+ !isInService && !isEmergencyOnly && !isNtn
+ }
+ },
+ defaultValue = true, // no connections == everything is OOS
+ ) { isOosAndNotEmergencyAndNotSatellite ->
+ isOosAndNotEmergencyAndNotSatellite.all { it }
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "",
+ columnName = COL_ALL_OOS,
+ initialValue = true,
+ )
/** When all connections are considered OOS, satellite connectivity is potentially valid */
val areAllConnectionsOutOfService =
@@ -122,10 +155,24 @@
} else {
flowOf(false)
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "",
+ columnName = COL_FULL_OOS,
+ initialValue = true,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), true)
companion object {
const val TAG = "DeviceBasedSatelliteInteractor"
+
+ const val COL_LEVEL = "level"
+ const val COL_ALL_OOS = "allConnsOOS"
+ const val COL_ALLOWED = "allowed"
+ // Going to try to optimize for not using too much width on the table here. This information
+ // can be ascertained by checking for the device emergency only in the mobile logs as well
+ const val COL_FULL_OOS = "allOosAndNoEmer"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/shared/model/SatelliteConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/shared/model/SatelliteConnectionState.kt
index bfe2941..905ed730 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/shared/model/SatelliteConnectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/shared/model/SatelliteConnectionState.kt
@@ -26,8 +26,10 @@
import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF
import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE
import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
-enum class SatelliteConnectionState {
+enum class SatelliteConnectionState : Diffable<SatelliteConnectionState> {
// State is unknown or undefined
Unknown,
// Radio is off
@@ -37,7 +39,15 @@
// Radio is connected, aka satellite is available for use
Connected;
+ override fun logDiffs(prevVal: SatelliteConnectionState, row: TableRowLogger) {
+ if (prevVal != this) {
+ row.logChange(COL_CONNECTION_STATE, name)
+ }
+ }
+
companion object {
+ const val COL_CONNECTION_STATE = "connState"
+
// TODO(b/316635648): validate these states. We don't need the level of granularity that
// SatelliteManager gives us.
fun fromModemState(@SatelliteManager.SatelliteModemState modemState: Int) =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 48278d4..199b5b67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -22,9 +22,12 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.res.R
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog
+import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteTableLog
import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
@@ -71,22 +74,34 @@
@Application scope: CoroutineScope,
airplaneModeRepository: AirplaneModeRepository,
@DeviceBasedSatelliteInputLog logBuffer: LogBuffer,
+ @DeviceBasedSatelliteTableLog tableLog: TableLogBuffer,
) : DeviceBasedSatelliteViewModel {
private val shouldShowIcon: Flow<Boolean> =
- interactor.areAllConnectionsOutOfService.flatMapLatest { allOos ->
- if (!allOos) {
- flowOf(false)
- } else {
- combine(
- interactor.isSatelliteAllowed,
- interactor.isSatelliteProvisioned,
- interactor.isWifiActive,
- airplaneModeRepository.isAirplaneMode
- ) { isSatelliteAllowed, isSatelliteProvisioned, isWifiActive, isAirplaneMode ->
- isSatelliteAllowed && isSatelliteProvisioned && !isWifiActive && !isAirplaneMode
+ interactor.areAllConnectionsOutOfService
+ .flatMapLatest { allOos ->
+ if (!allOos) {
+ flowOf(false)
+ } else {
+ combine(
+ interactor.isSatelliteAllowed,
+ interactor.isSatelliteProvisioned,
+ interactor.isWifiActive,
+ airplaneModeRepository.isAirplaneMode
+ ) { isSatelliteAllowed, isSatelliteProvisioned, isWifiActive, isAirplaneMode ->
+ isSatelliteAllowed &&
+ isSatelliteProvisioned &&
+ !isWifiActive &&
+ !isAirplaneMode
+ }
}
}
- }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "vm",
+ columnName = COL_VISIBLE_CONDITION,
+ initialValue = false,
+ )
// This adds a 10 seconds delay before showing the icon
private val shouldActuallyShowIcon: StateFlow<Boolean> =
@@ -106,6 +121,13 @@
flowOf(false)
}
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "vm",
+ columnName = COL_VISIBLE,
+ initialValue = false,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val icon: StateFlow<Icon?> =
@@ -163,5 +185,8 @@
companion object {
private const val TAG = "DeviceBasedSatelliteViewModel"
private val DELAY_DURATION = 10.seconds
+
+ const val COL_VISIBLE_CONDITION = "visCondition"
+ const val COL_VISIBLE = "visible"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
index 3fffd9f..8b50f84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
@@ -33,6 +33,9 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.android.systemui.common.ui.compose.Icon
@@ -58,7 +61,9 @@
onClick = viewModel.onClick,
onLongClick = viewModel.onLongClick
)
- .padding(20.dp),
+ .padding(20.dp)
+ .semantics(mergeDescendants = true) {}
+ .clearAndSetSemantics { contentDescription = viewModel.contentDescription },
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement =
Arrangement.spacedBy(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
index 7c1cb6a..921d79b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
@@ -28,6 +28,7 @@
val icon: Icon,
val text: String,
val subtext: String,
+ val contentDescription: String,
val enabled: Boolean,
val onClick: () -> Unit,
val onLongClick: () -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 5772099..38bade0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -89,8 +89,9 @@
ModeTileViewModel(
id = mode.id,
icon = zenModeInteractor.getModeIcon(mode, context),
- text = mode.rule.name,
+ text = mode.name,
subtext = getTileSubtext(mode),
+ contentDescription = getTileContentDescription(mode),
enabled = mode.isActive,
onClick = {
if (!mode.rule.isEnabled) {
@@ -135,9 +136,35 @@
return context.resources.getString(R.string.zen_mode_no_manual_invocation)
}
- val on = context.resources.getString(R.string.zen_mode_on)
- val off = context.resources.getString(R.string.zen_mode_off)
- return mode.getDynamicDescription(context) ?: if (mode.isActive) on else off
+ val modeSubtext = mode.getDynamicDescription(context)
+ return if (mode.isActive) {
+ if (modeSubtext != null) {
+ context.getString(R.string.zen_mode_on_with_details, modeSubtext)
+ } else {
+ context.getString(R.string.zen_mode_on)
+ }
+ } else {
+ modeSubtext ?: context.getString(R.string.zen_mode_off)
+ }
+ }
+
+ private fun getTileContentDescription(mode: ZenMode): String {
+ return buildList {
+ add(mode.name)
+ if (!mode.rule.isEnabled) {
+ add(context.getString(R.string.zen_mode_set_up))
+ } else if (!mode.rule.isManualInvocationAllowed && !mode.isActive) {
+ add(context.getString(R.string.zen_mode_no_manual_invocation))
+ } else {
+ add(
+ context.getString(
+ if (mode.isActive) R.string.zen_mode_on else R.string.zen_mode_off
+ )
+ )
+ mode.getDynamicDescription(context)?.let { add(it) }
+ }
+ }
+ .joinToString(separator = "\n")
}
private fun makeZenModeDialog(): Dialog {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 055671c..28ac2c0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -28,7 +28,6 @@
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -63,9 +62,7 @@
/**
* Collect information for the given [flow], calling [consumer] for each emitted event. Defaults to
* [LifeCycle.State.CREATED] to better align with legacy ViewController usage of attaching listeners
- * during onViewAttached() and removing during onViewRemoved().
- *
- * @return a disposable handle in order to cancel the flow in the future.
+ * during onViewAttached() and removing during onViewRemoved()
*/
@JvmOverloads
fun <T> collectFlow(
@@ -74,8 +71,8 @@
consumer: Consumer<T>,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
state: Lifecycle.State = Lifecycle.State.CREATED,
-): DisposableHandle {
- return view.repeatWhenAttached(coroutineContext) {
+) {
+ view.repeatWhenAttached(coroutineContext) {
repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 2468449..eb91518 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -695,11 +695,10 @@
addRow(AudioManager.STREAM_MUSIC,
R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true);
if (!AudioSystem.isSingleVolume(mContext)) {
-
addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume,
R.drawable.ic_ring_volume_off, true, false);
-
-
+ addRow(AudioManager.STREAM_NOTIFICATION, R.drawable.ic_volume_ringer,
+ R.drawable.ic_volume_off, true, false);
addRow(STREAM_ALARM,
R.drawable.ic_alarm, R.drawable.ic_volume_alarm_mute, true, false);
addRow(AudioManager.STREAM_VOICE_CALL,
@@ -1994,7 +1993,7 @@
: R.drawable.ic_volume_media_bt;
}
} else if (isStreamMuted(ss)) {
- iconRes = ss.muted ? R.drawable.ic_volume_media_off : row.iconMuteRes;
+ iconRes = (ss.muted && isTv()) ? R.drawable.ic_volume_media_off : row.iconMuteRes;
} else {
iconRes = mShowLowMediaVolumeIcon && ss.level * 2 < (ss.levelMax + ss.levelMin)
? R.drawable.ic_volume_media_low : row.iconRes;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index a18d272..5600b87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -711,16 +711,6 @@
}
@Test
- public void testDestroy_cleansUpHandler() {
- final TouchHandler touchHandler = createTouchHandler();
-
- final Environment environment = new Environment(Stream.of(touchHandler)
- .collect(Collectors.toCollection(HashSet::new)), mKosmos);
- environment.destroyMonitor();
- verify(touchHandler).onDestroy();
- }
-
- @Test
public void testLastSessionPop_createsNewInputSession() {
final TouchHandler touchHandler = createTouchHandler();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
new file mode 100644
index 0000000..432f7af
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
@@ -0,0 +1,164 @@
+/*
+ * 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.inputdevice.tutorial.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
+import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
+import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor.TutorialType
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.touchpad.data.repository.FakeTouchpadRepository
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.hours
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TutorialSchedulerInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: TutorialSchedulerInteractor
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private lateinit var dataStoreScope: CoroutineScope
+ private val keyboardRepository = FakeKeyboardRepository()
+ private val touchpadRepository = FakeTouchpadRepository()
+ private lateinit var schedulerRepository: TutorialSchedulerRepository
+
+ @Before
+ fun setup() {
+ dataStoreScope = CoroutineScope(Dispatchers.Unconfined)
+ schedulerRepository =
+ TutorialSchedulerRepository(
+ context,
+ dataStoreScope,
+ dataStoreName = "TutorialSchedulerInteractorTest"
+ )
+ underTest =
+ TutorialSchedulerInteractor(
+ testScope.backgroundScope,
+ keyboardRepository,
+ touchpadRepository,
+ schedulerRepository
+ )
+ underTest.start()
+ }
+
+ @After
+ fun clear() {
+ runBlocking { schedulerRepository.clearDataStore() }
+ dataStoreScope.cancel()
+ }
+
+ @Test
+ fun connectKeyboard_delayElapse_launchForKeyboard() =
+ testScope.runTest {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ advanceTimeBy(LAUNCH_DELAY)
+ assertLaunch(TutorialType.KEYBOARD)
+ }
+
+ @Test
+ fun connectBothDevices_delayElapse_launchForBoth() =
+ testScope.runTest {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ advanceTimeBy(LAUNCH_DELAY)
+ assertLaunch(TutorialType.BOTH)
+ }
+
+ @Test
+ fun connectBothDevice_delayNotElapse_launchNothing() =
+ testScope.runTest {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
+ assertLaunch(TutorialType.NONE)
+ }
+
+ @Test
+ fun nothingConnect_delayElapse_launchNothing() =
+ testScope.runTest {
+ keyboardRepository.setIsAnyKeyboardConnected(false)
+ touchpadRepository.setIsAnyTouchpadConnected(false)
+ advanceTimeBy(LAUNCH_DELAY)
+ assertLaunch(TutorialType.NONE)
+ }
+
+ @Test
+ fun connectKeyboard_thenTouchpad_delayElapse_launchForBoth() =
+ testScope.runTest {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ advanceTimeBy(REMAINING_TIME)
+ assertLaunch(TutorialType.BOTH)
+ }
+
+ @Test
+ fun connectKeyboard_thenTouchpad_removeKeyboard_delayElapse_launchNothing() =
+ testScope.runTest {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ keyboardRepository.setIsAnyKeyboardConnected(false)
+ advanceTimeBy(REMAINING_TIME)
+ assertLaunch(TutorialType.NONE)
+ }
+
+ // TODO: likely to be changed after we update TutorialSchedulerInteractor.launchTutorial
+ private suspend fun assertLaunch(tutorialType: TutorialType) {
+ when (tutorialType) {
+ TutorialType.KEYBOARD -> {
+ assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isTrue()
+ assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isFalse()
+ }
+ TutorialType.TOUCHPAD -> {
+ assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isFalse()
+ assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isTrue()
+ }
+ TutorialType.BOTH -> {
+ assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isTrue()
+ assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isTrue()
+ }
+ TutorialType.NONE -> {
+ assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isFalse()
+ assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isFalse()
+ }
+ }
+ }
+
+ companion object {
+ private val LAUNCH_DELAY = 72.hours
+ private val A_SHORT_PERIOD_OF_TIME = 2.hours
+ private val REMAINING_TIME = 70.hours
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
index cd0390e..dbb77d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
@@ -70,6 +70,7 @@
wifiInteractor,
testScope.backgroundScope,
FakeLogBuffer.Factory.create(),
+ mock(),
)
}
@@ -113,6 +114,7 @@
wifiInteractor,
testScope.backgroundScope,
FakeLogBuffer.Factory.create(),
+ mock(),
)
val latest by collectLastValue(underTest.isSatelliteAllowed)
@@ -161,6 +163,7 @@
wifiInteractor,
testScope.backgroundScope,
FakeLogBuffer.Factory.create(),
+ mock(),
)
val latest by collectLastValue(underTest.connectionState)
@@ -217,6 +220,7 @@
wifiInteractor,
testScope.backgroundScope,
FakeLogBuffer.Factory.create(),
+ mock(),
)
val latest by collectLastValue(underTest.signalStrength)
@@ -535,6 +539,7 @@
wifiInteractor,
testScope.backgroundScope,
FakeLogBuffer.Factory.create(),
+ mock(),
)
val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index 64b07fc..c3cc33f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -33,7 +33,6 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
-import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlin.time.Duration.Companion.seconds
@@ -44,6 +43,7 @@
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -73,6 +73,7 @@
wifiInteractor,
testScope.backgroundScope,
FakeLogBuffer.Factory.create(),
+ mock(),
)
underTest =
@@ -82,6 +83,7 @@
testScope.backgroundScope,
airplaneModeRepository,
FakeLogBuffer.Factory.create(),
+ mock(),
)
}
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 9dd3e53..6e39365 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -102,6 +102,7 @@
import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.systemui.SysuiTestCase;
@@ -168,7 +169,6 @@
import com.android.wm.shell.bubbles.BubbleDataRepository;
import com.android.wm.shell.bubbles.BubbleEducationController;
import com.android.wm.shell.bubbles.BubbleEntry;
-import com.android.wm.shell.bubbles.BubbleExpandedViewManager;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubbleStackView;
@@ -381,6 +381,9 @@
@Before
public void setUp() throws Exception {
+ // Make sure ProtoLog is initialized before any logging occurs.
+ ProtoLog.init();
+
MockitoAnnotations.initMocks(this);
PhysicsAnimatorTestUtils.prepareForTest();
@@ -1404,7 +1407,6 @@
.thenReturn(userContext);
BubbleViewInfoTask.BubbleViewInfo info = BubbleViewInfoTask.BubbleViewInfo.populate(context,
- BubbleExpandedViewManager.fromBubbleController(mBubbleController),
() -> new BubbleTaskView(mock(TaskView.class), mock(Executor.class)),
mPositioner,
mBubbleController.getStackView(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/domain/GestureInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/domain/GestureInteractorKosmos.kt
index 658aaa6..1d2439c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/domain/GestureInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/domain/GestureInteractorKosmos.kt
@@ -16,12 +16,23 @@
package com.android.systemui.keyguard.gesture.domain
-import com.android.systemui.keyguard.gesture.data.gestureRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.navigationbar.gestural.data.gestureRepository
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor
+import com.android.systemui.shared.system.activityManagerWrapper
+import com.android.systemui.shared.system.taskStackChangeListeners
val Kosmos.gestureInteractor: GestureInteractor by
Kosmos.Fixture {
- GestureInteractor(gestureRepository = gestureRepository, scope = applicationCoroutineScope)
+ GestureInteractor(
+ gestureRepository = gestureRepository,
+ mainDispatcher = testDispatcher,
+ backgroundCoroutineContext = backgroundCoroutineContext,
+ scope = applicationCoroutineScope,
+ activityManagerWrapper = activityManagerWrapper,
+ taskStackChangeListeners = taskStackChangeListeners
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
index c0bb9a6..90cd8c7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
@@ -16,15 +16,27 @@
package com.android.systemui.lifecycle
+import androidx.compose.runtime.getValue
import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flowOf
class FakeSysUiViewModel(
private val onActivation: () -> Unit = {},
private val onDeactivation: () -> Unit = {},
+ private val upstreamFlow: Flow<Boolean> = flowOf(true),
+ private val upstreamStateFlow: StateFlow<Boolean> = MutableStateFlow(true).asStateFlow(),
) : SysUiViewModel() {
+
var activationCount = 0
var cancellationCount = 0
+ val stateBackedByFlow: Boolean by hydratedStateOf(initialValue = true, source = upstreamFlow)
+ val stateBackedByStateFlow: Boolean by hydratedStateOf(source = upstreamStateFlow)
+
override suspend fun onActivated(): Nothing {
activationCount++
onActivation()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/data/GestureRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/gestural/data/GestureRepositoryKosmos.kt
similarity index 94%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/data/GestureRepositoryKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/gestural/data/GestureRepositoryKosmos.kt
index 9bd346e..55ce43a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/data/GestureRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/gestural/data/GestureRepositoryKosmos.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.gesture.data
+package com.android.systemui.navigationbar.gestural.data
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
index 7e8f1a9..1fa6236 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
@@ -44,6 +44,14 @@
// do nothing
}
+ override fun unpinAll(userUnPinned: Boolean) {
+ // do nothing
+ }
+
+ override fun releaseAfterExpansion() {
+ // do nothing
+ }
+
fun setNotifications(notifications: List<HeadsUpRowRepository>) {
this.orderedHeadsUpRows.value = notifications.toList()
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index 3247525..634354b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
@@ -27,6 +28,7 @@
val Kosmos.notificationsPlaceholderViewModel by Fixture {
NotificationsPlaceholderViewModel(
interactor = notificationStackAppearanceInteractor,
+ sceneInteractor = sceneInteractor,
shadeInteractor = shadeInteractor,
headsUpNotificationInteractor = headsUpNotificationInteractor,
featureFlags = featureFlagsClassic,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/data/repository/FakeTouchpadRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/data/repository/FakeTouchpadRepository.kt
new file mode 100644
index 0000000..1ec6bbf
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/data/repository/FakeTouchpadRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.touchpad.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeTouchpadRepository : TouchpadRepository {
+
+ private val _isAnyTouchpadConnected = MutableStateFlow(false)
+ override val isAnyTouchpadConnected: Flow<Boolean> = _isAnyTouchpadConnected
+
+ fun setIsAnyTouchpadConnected(connected: Boolean) {
+ _isAnyTouchpadConnected.value = connected
+ }
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
index f30e770..954651d 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
@@ -18,7 +18,6 @@
import static android.app.appfunctions.flags.Flags.enableAppFunctionManager;
-import android.app.appfunctions.IAppFunctionManager;
import android.content.Context;
import com.android.server.SystemService;
@@ -27,19 +26,17 @@
* Service that manages app functions.
*/
public class AppFunctionManagerService extends SystemService {
+ private final AppFunctionManagerServiceImpl mServiceImpl;
public AppFunctionManagerService(Context context) {
super(context);
+ mServiceImpl = new AppFunctionManagerServiceImpl(context);
}
@Override
public void onStart() {
if (enableAppFunctionManager()) {
- publishBinderService(Context.APP_FUNCTION_SERVICE, new AppFunctionManagerStub());
+ publishBinderService(Context.APP_FUNCTION_SERVICE, mServiceImpl);
}
}
-
- private static class AppFunctionManagerStub extends IAppFunctionManager.Stub {
-
- }
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
new file mode 100644
index 0000000..e2167a8
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appfunctions;
+
+import android.annotation.NonNull;
+import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.app.appfunctions.IAppFunctionManager;
+import android.app.appfunctions.IAppFunctionService;
+import android.app.appfunctions.IExecuteAppFunctionCallback;
+import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
+import android.app.appfunctions.ServiceCallHelper;
+import android.app.appfunctions.ServiceCallHelper.RunServiceCallCallback;
+import android.app.appfunctions.ServiceCallHelper.ServiceUsageCompleteListener;
+import android.app.appfunctions.ServiceCallHelperImpl;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implementation of the AppFunctionManagerService.
+ */
+public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
+ private static final String TAG = AppFunctionManagerServiceImpl.class.getSimpleName();
+ private final ServiceCallHelper<IAppFunctionService> mExternalServiceCallHelper;
+ private final CallerValidator mCallerValidator;
+ private final ServiceHelper mInternalServiceHelper;
+
+ public AppFunctionManagerServiceImpl(@NonNull Context context) {
+ this(new ServiceCallHelperImpl<>(
+ context,
+ IAppFunctionService.Stub::asInterface, new ThreadPoolExecutor(
+ /*corePoolSize=*/ Runtime.getRuntime().availableProcessors(),
+ /*maxConcurrency=*/ Runtime.getRuntime().availableProcessors(),
+ /*keepAliveTime=*/ 0L,
+ /*unit=*/ TimeUnit.SECONDS,
+ /*workQueue=*/ new LinkedBlockingQueue<>())),
+ new CallerValidatorImpl(context),
+ new ServiceHelperImpl(context));
+ }
+
+ @VisibleForTesting
+ AppFunctionManagerServiceImpl(ServiceCallHelper<IAppFunctionService> serviceCallHelper,
+ CallerValidator apiValidator,
+ ServiceHelper appFunctionInternalServiceHelper) {
+ mExternalServiceCallHelper = Objects.requireNonNull(serviceCallHelper);
+ mCallerValidator = Objects.requireNonNull(apiValidator);
+ mInternalServiceHelper =
+ Objects.requireNonNull(appFunctionInternalServiceHelper);
+ }
+
+ @Override
+ public void executeAppFunction(
+ @NonNull ExecuteAppFunctionAidlRequest requestInternal,
+ @NonNull IExecuteAppFunctionCallback executeAppFunctionCallback) {
+ Objects.requireNonNull(requestInternal);
+ Objects.requireNonNull(executeAppFunctionCallback);
+
+ final SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback =
+ new SafeOneTimeExecuteAppFunctionCallback(executeAppFunctionCallback);
+
+ String validatedCallingPackage = mCallerValidator
+ .validateCallingPackage(requestInternal.getCallingPackage());
+ UserHandle targetUser = mCallerValidator.verifyTargetUserHandle(
+ requestInternal.getUserHandle(), validatedCallingPackage);
+
+ // TODO(b/354956319): Add and honor the new enterprise policies.
+ if (mCallerValidator.isUserOrganizationManaged(targetUser)) {
+ safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
+ ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+ "Cannot run on a device with a device owner or from the managed profile."
+ ).build());
+ return;
+ }
+
+ String targetPackageName = requestInternal.getClientRequest().getTargetPackageName();
+ if (TextUtils.isEmpty(targetPackageName)) {
+ safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
+ ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT,
+ "Target package name cannot be empty."
+ ).build());
+ return;
+ }
+
+ if (!mCallerValidator.verifyCallerCanExecuteAppFunction(
+ validatedCallingPackage, targetPackageName)) {
+ throw new SecurityException("Caller does not have permission to execute the app "
+ + "function.");
+ }
+
+ Intent serviceIntent = mInternalServiceHelper.resolveAppFunctionService(
+ targetPackageName,
+ targetUser);
+ if (serviceIntent == null) {
+ safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
+ ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+ "Cannot find the target service."
+ ).build());
+ return;
+ }
+
+ // TODO(b/357551503): Offload call to async executor.
+ bindAppFunctionServiceUnchecked(requestInternal, serviceIntent, targetUser,
+ safeExecuteAppFunctionCallback,
+ /*bindFlags=*/ Context.BIND_AUTO_CREATE,
+ // TODO(b/357551503): Make timeout configurable.
+ /*timeoutInMillis=*/ 30_000L);
+ }
+
+ private void bindAppFunctionServiceUnchecked(
+ @NonNull ExecuteAppFunctionAidlRequest requestInternal,
+ @NonNull Intent serviceIntent, @NonNull UserHandle targetUser,
+ @NonNull SafeOneTimeExecuteAppFunctionCallback
+ safeExecuteAppFunctionCallback,
+ int bindFlags, long timeoutInMillis) {
+ boolean bindServiceResult = mExternalServiceCallHelper.runServiceCall(
+ serviceIntent,
+ bindFlags,
+ timeoutInMillis,
+ targetUser,
+ /*timeOutCallback=*/ new RunServiceCallCallback<IAppFunctionService>() {
+ @Override
+ public void onServiceConnected(@NonNull IAppFunctionService service,
+ @NonNull ServiceUsageCompleteListener
+ serviceUsageCompleteListener) {
+ try {
+ service.executeAppFunction(
+ requestInternal.getClientRequest(),
+ new IExecuteAppFunctionCallback.Stub() {
+ @Override
+ public void onResult(ExecuteAppFunctionResponse response) {
+ safeExecuteAppFunctionCallback.onResult(response);
+ serviceUsageCompleteListener.onCompleted();
+ }
+ }
+ );
+ } catch (Exception e) {
+ safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
+ .Builder(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+ e.getMessage()).build());
+ serviceUsageCompleteListener.onCompleted();
+ }
+ }
+
+ @Override
+ public void onFailedToConnect() {
+ Slog.e(TAG, "Failed to connect to service");
+ safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
+ .Builder(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+ "Failed to connect to AppFunctionService").build());
+ }
+
+ @Override
+ public void onTimedOut() {
+ Slog.e(TAG, "Timed out");
+ safeExecuteAppFunctionCallback.onResult(
+ new ExecuteAppFunctionResponse.Builder(
+ ExecuteAppFunctionResponse.RESULT_TIMED_OUT,
+ "Binding to AppFunctionService timed out."
+ ).build());
+ }
+ }
+ );
+
+ if (!bindServiceResult) {
+ Slog.e(TAG, "Failed to bind to the AppFunctionService");
+ safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
+ ExecuteAppFunctionResponse.RESULT_TIMED_OUT,
+ "Failed to bind the AppFunctionService."
+ ).build());
+ }
+ }
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
new file mode 100644
index 0000000..9bd633f
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
@@ -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.server.appfunctions;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+
+/**
+ * Interface for validating that the caller has the correct privilege to call an AppFunctionManager
+ * API.
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public interface CallerValidator {
+ // TODO(b/357551503): Should we verify NOT instant app?
+ // TODO(b/357551503): Verify that user have been unlocked.
+
+ /**
+ * This method is used to validate that the calling package reported in the request is the
+ * same as the binder calling identity.
+ *
+ * @param claimedCallingPackage The package name of the caller.
+ * @return The package name of the caller.
+ * @throws SecurityException if the package name and uid don't match.
+ */
+ String validateCallingPackage(@NonNull String claimedCallingPackage);
+
+ /**
+ * Validates that the caller can invoke an AppFunctionManager API in the provided
+ * target user space.
+ *
+ * @param targetUserHandle The user which the caller is requesting to execute as.
+ * @param claimedCallingPackage The package name of the caller.
+ * @return The user handle that the call should run as. Will always be a concrete user.
+ * @throws IllegalArgumentException if the target user is a special user.
+ * @throws SecurityException if caller trying to interact across users without {@link
+ * Manifest.permission#INTERACT_ACROSS_USERS_FULL}
+ */
+ UserHandle verifyTargetUserHandle(@NonNull UserHandle targetUserHandle,
+ @NonNull String claimedCallingPackage);
+
+ /**
+ * Validates that the caller can execute the specified app function.
+ * <p>
+ * The caller can execute if the app function's package name is the same as the caller's package
+ * or the caller has either {@link Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or
+ * {@link Manifest.permission.EXECUTE_APP_FUNCTIONS} granted. In some cases, app functions
+ * can still opt-out of caller having {@link Manifest.permission.EXECUTE_APP_FUNCTIONS}.
+ *
+ * @param callerPackageName The calling package (as previously validated).
+ * @param targetPackageName The package that owns the app function to execute.
+ * @return Whether the caller can execute the specified app function.
+ */
+ boolean verifyCallerCanExecuteAppFunction(
+ @NonNull String callerPackageName, @NonNull String targetPackageName);
+
+ /**
+ * Checks if the user is organization managed.
+ *
+ * @param targetUser The user which the caller is requesting to execute as.
+ * @return Whether the user is organization managed.
+ */
+ boolean isUserOrganizationManaged(@NonNull UserHandle targetUser);
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
new file mode 100644
index 0000000..7cd660d
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appfunctions;
+
+import android.Manifest;
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.util.Objects;
+
+/* Validates that caller has the correct privilege to call an AppFunctionManager Api. */
+class CallerValidatorImpl implements CallerValidator {
+ private final Context mContext;
+
+
+ CallerValidatorImpl(@NonNull Context context) {
+ mContext = Objects.requireNonNull(context);
+ }
+
+ @Override
+ @NonNull
+ @BinderThread
+ public String validateCallingPackage(@NonNull String claimedCallingPackage) {
+ int callingUid = Binder.getCallingUid();
+ final long callingIdentityToken = Binder.clearCallingIdentity();
+ try {
+ validateCallingPackageInternal(callingUid, claimedCallingPackage);
+ return claimedCallingPackage;
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentityToken);
+ }
+ }
+
+ @Override
+ @NonNull
+ @BinderThread
+ public UserHandle verifyTargetUserHandle(@NonNull UserHandle targetUserHandle,
+ @NonNull String claimedCallingPackage) {
+ int callingPid = Binder.getCallingPid();
+ int callingUid = Binder.getCallingUid();
+ final long callingIdentityToken = Binder.clearCallingIdentity();
+ try {
+ return handleIncomingUser(claimedCallingPackage, targetUserHandle,
+ callingPid, callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentityToken);
+ }
+ }
+
+ @Override
+ @BinderThread
+ @RequiresPermission(anyOf = {Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
+ Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional = true)
+ // TODO(b/360864791): Add and honor apps that opt-out from EXECUTE_APP_FUNCTIONS caller.
+ public boolean verifyCallerCanExecuteAppFunction(
+ @NonNull String callerPackageName, @NonNull String targetPackageName) {
+ int pid = Binder.getCallingPid();
+ int uid = Binder.getCallingUid();
+ boolean hasExecutionPermission = mContext.checkPermission(
+ Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, pid, uid)
+ == PackageManager.PERMISSION_GRANTED;
+ boolean hasTrustedExecutionPermission = mContext.checkPermission(
+ Manifest.permission.EXECUTE_APP_FUNCTIONS, pid, uid)
+ == PackageManager.PERMISSION_GRANTED;
+ boolean isSamePackage = callerPackageName.equals(targetPackageName);
+ return hasExecutionPermission || hasTrustedExecutionPermission || isSamePackage;
+ }
+
+ @Override
+ @BinderThread
+ public boolean isUserOrganizationManaged(@NonNull UserHandle targetUser) {
+ final long callingIdentityToken = Binder.clearCallingIdentity();
+ try {
+ if (Objects.requireNonNull(mContext.getSystemService(DevicePolicyManager.class))
+ .isDeviceManaged()) {
+ return true;
+ }
+ return Objects.requireNonNull(mContext.getSystemService(UserManager.class))
+ .isManagedProfile(targetUser.getIdentifier());
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentityToken);
+ }
+ }
+
+ /**
+ * Helper for dealing with incoming user arguments to system service calls.
+ *
+ * <p>Takes care of checking permissions and if the target is special user, this method will
+ * simply throw.
+ *
+ * @param callingPackageName The package name of the caller.
+ * @param targetUserHandle The user which the caller is requesting to execute as.
+ * @param callingPid The actual pid of the caller as determined by Binder.
+ * @param callingUid The actual uid of the caller as determined by Binder.
+ * @return the user handle that the call should run as. Will always be a concrete user.
+ * @throws IllegalArgumentException if the target user is a special user.
+ * @throws SecurityException if caller trying to interact across user without {@link
+ * Manifest.permission#INTERACT_ACROSS_USERS_FULL}
+ */
+ @NonNull
+ private UserHandle handleIncomingUser(
+ @NonNull String callingPackageName,
+ @NonNull UserHandle targetUserHandle,
+ int callingPid,
+ int callingUid) {
+ UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid);
+ if (callingUserHandle.equals(targetUserHandle)) {
+ return targetUserHandle;
+ }
+
+ // Duplicates UserController#ensureNotSpecialUser
+ if (targetUserHandle.getIdentifier() < 0) {
+ throw new IllegalArgumentException(
+ "Call does not support special user " + targetUserHandle);
+ }
+
+ if (mContext.checkPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingPid, callingUid)
+ == PackageManager.PERMISSION_GRANTED) {
+ try {
+ mContext.createPackageContextAsUser(
+ callingPackageName, /* flags= */ 0, targetUserHandle);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new SecurityException(
+ "Package: "
+ + callingPackageName
+ + " haven't installed for user "
+ + targetUserHandle.getIdentifier());
+ }
+ return targetUserHandle;
+ }
+ throw new SecurityException(
+ "Permission denied while calling from uid "
+ + callingUid
+ + " with "
+ + targetUserHandle
+ + "; Requires permission: "
+ + Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ }
+
+ /**
+ * Checks that the caller's supposed package name matches the uid making the call.
+ *
+ * @throws SecurityException if the package name and uid don't match.
+ */
+ private void validateCallingPackageInternal(
+ int actualCallingUid, @NonNull String claimedCallingPackage) {
+ UserHandle callingUserHandle = UserHandle.getUserHandleForUid(actualCallingUid);
+ Context actualCallingUserContext = mContext.createContextAsUser(
+ callingUserHandle, /* flags= */ 0);
+ int claimedCallingUid =
+ getPackageUid(actualCallingUserContext, claimedCallingPackage);
+ if (claimedCallingUid != actualCallingUid) {
+ throw new SecurityException(
+ "Specified calling package ["
+ + claimedCallingPackage
+ + "] does not match the calling uid "
+ + actualCallingUid);
+ }
+ }
+
+ /**
+ * Finds the UID of the {@code packageName} in the given {@code context}. Returns {@link
+ * Process#INVALID_UID} if unable to find the UID.
+ */
+ private int getPackageUid(@NonNull Context context, @NonNull String packageName) {
+ try {
+ return context.getPackageManager().getPackageUid(packageName, /* flags= */ 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return Process.INVALID_UID;
+ }
+ }
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/ServiceHelper.java b/services/appfunctions/java/com/android/server/appfunctions/ServiceHelper.java
new file mode 100644
index 0000000..6cd87d3
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/ServiceHelper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appfunctions;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Helper interface for AppFunctionService.
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public interface ServiceHelper {
+ /**
+ * Resolves the AppFunctionService for the target package.
+ *
+ * @param targetPackageName The package name of the target.
+ * @param targetUser The user which the caller is requesting to execute as.
+ * @return The intent to bind to the target service.
+ */
+ Intent resolveAppFunctionService(@NonNull String targetPackageName,
+ @NonNull UserHandle targetUser);
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/ServiceHelperImpl.java b/services/appfunctions/java/com/android/server/appfunctions/ServiceHelperImpl.java
new file mode 100644
index 0000000..e49fba5
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/ServiceHelperImpl.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appfunctions;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.app.appfunctions.AppFunctionService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.UserHandle;
+
+import java.util.Objects;
+
+class ServiceHelperImpl implements ServiceHelper {
+ private final Context mContext;
+
+ // TODO(b/357551503): Keep track of unlocked users.
+
+ ServiceHelperImpl(@NonNull Context context) {
+ mContext = Objects.requireNonNull(context);
+ }
+
+ @Override
+ public Intent resolveAppFunctionService(@NonNull String targetPackageName,
+ @NonNull UserHandle targetUser) {
+ Intent serviceIntent = new Intent(AppFunctionService.SERVICE_INTERFACE);
+ serviceIntent.setPackage(targetPackageName);
+ ResolveInfo resolveInfo = mContext.createContextAsUser(targetUser, /* flags= */ 0)
+ .getPackageManager().resolveService(serviceIntent, 0);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ return null;
+ }
+
+ ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ if (!Manifest.permission.BIND_APP_FUNCTION_SERVICE.equals(
+ serviceInfo.permission)) {
+ return null;
+ }
+ serviceIntent.setComponent(
+ new ComponentName(serviceInfo.packageName, serviceInfo.name));
+
+ return serviceIntent;
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 1be352e..0827f2a 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -24,7 +24,7 @@
import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
-import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_BLOCKED_ACTIVITY;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
@@ -237,11 +237,11 @@
@Override
public void onActivityLaunchBlocked(int displayId,
- @NonNull ComponentName componentName, @UserIdInt int userId,
+ @NonNull ComponentName componentName, @NonNull UserHandle user,
@Nullable IntentSender intentSender) {
try {
mActivityListener.onActivityLaunchBlocked(
- displayId, componentName, userId, intentSender);
+ displayId, componentName, user, intentSender);
} catch (RemoteException e) {
Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
}
@@ -736,7 +736,7 @@
}
}
break;
- case POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR:
+ case POLICY_TYPE_BLOCKED_ACTIVITY:
if (android.companion.virtualdevice.flags.Flags.activityControlApi()) {
synchronized (mVirtualDeviceLock) {
mDevicePolicies.put(policyType, devicePolicy);
@@ -1371,8 +1371,7 @@
mActivityListenerAdapter.onActivityLaunchBlocked(
displayId,
activityInfo.getComponentName(),
- UserHandle.getUserHandleForUid(
- activityInfo.applicationInfo.uid).getIdentifier(),
+ UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid),
intentSender);
}
}
@@ -1388,7 +1387,7 @@
return true;
}
// Do not show the dialog if disabled by policy.
- return getDevicePolicy(POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR) == DEVICE_POLICY_DEFAULT;
+ return getDevicePolicy(POLICY_TYPE_BLOCKED_ACTIVITY) == DEVICE_POLICY_DEFAULT;
}
private void onSecureWindowShown(int displayId, int uid) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 6333159..4aac7a0 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1347,7 +1347,6 @@
bus.getStatsDuration(),
bus.getDischargePercentage(),
bus.getDischargeDurationMs());
-
if (DBG) {
Slog.d(TAG, "BatteryUsageStats dump = " + bus);
}
@@ -1357,45 +1356,25 @@
final float totalDeviceConsumedPowerMah = (float) deviceConsumer.getConsumedPower();
- for (@BatteryConsumer.PowerComponent int componentId = 0;
- componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
- componentId++) {
+ for (@BatteryConsumer.PowerComponentId int componentIndex :
+ deviceConsumer.getPowerComponentIds()) {
for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
- if (!addStatsForPredefinedComponent(
+ if (!addStatsForPowerComponent(
data,
sessionInfo,
Process.INVALID_UID,
processState,
totalDeviceConsumedPowerMah,
+ 0,
deviceConsumer,
- componentId)) {
+ componentIndex)) {
return StatsManager.PULL_SUCCESS;
}
}
}
- final int customPowerComponentCount = deviceConsumer.getCustomPowerComponentCount();
- for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
- componentId
- < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
- + customPowerComponentCount;
- componentId++) {
-
- if (!addStatsForCustomComponent(
- data,
- sessionInfo,
- Process.INVALID_UID,
- BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
- 0,
- totalDeviceConsumedPowerMah,
- deviceConsumer,
- componentId)) {
- return StatsManager.PULL_SUCCESS;
- }
- }
-
final List<UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers();
uidConsumers.sort(
Comparator.<BatteryConsumer>comparingDouble(BatteryConsumer::getConsumedPower)
@@ -1406,47 +1385,22 @@
final int uid = uidConsumer.getUid();
final float totalConsumedPowerMah = (float) uidConsumer.getConsumedPower();
- for (@BatteryConsumer.PowerComponent int componentId = 0;
- componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
- componentId++) {
+ for (@BatteryConsumer.PowerComponentId int componentIndex :
+ uidConsumer.getPowerComponentIds()) {
for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
- if (!addStatsForPredefinedComponent(
+ long timeInProcessStateMs = uidConsumer.getTimeInProcessStateMs(
+ processState);
+ if (!addStatsForPowerComponent(
data,
sessionInfo,
uid,
processState,
totalConsumedPowerMah,
+ timeInProcessStateMs,
uidConsumer,
- componentId)) {
- return StatsManager.PULL_SUCCESS;
- }
- }
- }
-
- // looping over custom components
- for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
- componentId
- < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
- + customPowerComponentCount;
- componentId++) {
- for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
- final long timeInStateMillis =
- uidConsumer.getTimeInProcessStateMs(processState);
- if (timeInStateMillis <= 0) {
- continue;
- }
-
- if (!addStatsForCustomComponent(
- data,
- sessionInfo,
- uid,
- processState,
- timeInStateMillis,
- totalConsumedPowerMah,
- uidConsumer,
- componentId)) {
+ componentIndex)) {
return StatsManager.PULL_SUCCESS;
}
}
@@ -1455,20 +1409,21 @@
return StatsManager.PULL_SUCCESS;
}
- private boolean addStatsForPredefinedComponent(
+ private boolean addStatsForPowerComponent(
List<StatsEvent> data,
SessionInfo sessionInfo,
int uid,
@BatteryConsumer.ProcessState int processState,
float totalConsumedPowerMah,
+ long timeInState,
BatteryConsumer batteryConsumer,
- @BatteryConsumer.PowerComponent int componentId) {
+ @BatteryConsumer.PowerComponentId int componentId) {
final BatteryConsumer.Key key = batteryConsumer.getKey(componentId, processState);
if (key == null) {
return true;
}
- final String powerComponentName = BatteryConsumer.powerComponentIdToString(componentId);
+ final String powerComponentName = batteryConsumer.getPowerComponentName(componentId);
final float powerMah = (float) batteryConsumer.getConsumedPower(key);
final long powerComponentDurationMillis = batteryConsumer.getUsageDurationMillis(key);
@@ -1476,13 +1431,6 @@
return true;
}
- long timeInState = 0;
- if (batteryConsumer instanceof UidBatteryConsumer) {
- timeInState =
- ((UidBatteryConsumer) batteryConsumer)
- .getTimeInProcessStateMs(processState);
- }
-
return addStatsAtom(
data,
sessionInfo,
@@ -1495,44 +1443,6 @@
powerComponentDurationMillis);
}
- private boolean addStatsForCustomComponent(
- List<StatsEvent> data,
- SessionInfo sessionInfo,
- int uid,
- @BatteryConsumer.ProcessState int processState,
- long timeInStateMillis,
- float totalConsumedPowerMah,
- BatteryConsumer batteryConsumer,
- int componentId) {
-
- if (componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
- throw new IllegalArgumentException("Invalid custom component id: " + componentId);
- }
-
- final float powerMah =
- (float) batteryConsumer.getConsumedPowerForCustomComponent(componentId);
- if (powerMah == 0) {
- return true;
- }
-
- final String powerComponentName =
- batteryConsumer.getCustomPowerComponentName(componentId);
-
- final long powerComponentDurationMillis =
- batteryConsumer.getUsageDurationForCustomComponentMillis(componentId);
-
- return addStatsAtom(
- data,
- sessionInfo,
- uid,
- processState,
- timeInStateMillis,
- powerComponentName,
- totalConsumedPowerMah,
- powerMah,
- powerComponentDurationMillis);
- }
-
/**
* Returns true on success and false if reached max atoms capacity and no more atoms should
* be added
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 0b6d135..9000e9b 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -215,6 +215,7 @@
"stability",
"statsd",
"system_performance",
+ "system_sw_battery",
"system_sw_touch",
"system_sw_usb",
"test_suites",
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index f0f6db2..35be0f3 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -183,15 +183,17 @@
Flags::offloadDozeOverrideHoldsWakelock
);
- private final FlagState mOffloadSessionCancelBlockScreenOn =
- new FlagState(
- Flags.FLAG_OFFLOAD_SESSION_CANCEL_BLOCK_SCREEN_ON,
- Flags::offloadSessionCancelBlockScreenOn);
+ private final FlagState mOffloadSessionCancelBlockScreenOn = new FlagState(
+ Flags.FLAG_OFFLOAD_SESSION_CANCEL_BLOCK_SCREEN_ON,
+ Flags::offloadSessionCancelBlockScreenOn);
- private final FlagState mNewHdrBrightnessModifier =
- new FlagState(
- Flags.FLAG_NEW_HDR_BRIGHTNESS_MODIFIER,
- Flags::newHdrBrightnessModifier);
+ private final FlagState mNewHdrBrightnessModifier = new FlagState(
+ Flags.FLAG_NEW_HDR_BRIGHTNESS_MODIFIER,
+ Flags::newHdrBrightnessModifier);
+
+ private final FlagState mIdleScreenConfigInSubscribingLightSensor = new FlagState(
+ Flags.FLAG_IDLE_SCREEN_CONFIG_IN_SUBSCRIBING_LIGHT_SENSOR,
+ Flags::idleScreenConfigInSubscribingLightSensor);
private final FlagState mNormalBrightnessForDozeParameter = new FlagState(
Flags.FLAG_NORMAL_BRIGHTNESS_FOR_DOZE_PARAMETER,
@@ -404,6 +406,14 @@
return mNormalBrightnessForDozeParameter.isEnabled();
}
+ /**
+ * @return {@code true} if idle timer refresh rate config is accounted for while subscribing to
+ * the light sensor
+ */
+ public boolean isIdleScreenConfigInSubscribingLightSensorEnabled() {
+ return mIdleScreenConfigInSubscribingLightSensor.isEnabled();
+ }
+
/**
* dumps all flagstates
* @param pw printWriter
@@ -444,6 +454,7 @@
pw.println(" " + mOffloadSessionCancelBlockScreenOn);
pw.println(" " + mNewHdrBrightnessModifier);
pw.println(" " + mNormalBrightnessForDozeParameter);
+ pw.println(" " + mIdleScreenConfigInSubscribingLightSensor);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index d929249..da5063a 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -330,3 +330,14 @@
bug: "331275392"
is_fixed_read_only: true
}
+
+flag {
+ name: "idle_screen_config_in_subscribing_light_sensor"
+ namespace: "display_manager"
+ description: "Account for Idle screen refresh rate configs while subscribing to light sensor"
+ bug: "358019330"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 31f5a41..c31d1d8 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -483,6 +483,7 @@
/* attemptReadFromFeatureParams= */ true);
mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
/* attemptReadFromFeatureParams= */ true);
+ mBrightnessObserver.loadIdleScreenRefreshRateConfigs(displayDeviceConfig);
mBrightnessObserver.reloadLightSensor(displayDeviceConfig);
mHbmObserver.setupHdrRefreshRates(displayDeviceConfig);
}
@@ -1752,6 +1753,10 @@
private SparseArray<RefreshRateRange> mHighZoneRefreshRateForThermals;
private int mRefreshRateInHighZone;
+ @Nullable
+ private List<IdleScreenRefreshRateTimeoutLuxThresholdPoint>
+ mIdleScreenRefreshRateTimeoutLuxThresholdPoints;
+
@GuardedBy("mLock")
private @Temperature.ThrottlingStatus int mThermalStatus = Temperature.THROTTLING_NONE;
@@ -1765,6 +1770,24 @@
mRefreshRateInHighZone = context.getResources().getInteger(
R.integer.config_fixedRefreshRateInHighZone);
mVsyncLowLightBlockingVoteEnabled = flags.isVsyncLowLightVoteEnabled();
+ loadIdleScreenRefreshRateConfigs(/* displayDeviceConfig= */ null);
+ }
+
+ private void loadIdleScreenRefreshRateConfigs(DisplayDeviceConfig displayDeviceConfig) {
+ synchronized (mLock) {
+ if (!mDisplayManagerFlags.isIdleScreenConfigInSubscribingLightSensorEnabled()
+ || displayDeviceConfig == null || displayDeviceConfig
+ .getIdleScreenRefreshRateTimeoutLuxThresholdPoint().isEmpty()) {
+ // Setting this to null will let surface flinger know that the idle timer is not
+ // configured in the display configs
+ mIdleScreenRefreshRateConfig = null;
+ mIdleScreenRefreshRateTimeoutLuxThresholdPoints = null;
+ return;
+ }
+ mIdleScreenRefreshRateTimeoutLuxThresholdPoints =
+ displayDeviceConfig
+ .getIdleScreenRefreshRateTimeoutLuxThresholdPoint();
+ }
}
/**
@@ -1813,11 +1836,19 @@
return mRefreshRateInLowZone;
}
+ @Nullable
@VisibleForTesting
IdleScreenRefreshRateConfig getIdleScreenRefreshRateConfig() {
return mIdleScreenRefreshRateConfig;
}
+ @Nullable
+ @VisibleForTesting
+ List<IdleScreenRefreshRateTimeoutLuxThresholdPoint>
+ getIdleScreenRefreshRateTimeoutLuxThresholdPoints() {
+ return mIdleScreenRefreshRateTimeoutLuxThresholdPoints;
+ }
+
private void loadLowBrightnessThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig,
boolean attemptReadFromFeatureParams) {
loadRefreshRateInHighZone(displayDeviceConfig, attemptReadFromFeatureParams);
@@ -2212,12 +2243,11 @@
mShouldObserveAmbientHighChange = false;
}
- if (mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) {
+ if (shouldRegisterLightSensor()) {
Sensor lightSensor = getLightSensor();
if (lightSensor != null && lightSensor != mLightSensor) {
final Resources res = mContext.getResources();
-
mAmbientFilter = AmbientFilterFactory.createBrightnessFilter(TAG, res);
mLightSensor = lightSensor;
}
@@ -2443,8 +2473,8 @@
}
boolean registerForThermals = false;
- if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange)
- && isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) {
+ if (shouldRegisterLightSensor() && isDeviceActive() && !mLowPowerModeEnabled
+ && mRefreshRateChangeable) {
registerLightSensor();
registerForThermals = mLowZoneRefreshRateForThermals != null
|| mHighZoneRefreshRateForThermals != null;
@@ -2463,6 +2493,17 @@
}
}
+ private boolean shouldRegisterLightSensor() {
+ return mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange
+ || isIdleScreenRefreshRateConfigDefined();
+ }
+
+ private boolean isIdleScreenRefreshRateConfigDefined() {
+ return mDisplayManagerFlags.isIdleScreenConfigInSubscribingLightSensorEnabled()
+ && mIdleScreenRefreshRateTimeoutLuxThresholdPoints != null
+ && !mIdleScreenRefreshRateTimeoutLuxThresholdPoints.isEmpty();
+ }
+
private void registerLightSensor() {
if (mRegisteredLightSensor == mLightSensor) {
return;
@@ -2563,7 +2604,6 @@
// is interrupted by a new sensor event.
mHandler.postDelayed(mInjectSensorEventRunnable, INJECT_EVENTS_INTERVAL_MS);
}
-
if (mDisplayManagerFlags.isIdleScreenRefreshRateTimeoutEnabled()) {
updateIdleScreenRefreshRate(mAmbientLux);
}
@@ -2628,24 +2668,15 @@
}
private void updateIdleScreenRefreshRate(float ambientLux) {
- List<IdleScreenRefreshRateTimeoutLuxThresholdPoint>
- idleScreenRefreshRateTimeoutLuxThresholdPoints;
- synchronized (mLock) {
- if (mDefaultDisplayDeviceConfig == null || mDefaultDisplayDeviceConfig
- .getIdleScreenRefreshRateTimeoutLuxThresholdPoint().isEmpty()) {
- // Setting this to null will let surface flinger know that the idle timer is not
- // configured in the display configs
- mIdleScreenRefreshRateConfig = null;
- return;
- }
-
- idleScreenRefreshRateTimeoutLuxThresholdPoints =
- mDefaultDisplayDeviceConfig
- .getIdleScreenRefreshRateTimeoutLuxThresholdPoint();
+ if (mIdleScreenRefreshRateTimeoutLuxThresholdPoints == null
+ || mIdleScreenRefreshRateTimeoutLuxThresholdPoints.isEmpty()) {
+ mIdleScreenRefreshRateConfig = null;
+ return;
}
+
int newTimeout = -1;
for (IdleScreenRefreshRateTimeoutLuxThresholdPoint point :
- idleScreenRefreshRateTimeoutLuxThresholdPoints) {
+ mIdleScreenRefreshRateTimeoutLuxThresholdPoints) {
int newLux = point.getLux().intValue();
if (newLux <= ambientLux) {
newTimeout = point.getTimeout().intValue();
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index a8fc862..bad714f 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -466,7 +466,7 @@
injector.getLooper());
mTouchpadDebugViewController =
touchpadVisualizer() ? new TouchpadDebugViewController(mContext,
- injector.getLooper()) : null;
+ injector.getLooper(), this) : null;
mBatteryController = new BatteryController(mContext, mNative, injector.getLooper(),
injector.getUEventManager());
mKeyboardBacklightController = InputFeatureFlagProvider.isKeyboardBacklightControlEnabled()
@@ -1798,6 +1798,16 @@
return mNative.getSensorList(deviceId);
}
+ /**
+ * Retrieves the hardware properties of the touchpad for the given device ID.
+ * Returns null if the device has no touchpad hardware properties
+ * or if the device ID is invalid.
+ */
+ @Nullable
+ public TouchpadHardwareProperties getTouchpadHardwareProperties(int deviceId) {
+ return mNative.getTouchpadHardwareProperties(deviceId);
+ }
+
@Override // Binder call
public boolean registerSensorListener(IInputSensorEventListener listener) {
if (DEBUG) {
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 69a9f4d..1e7c97f9 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -216,6 +216,9 @@
InputSensorInfo[] getSensorList(int deviceId);
+ @Nullable
+ TouchpadHardwareProperties getTouchpadHardwareProperties(int deviceId);
+
boolean flushSensor(int deviceId, int sensorType);
boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
@@ -512,6 +515,9 @@
public native InputSensorInfo[] getSensorList(int deviceId);
@Override
+ public native TouchpadHardwareProperties getTouchpadHardwareProperties(int deviceId);
+
+ @Override
public native boolean flushSensor(int deviceId, int sensorType);
@Override
diff --git a/services/core/java/com/android/server/input/TouchpadHardwareProperties.java b/services/core/java/com/android/server/input/TouchpadHardwareProperties.java
new file mode 100644
index 0000000..71abb19
--- /dev/null
+++ b/services/core/java/com/android/server/input/TouchpadHardwareProperties.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import com.android.internal.util.DataClass;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByNative;
+
+/**
+ * A Java representation of hardware properties for a touchpad or mouse device.
+ * This class mirrors the Gestures library HardwareProperties C++ struct used for representing
+ * touchpad and mouse device properties, including touch area, resolution, and features like haptic
+ * feedback, multitouch, and scroll wheels. It facilitates interaction between native and managed
+ * code in Android.
+ */
+@DataClass(
+ genToString = true
+)
+@UsedByNative(
+ description = "Called from JNI in jni/com_android_server_input_InputManagerService.cpp",
+ kind = KeepItemKind.CLASS_AND_MEMBERS)
+public class TouchpadHardwareProperties {
+ /**
+ * The minimum X coordinate that the device can report.
+ */
+ private float mLeft;
+
+ /**
+ * The minimum Y coordinate that the device can report.
+ */
+ private float mTop;
+
+ /**
+ * The maximum X coordinate that the device can report.
+ */
+ private float mRight;
+
+ /**
+ * The maximum Y coordinate that the device can report.
+ */
+ private float mBottom;
+
+ /**
+ * The resolution of the X axis, in units per mm. Set to 0 if the
+ * resolution is unknown.
+ */
+ private float mResX;
+ /**
+ * The resolutions of the Y axis, in units per mm. Set to 0 if the
+ * resolution is unknown.
+ */
+ private float mResY;
+
+ /**
+ * The minimum orientation value.
+ */
+ private float mOrientationMinimum;
+ /**
+ * The maximum orientation value.
+ */
+ private float mOrientationMaximum;
+
+ /**
+ * The maximum number of finger slots that the device can report in one
+ * HardwareState struct.
+ */
+ private short mMaxFingerCount;
+
+ /**
+ * Whether the touchpad has a button under its touch surface, allowing the
+ * user to click by pressing (almost) anywhere on the pad, as opposed to
+ * having one or more separate buttons for clicking.
+ */
+ private boolean mIsButtonPad;
+
+ /**
+ * Whether the touchpad is haptic, meaning that it reports true pressure (not
+ * just touch area) via the pressure axis, and can provide haptic feedback.
+ */
+ private boolean mIsHapticPad;
+
+ /**
+ * Whether the touchpad reports pressure values in any way.
+ */
+ private boolean mReportsPressure = true;
+
+ /**
+ * Returns a string representation of this instance, including all fields.
+ */
+ public String toString() {
+ return "HardwareProperties{"
+ + "left=" + mLeft
+ + ", top=" + mTop
+ + ", right=" + mRight
+ + ", bottom=" + mBottom
+ + ", resX=" + mResX
+ + ", resY=" + mResY
+ + ", orientationMinimum=" + mOrientationMinimum
+ + ", orientationMaximum=" + mOrientationMaximum
+ + ", maxFingerCount=" + mMaxFingerCount
+ + ", isButtonPad=" + mIsButtonPad
+ + ", isHapticPad=" + mIsHapticPad
+ + ", reportsPressure=" + mReportsPressure
+ + '}';
+ }
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/input
+ // /TouchpadHardwareProperties.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ /* package-private */ TouchpadHardwareProperties(
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float resX,
+ float resY,
+ float orientationMinimum,
+ float orientationMaximum,
+ short maxFingerCount,
+ boolean isButtonPad,
+ boolean isHapticPad,
+ boolean reportsPressure) {
+ this.mLeft = left;
+ this.mTop = top;
+ this.mRight = right;
+ this.mBottom = bottom;
+ this.mResX = resX;
+ this.mResY = resY;
+ this.mOrientationMinimum = orientationMinimum;
+ this.mOrientationMaximum = orientationMaximum;
+ this.mMaxFingerCount = maxFingerCount;
+ this.mIsButtonPad = isButtonPad;
+ this.mIsHapticPad = isHapticPad;
+ this.mReportsPressure = reportsPressure;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The minimum X coordinate that the device can report.
+ */
+ @DataClass.Generated.Member
+ public float getLeft() {
+ return mLeft;
+ }
+
+ /**
+ * The minimum Y coordinate that the device can report.
+ */
+ @DataClass.Generated.Member
+ public float getTop() {
+ return mTop;
+ }
+
+ /**
+ * The maximum X coordinate that the device can report.
+ */
+ @DataClass.Generated.Member
+ public float getRight() {
+ return mRight;
+ }
+
+ /**
+ * The maximum Y coordinate that the device can report.
+ */
+ @DataClass.Generated.Member
+ public float getBottom() {
+ return mBottom;
+ }
+
+ /**
+ * The resolution of the X axis, in units per mm. Set to 0 if the
+ * resolution is unknown.
+ */
+ @DataClass.Generated.Member
+ public float getResX() {
+ return mResX;
+ }
+
+ /**
+ * The resolutions of the Y axis, in units per mm. Set to 0 if the
+ * resolution is unknown.
+ */
+ @DataClass.Generated.Member
+ public float getResY() {
+ return mResY;
+ }
+
+ /**
+ * The minimum orientation value.
+ */
+ @DataClass.Generated.Member
+ public float getOrientationMinimum() {
+ return mOrientationMinimum;
+ }
+
+ /**
+ * The maximum orientation value.
+ */
+ @DataClass.Generated.Member
+ public float getOrientationMaximum() {
+ return mOrientationMaximum;
+ }
+
+ /**
+ * The maximum number of finger slots that the device can report in one
+ * HardwareState struct.
+ */
+ @DataClass.Generated.Member
+ public short getMaxFingerCount() {
+ return mMaxFingerCount;
+ }
+
+ /**
+ * Whether the touchpad has a button under its touch surface, allowing the
+ * user to click by pressing (almost) anywhere on the pad, as opposed to
+ * having one or more separate buttons for clicking.
+ */
+ @DataClass.Generated.Member
+ public boolean isIsButtonPad() {
+ return mIsButtonPad;
+ }
+
+ /**
+ * Whether the touchpad is haptic, meaning that it reports true pressure (not
+ * just touch area) via the pressure axis, and can provide haptic feedback.
+ */
+ @DataClass.Generated.Member
+ public boolean isIsHapticPad() {
+ return mIsHapticPad;
+ }
+
+ /**
+ * Whether the touchpad reports pressure values in any way.
+ */
+ @DataClass.Generated.Member
+ public boolean isReportsPressure() {
+ return mReportsPressure;
+ }
+
+ /**
+ * A builder for {@link TouchpadHardwareProperties}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static class Builder {
+
+ private float mLeft;
+ private float mTop;
+ private float mRight;
+ private float mBottom;
+ private float mResX;
+ private float mResY;
+ private float mOrientationMinimum;
+ private float mOrientationMaximum;
+ private short mMaxFingerCount;
+ private boolean mIsButtonPad;
+ private boolean mIsHapticPad;
+ private boolean mReportsPressure;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param left
+ * The minimum X coordinate that the device can report.
+ * @param top
+ * The minimum Y coordinate that the device can report.
+ * @param right
+ * The maximum X coordinate that the device can report.
+ * @param bottom
+ * The maximum Y coordinate that the device can report.
+ * @param resX
+ * The resolution of the X axis, in units per mm. Set to 0 if the
+ * resolution is unknown.
+ * @param resY
+ * The resolutions of the Y axis, in units per mm. Set to 0 if the
+ * resolution is unknown.
+ * @param orientationMinimum
+ * The minimum orientation value.
+ * @param orientationMaximum
+ * The maximum orientation value.
+ * @param maxFingerCount
+ * The maximum number of finger slots that the device can report in one
+ * HardwareState struct.
+ * @param isButtonPad
+ * Whether the touchpad has a button under its touch surface, allowing the
+ * user to click by pressing (almost) anywhere on the pad, as opposed to
+ * having one or more separate buttons for clicking.
+ * @param isHapticPad
+ * Whether the touchpad is haptic, meaning that it reports true pressure (not
+ * just touch area) via the pressure axis, and can provide haptic feedback.
+ */
+ public Builder(
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float resX,
+ float resY,
+ float orientationMinimum,
+ float orientationMaximum,
+ short maxFingerCount,
+ boolean isButtonPad,
+ boolean isHapticPad) {
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+ mResX = resX;
+ mResY = resY;
+ mOrientationMinimum = orientationMinimum;
+ mOrientationMaximum = orientationMaximum;
+ mMaxFingerCount = maxFingerCount;
+ mIsButtonPad = isButtonPad;
+ mIsHapticPad = isHapticPad;
+ }
+
+ /**
+ * The minimum X coordinate that the device can report.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setLeft(float value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mLeft = value;
+ return this;
+ }
+
+ /**
+ * The minimum Y coordinate that the device can report.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setTop(float value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mTop = value;
+ return this;
+ }
+
+ /**
+ * The maximum X coordinate that the device can report.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setRight(float value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mRight = value;
+ return this;
+ }
+
+ /**
+ * The maximum Y coordinate that the device can report.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setBottom(float value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mBottom = value;
+ return this;
+ }
+
+ /**
+ * The resolution of the X axis, in units per mm. Set to 0 if the
+ * resolution is unknown.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setResX(float value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10;
+ mResX = value;
+ return this;
+ }
+
+ /**
+ * The resolutions of the Y axis, in units per mm. Set to 0 if the
+ * resolution is unknown.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setResY(float value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20;
+ mResY = value;
+ return this;
+ }
+
+ /**
+ * The minimum orientation value.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setOrientationMinimum(float value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x40;
+ mOrientationMinimum = value;
+ return this;
+ }
+
+ /**
+ * The maximum orientation value.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setOrientationMaximum(float value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x80;
+ mOrientationMaximum = value;
+ return this;
+ }
+
+ /**
+ * The maximum number of finger slots that the device can report in one
+ * HardwareState struct.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setMaxFingerCount(short value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x100;
+ mMaxFingerCount = value;
+ return this;
+ }
+
+ /**
+ * Whether the touchpad has a button under its touch surface, allowing the
+ * user to click by pressing (almost) anywhere on the pad, as opposed to
+ * having one or more separate buttons for clicking.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setIsButtonPad(boolean value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x200;
+ mIsButtonPad = value;
+ return this;
+ }
+
+ /**
+ * Whether the touchpad is haptic, meaning that it reports true pressure (not
+ * just touch area) via the pressure axis, and can provide haptic feedback.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setIsHapticPad(boolean value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x400;
+ mIsHapticPad = value;
+ return this;
+ }
+
+ /**
+ * Whether the touchpad reports pressure values in any way.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setReportsPressure(boolean value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x800;
+ mReportsPressure = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @android.annotation.NonNull TouchpadHardwareProperties build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1000; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x800) == 0) {
+ mReportsPressure = true;
+ }
+ TouchpadHardwareProperties o = new TouchpadHardwareProperties(
+ mLeft,
+ mTop,
+ mRight,
+ mBottom,
+ mResX,
+ mResY,
+ mOrientationMinimum,
+ mOrientationMaximum,
+ mMaxFingerCount,
+ mIsButtonPad,
+ mIsHapticPad,
+ mReportsPressure);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x1000) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1723570664889L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/services/core"
+ + "/java/com/android/server/input/TouchpadHardwareProperties.java",
+ inputSignatures = "private float mLeft\nprivate float mTop\nprivate float mRight\n"
+ + "private float mBottom\nprivate float mResX\nprivate float mResY\n"
+ + "private float mOrientationMinimum\nprivate float mOrientationMaximum\n"
+ + "private short mMaxFingerCount\nprivate boolean mIsButtonPad\n"
+ + "private boolean mIsHapticPad\nprivate boolean mReportsPressure\n"
+ + "public java.lang.String toString()\n"
+ + "class TouchpadHardwareProperties extends java.lang.Object implements []\n"
+ + "@com.android.internal.util.DataClass(genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+ //@formatter:on
+ // End of generated code
+}
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
index 9c2aa36..c7760c6 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
@@ -29,6 +29,9 @@
import android.view.InputDevice;
import android.view.WindowManager;
+import com.android.server.input.InputManagerService;
+import com.android.server.input.TouchpadHardwareProperties;
+
import java.util.Objects;
public class TouchpadDebugViewController {
@@ -39,13 +42,16 @@
private final Handler mHandler;
@Nullable
private TouchpadDebugView mTouchpadDebugView;
+ private final InputManagerService mInputManagerService;
- public TouchpadDebugViewController(Context context, Looper looper) {
+ public TouchpadDebugViewController(Context context, Looper looper,
+ InputManagerService inputManagerService) {
final DisplayManager displayManager = Objects.requireNonNull(
context.getSystemService(DisplayManager.class));
final Display defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
mContext = context.createDisplayContext(defaultDisplay);
mHandler = new Handler(looper);
+ mInputManagerService = inputManagerService;
}
public void systemRunning() {
@@ -110,6 +116,17 @@
wm.addView(mTouchpadDebugView, lp);
Slog.d(TAG, "Touchpad debug view created.");
+
+ TouchpadHardwareProperties mTouchpadHardwareProperties =
+ mInputManagerService.getTouchpadHardwareProperties(
+ touchpadId);
+ // TODO(b/360137366): Use the hardware properties to initialise layout parameters.
+ if (mTouchpadHardwareProperties != null) {
+ Slog.d(TAG, mTouchpadHardwareProperties.toString());
+ } else {
+ Slog.w(TAG, "Failed to retrieve touchpad hardware properties for "
+ + "device ID: " + touchpadId);
+ }
}
private void hideDebugView(int touchpadId) {
@@ -122,4 +139,4 @@
mTouchpadDebugView = null;
Slog.d(TAG, "Touchpad debug view removed.");
}
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3654283..f34b4e9 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4233,6 +4233,9 @@
@NonNull UserData userData) {
final var bindingController = userData.mBindingController;
final var currentImi = bindingController.getSelectedMethod();
+ if (currentImi == null) {
+ return false;
+ }
final ImeSubtypeListItem nextSubtype = userData.mSwitchingController
.getNextInputMethodLocked(onlyCurrentIme, currentImi,
bindingController.getCurrentSubtype(),
@@ -4250,6 +4253,9 @@
private boolean shouldOfferSwitchingToNextInputMethodLocked(@NonNull UserData userData) {
final var bindingController = userData.mBindingController;
final var currentImi = bindingController.getSelectedMethod();
+ if (currentImi == null) {
+ return false;
+ }
final ImeSubtypeListItem nextSubtype = userData.mSwitchingController
.getNextInputMethodLocked(false /* onlyCurrentIme */, currentImi,
bindingController.getCurrentSubtype(),
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 202543c..96b3e08 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -686,11 +686,8 @@
*/
@Nullable
public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
- @Nullable InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
+ @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
@SwitchMode int mode, boolean forward) {
- if (imi == null) {
- return null;
- }
if (Flags.imeSwitcherRevamp()) {
return mRotationList.next(imi, subtype, onlyCurrentIme,
isRecency(mode, forward), forward);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c7c984b..ffb2bb6 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5675,7 +5675,7 @@
// a "normal" rule, it must provide a CP/ConfigActivity too.
if (android.app.Flags.modesApi()) {
boolean isImplicitRuleUpdateFromSystem = updateId != null
- && ZenModeHelper.isImplicitRuleId(updateId)
+ && ZenModeConfig.isImplicitRuleId(updateId)
&& isCallerSystemOrSystemUi();
if (!isImplicitRuleUpdateFromSystem
&& rule.getOwner() == null
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 0f50260..ee3f48d 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -39,6 +39,7 @@
import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_ACTIVATE;
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE;
+import static android.service.notification.ZenModeConfig.implicitRuleId;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.Preconditions.checkArgument;
@@ -155,8 +156,6 @@
static final int RULE_LIMIT_PER_PACKAGE = 100;
private static final Duration DELETED_RULE_KEPT_FOR = Duration.ofDays(30);
- private static final String IMPLICIT_RULE_ID_PREFIX = "implicit_"; // + pkg_name
-
private static final int MAX_ICON_RESOURCE_NAME_LENGTH = 1000;
/**
@@ -783,14 +782,6 @@
return rule;
}
- private static String implicitRuleId(String forPackage) {
- return IMPLICIT_RULE_ID_PREFIX + forPackage;
- }
-
- static boolean isImplicitRuleId(@NonNull String ruleId) {
- return ruleId.startsWith(IMPLICIT_RULE_ID_PREFIX);
- }
-
boolean removeAutomaticZenRule(String id, @ConfigOrigin int origin, String reason,
int callingUid) {
checkManageRuleOrigin("removeAutomaticZenRule", origin);
@@ -977,7 +968,16 @@
rule.setConditionOverride(OVERRIDE_DEACTIVATE);
}
}
+ } else if (origin == ORIGIN_USER_IN_APP && condition != null
+ && condition.source == SOURCE_USER_ACTION) {
+ // Remove override and just apply the condition. Since the app is reporting that the
+ // user asked for it, by definition it knows that, and will adjust its automatic
+ // behavior accordingly -> no need to override.
+ rule.condition = condition;
+ rule.resetConditionOverride();
} else {
+ // Update the condition, and check whether we can remove the override (if automatic
+ // and manual decisions agree).
rule.condition = condition;
rule.reconsiderConditionOverride();
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 934feb3..ba3de33 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3422,7 +3422,7 @@
return handleHomeShortcuts(focusedToken, event);
case KeyEvent.KEYCODE_RECENT_APPS:
if (firstDown) {
- toggleRecentApps();
+ showRecentApps(false /* triggeredFromAltTab */);
notifyKeyGestureCompleted(event,
KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS);
}
diff --git a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerCalculator.java b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerCalculator.java
index 5b7467e..c1f2ae8 100644
--- a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerCalculator.java
@@ -62,7 +62,7 @@
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
for (int i = 0; i < customEnergyConsumerPowerMah.length; i++) {
- deviceBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
+ deviceBatteryConsumerBuilder.setConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i,
customEnergyConsumerPowerMah[i]);
}
@@ -72,7 +72,7 @@
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
for (int i = 0; i < totalAppPowerMah.length; i++) {
- appsBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
+ appsBatteryConsumerBuilder.setConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i,
totalAppPowerMah[i]);
}
@@ -96,7 +96,7 @@
newTotalPowerMah = totalPowerMah;
}
for (int i = 0; i < customEnergyConsumerPowerMah.length; i++) {
- app.setConsumedPowerForCustomComponent(
+ app.setConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i,
customEnergyConsumerPowerMah[i]);
if (!app.isVirtualUid()) {
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 6820197..9506741 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -172,6 +172,9 @@
void setUidStats(int uid, int[] states, long[] values) {
UidStats uidStats = getUidStats(uid);
+ if (uidStats.stats == null) {
+ createUidStats(uidStats, mPowerStatsTimestamp);
+ }
uidStats.stats.setStats(states, values);
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
index 081e560..c734f68 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
@@ -46,6 +46,10 @@
mHistory = history;
}
+ public AggregatedPowerStatsConfig getConfig() {
+ return mAggregatedPowerStatsConfig;
+ }
+
void setPowerComponentEnabled(int powerComponentId, boolean enabled) {
synchronized (this) {
if (mStats != null) {
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
index 281faf1..c5bed24 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -16,12 +16,14 @@
package com.android.server.power.stats;
+import android.annotation.Nullable;
import android.os.AggregateBatteryConsumer;
import android.os.BatteryConsumer;
import android.os.BatteryUsageStats;
import android.os.UidBatteryConsumer;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.PowerStats;
import java.util.ArrayList;
@@ -59,7 +61,7 @@
*/
public void exportAggregatedPowerStats(BatteryUsageStats.Builder batteryUsageStatsBuilder,
long monotonicStartTime, long monotonicEndTime) {
- synchronized (this) {
+ synchronized (mPowerStatsAggregator) {
boolean hasStoredSpans = false;
long maxEndTime = monotonicStartTime;
List<PowerStatsSpan.Metadata> spans = mPowerStatsStore.getTableOfContents();
@@ -116,7 +118,8 @@
}
}
- private void populateBatteryUsageStatsBuilder(
+ @VisibleForTesting
+ void populateBatteryUsageStatsBuilder(
BatteryUsageStats.Builder batteryUsageStatsBuilder, AggregatedPowerStats stats) {
List<PowerComponentAggregatedPowerStats> powerComponentStats =
stats.getPowerComponentStats();
@@ -125,15 +128,17 @@
}
}
- private static void populateBatteryUsageStatsBuilder(
+ private void populateBatteryUsageStatsBuilder(
BatteryUsageStats.Builder batteryUsageStatsBuilder,
PowerComponentAggregatedPowerStats powerComponentStats) {
PowerStats.Descriptor descriptor = powerComponentStats.getPowerStatsDescriptor();
if (descriptor == null) {
return;
}
- boolean isCustomComponent =
- descriptor.powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+
+ if (!batteryUsageStatsBuilder.isSupportedPowerComponent(descriptor.powerComponentId)) {
+ return;
+ }
PowerStatsLayout layout = new PowerStatsLayout();
layout.fromExtras(descriptor.extras);
@@ -149,16 +154,17 @@
}
for (int powerState = 0; powerState < BatteryConsumer.POWER_STATE_COUNT; powerState++) {
- if (batteryUsageStatsBuilder.isPowerStateDataNeeded() && !isCustomComponent) {
- if (powerState == BatteryConsumer.POWER_STATE_UNSPECIFIED) {
- continue;
+ if (batteryUsageStatsBuilder.isPowerStateDataNeeded()) {
+ if (powerState != BatteryConsumer.POWER_STATE_UNSPECIFIED) {
+ populateAggregatedBatteryConsumer(batteryUsageStatsBuilder,
+ powerComponentStats,
+ layout, deviceStats, screenState, powerState);
}
- } else if (powerState != BatteryConsumer.POWER_STATE_BATTERY) {
- continue;
+ } else if (powerState == BatteryConsumer.POWER_STATE_BATTERY) {
+ populateAggregatedBatteryConsumer(batteryUsageStatsBuilder,
+ powerComponentStats,
+ layout, deviceStats, screenState, powerState);
}
-
- populateAggregatedBatteryConsumer(batteryUsageStatsBuilder, powerComponentStats,
- layout, deviceStats, screenState, powerState);
}
}
if (layout.isUidPowerAttributionSupported()) {
@@ -167,15 +173,12 @@
}
}
- private static void populateAggregatedBatteryConsumer(
+ private void populateAggregatedBatteryConsumer(
BatteryUsageStats.Builder batteryUsageStatsBuilder,
PowerComponentAggregatedPowerStats powerComponentStats, PowerStatsLayout layout,
long[] deviceStats, @BatteryConsumer.ScreenState int screenState,
@BatteryConsumer.PowerState int powerState) {
int powerComponentId = powerComponentStats.powerComponentId;
- boolean isCustomComponent =
- powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
-
double[] totalPower = new double[1];
MultiStateStats.States.forEachTrackedStateCombination(
powerComponentStats.getConfig().getDeviceStateConfig(),
@@ -194,38 +197,27 @@
AggregateBatteryConsumer.Builder deviceScope =
batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
- if (isCustomComponent) {
- if (batteryUsageStatsBuilder.isSupportedCustomPowerComponent(powerComponentId)) {
- deviceScope.addConsumedPowerForCustomComponent(powerComponentId, totalPower[0]);
- }
- } else {
- BatteryConsumer.Key key = deviceScope.getKey(powerComponentId,
- BatteryConsumer.PROCESS_STATE_ANY, screenState, powerState);
- if (key != null) {
- deviceScope.addConsumedPower(key, totalPower[0],
- BatteryConsumer.POWER_MODEL_UNDEFINED);
- }
- deviceScope.addConsumedPower(powerComponentId, totalPower[0],
+ BatteryConsumer.Key key = getKeyForPartialTotal(batteryUsageStatsBuilder, deviceScope,
+ powerComponentId, screenState, powerState);
+ if (key != null) {
+ deviceScope.addConsumedPower(key, totalPower[0],
BatteryConsumer.POWER_MODEL_UNDEFINED);
}
+ deviceScope.addConsumedPower(powerComponentId, totalPower[0],
+ BatteryConsumer.POWER_MODEL_UNDEFINED);
}
- private static void populateBatteryConsumers(
+ private void populateBatteryConsumers(
BatteryUsageStats.Builder batteryUsageStatsBuilder,
PowerComponentAggregatedPowerStats powerComponentStats,
PowerStatsLayout layout) {
AggregatedPowerStatsConfig.PowerComponent powerComponent = powerComponentStats.getConfig();
- int powerComponentId = powerComponent.getPowerComponentId();
- boolean isCustomComponent =
- powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
PowerStats.Descriptor descriptor = powerComponentStats.getPowerStatsDescriptor();
long[] uidStats = new long[descriptor.uidStatsArrayLength];
- // TODO(b/347101393): add support for per-procstate breakdown for custom energy consumers
boolean breakDownByProcState = batteryUsageStatsBuilder.isProcessStateDataNeeded()
&& powerComponent
- .getUidStateConfig()[AggregatedPowerStatsConfig.STATE_PROCESS_STATE].isTracked()
- && !isCustomComponent;
+ .getUidStateConfig()[AggregatedPowerStatsConfig.STATE_PROCESS_STATE].isTracked();
ArrayList<Integer> uids = new ArrayList<>();
powerComponentStats.collectUids(uids);
@@ -239,7 +231,7 @@
}
for (int powerState = 0; powerState < BatteryConsumer.POWER_STATE_COUNT; powerState++) {
- if (batteryUsageStatsBuilder.isPowerStateDataNeeded() && !isCustomComponent) {
+ if (batteryUsageStatsBuilder.isPowerStateDataNeeded()) {
if (powerState == BatteryConsumer.POWER_STATE_UNSPECIFIED) {
continue;
}
@@ -254,14 +246,20 @@
}
}
- private static void populateUidBatteryConsumers(
+ private void populateUidBatteryConsumers(
BatteryUsageStats.Builder batteryUsageStatsBuilder,
PowerComponentAggregatedPowerStats powerComponentStats, PowerStatsLayout layout,
List<Integer> uids, AggregatedPowerStatsConfig.PowerComponent powerComponent,
long[] uidStats, boolean breakDownByProcState,
@BatteryConsumer.ScreenState int screenState,
@BatteryConsumer.PowerState int powerState) {
- int powerComponentId = powerComponentStats.powerComponentId;
+ if (!batteryUsageStatsBuilder.isPowerStateDataNeeded()
+ && powerState != BatteryConsumer.POWER_STATE_BATTERY) {
+ return;
+ }
+
+ @BatteryConsumer.PowerComponentId int powerComponentId =
+ powerComponentStats.powerComponentId;
double[] powerByProcState =
new double[breakDownByProcState ? BatteryConsumer.PROCESS_STATE_COUNT : 1];
double powerAllApps = 0;
@@ -283,63 +281,81 @@
}
double power = layout.getUidPowerEstimate(uidStats);
- int procState = breakDownByProcState
- ? states[AggregatedPowerStatsConfig.STATE_PROCESS_STATE]
- : BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
- powerByProcState[procState] += power;
+ if (breakDownByProcState) {
+ int procState = states[AggregatedPowerStatsConfig.STATE_PROCESS_STATE];
+ // There is a difference in how PowerComponentAggregatedPowerStats
+ // and BatteryUsageStats see the "unspecified" process state.
+ // PowerComponentAggregatedPowerStats preserves it as is.
+ // BatteryUsageStats uses PROCESS_STATE_UNSPECIFIED to hold the total
+ // across all states, and PROCESS_STATE_UNSPECIFIED is treated
+ // the same as PROCESS_STATE_BACKGROUND, which makes sense since
+ // PROCESS_STATE_UNSPECIFIED is only present for headless processes
+ // like Process.ROOT_UID, Process.WIFI_UID etc.
+ if (procState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+ procState = BatteryConsumer.PROCESS_STATE_BACKGROUND;
+ }
+ powerByProcState[procState] += power;
+ }
+ powerByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED] += power;
});
- double powerAllProcStates = 0;
+ int resultScreenState = batteryUsageStatsBuilder.isScreenStateDataNeeded()
+ ? screenState : BatteryConsumer.SCREEN_STATE_UNSPECIFIED;
+ int resultPowerState = batteryUsageStatsBuilder.isPowerStateDataNeeded()
+ ? powerState : BatteryConsumer.POWER_STATE_UNSPECIFIED;
for (int procState = 0; procState < powerByProcState.length; procState++) {
double power = powerByProcState[procState];
if (power == 0) {
continue;
}
- powerAllProcStates += power;
- if (breakDownByProcState
- && procState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
- if (batteryUsageStatsBuilder.isPowerStateDataNeeded()) {
- builder.addConsumedPower(
- builder.getKey(powerComponentId, procState, screenState,
- powerState),
- power, BatteryConsumer.POWER_MODEL_UNDEFINED);
- } else {
- builder.addConsumedPower(
- builder.getKey(powerComponentId, procState, screenState,
- BatteryConsumer.POWER_STATE_UNSPECIFIED),
- power, BatteryConsumer.POWER_MODEL_UNDEFINED);
- }
- }
+ BatteryConsumer.Key key = builder.getKey(powerComponentId, procState,
+ resultScreenState, resultPowerState);
+ builder.addConsumedPower(key, power, BatteryConsumer.POWER_MODEL_UNDEFINED);
}
- if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
- if (batteryUsageStatsBuilder.isSupportedCustomPowerComponent(powerComponentId)) {
- builder.addConsumedPowerForCustomComponent(powerComponentId,
- powerAllProcStates);
- }
- } else {
- builder.addConsumedPower(powerComponentId, powerAllProcStates,
+
+ if (resultScreenState != BatteryConsumer.SCREEN_STATE_UNSPECIFIED
+ || resultPowerState != BatteryConsumer.POWER_STATE_UNSPECIFIED) {
+ builder.addConsumedPower(powerComponentId,
+ powerByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED],
BatteryConsumer.POWER_MODEL_UNDEFINED);
}
- powerAllApps += powerAllProcStates;
+ powerAllApps += powerByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED];
}
AggregateBatteryConsumer.Builder allAppsScope =
batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
- if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
- if (batteryUsageStatsBuilder.isSupportedCustomPowerComponent(powerComponentId)) {
- allAppsScope.addConsumedPowerForCustomComponent(powerComponentId, powerAllApps);
- }
- } else {
- BatteryConsumer.Key key = allAppsScope.getKey(powerComponentId,
- BatteryConsumer.PROCESS_STATE_ANY, screenState, powerState);
- if (key != null) {
+ BatteryConsumer.Key key = getKeyForPartialTotal(batteryUsageStatsBuilder, allAppsScope,
+ powerComponentId, screenState, powerState);
+ if (key != null) {
allAppsScope.addConsumedPower(key, powerAllApps,
BatteryConsumer.POWER_MODEL_UNDEFINED);
- }
- allAppsScope.addConsumedPower(powerComponentId, powerAllApps,
- BatteryConsumer.POWER_MODEL_UNDEFINED);
}
+ allAppsScope.addConsumedPower(powerComponentId, powerAllApps,
+ BatteryConsumer.POWER_MODEL_UNDEFINED);
+ }
+
+ @Nullable
+ private BatteryConsumer.Key getKeyForPartialTotal(
+ BatteryUsageStats.Builder batteryUsageStatsBuilder,
+ AggregateBatteryConsumer.Builder builder,
+ @BatteryConsumer.PowerComponentId int powerComponentId,
+ @BatteryConsumer.ScreenState int screenState,
+ @BatteryConsumer.PowerState int powerState) {
+ if (!batteryUsageStatsBuilder.isScreenStateDataNeeded()) {
+ screenState = BatteryConsumer.SCREEN_STATE_UNSPECIFIED;
+ }
+ if (!batteryUsageStatsBuilder.isPowerStateDataNeeded()) {
+ powerState = BatteryConsumer.POWER_STATE_UNSPECIFIED;
+ }
+
+ if (screenState == BatteryConsumer.SCREEN_STATE_UNSPECIFIED
+ && powerState == BatteryConsumer.POWER_STATE_UNSPECIFIED) {
+ return null;
+ }
+
+ return builder.getKey(powerComponentId, BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ screenState, powerState);
}
private static boolean areMatchingStates(int[] states,
diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java
index 908c751..8fb1fd6 100644
--- a/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java
@@ -16,7 +16,7 @@
package com.android.server.power.stats;
-import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
+import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT;
import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL;
@@ -197,7 +197,7 @@
List<Integer> uids) {
int[] uidStateValues = new int[stats.getConfig().getUidStateConfig().length];
uidStateValues[STATE_SCREEN] = SCREEN_STATE_ON;
- uidStateValues[STATE_PROCESS_STATE] = PROCESS_STATE_ANY;
+ uidStateValues[STATE_PROCESS_STATE] = PROCESS_STATE_UNSPECIFIED;
for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) {
UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i);
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index c543b6d..cda86fa 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -500,6 +500,7 @@
&& parentInfo != null
&& parentInfo.id == mCurrentUserId) {
// only the children of the current user can be started in background
+ mCurrentUserId = userId;
startProfileLocked(userId);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0bd8441..530c03f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3292,6 +3292,12 @@
return false;
}
+ // Check if activity is top activity of its task fragment - this prevents any trampolines
+ // followed by enterPictureInPictureMode() calls by an activity from below in its stack.
+ if (getTaskFragment() == null || getTaskFragment().getTopNonFinishingActivity() != this) {
+ return false;
+ }
+
// Check to see if PiP is supported for the display this container is on.
if (mDisplayContent != null && !mDisplayContent.mDwpcHelper.isEnteringPipAllowed(
getUid())) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 07d39d9..aa6c13e 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -44,6 +44,7 @@
#include <batteryservice/include/batteryservice/BatteryServiceConstants.h>
#include <binder/IServiceManager.h>
#include <com_android_input_flags.h>
+#include <include/gestures.h>
#include <input/Input.h>
#include <input/PointerController.h>
#include <input/PrintTools.h>
@@ -217,6 +218,23 @@
jmethodID init;
} gInputSensorInfo;
+static struct TouchpadHardwarePropertiesOffsets {
+ jclass clazz;
+ jmethodID constructor;
+ jfieldID left;
+ jfieldID top;
+ jfieldID right;
+ jfieldID bottom;
+ jfieldID resX;
+ jfieldID resY;
+ jfieldID orientationMinimum;
+ jfieldID orientationMaximum;
+ jfieldID maxFingerCount;
+ jfieldID isButtonPad;
+ jfieldID isHapticPad;
+ jfieldID reportsPressure;
+} gTouchpadHardwarePropertiesOffsets;
+
// --- Global functions ---
template<typename T>
@@ -2632,6 +2650,45 @@
return arr;
}
+static jobject nativeGetTouchpadHardwareProperties(JNIEnv* env, jobject nativeImplObj,
+ jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ std::optional<HardwareProperties> touchpadHardwareProperties =
+ im->getInputManager()->getReader().getTouchpadHardwareProperties(deviceId);
+
+ jobject hwPropsObj = env->NewObject(gTouchpadHardwarePropertiesOffsets.clazz,
+ gTouchpadHardwarePropertiesOffsets.constructor);
+ if (hwPropsObj == NULL || !touchpadHardwareProperties.has_value()) {
+ return hwPropsObj;
+ }
+ env->SetFloatField(hwPropsObj, gTouchpadHardwarePropertiesOffsets.left,
+ touchpadHardwareProperties->left);
+ env->SetFloatField(hwPropsObj, gTouchpadHardwarePropertiesOffsets.top,
+ touchpadHardwareProperties->top);
+ env->SetFloatField(hwPropsObj, gTouchpadHardwarePropertiesOffsets.right,
+ touchpadHardwareProperties->right);
+ env->SetFloatField(hwPropsObj, gTouchpadHardwarePropertiesOffsets.bottom,
+ touchpadHardwareProperties->bottom);
+ env->SetFloatField(hwPropsObj, gTouchpadHardwarePropertiesOffsets.resX,
+ touchpadHardwareProperties->res_x);
+ env->SetFloatField(hwPropsObj, gTouchpadHardwarePropertiesOffsets.resY,
+ touchpadHardwareProperties->res_y);
+ env->SetFloatField(hwPropsObj, gTouchpadHardwarePropertiesOffsets.orientationMinimum,
+ touchpadHardwareProperties->orientation_minimum);
+ env->SetFloatField(hwPropsObj, gTouchpadHardwarePropertiesOffsets.orientationMaximum,
+ touchpadHardwareProperties->orientation_maximum);
+ env->SetIntField(hwPropsObj, gTouchpadHardwarePropertiesOffsets.maxFingerCount,
+ touchpadHardwareProperties->max_finger_cnt);
+ env->SetBooleanField(hwPropsObj, gTouchpadHardwarePropertiesOffsets.isButtonPad,
+ touchpadHardwareProperties->is_button_pad);
+ env->SetBooleanField(hwPropsObj, gTouchpadHardwarePropertiesOffsets.isHapticPad,
+ touchpadHardwareProperties->is_haptic_pad);
+ env->SetBooleanField(hwPropsObj, gTouchpadHardwarePropertiesOffsets.reportsPressure,
+ touchpadHardwareProperties->reports_pressure);
+
+ return hwPropsObj;
+}
+
static jboolean nativeEnableSensor(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint sensorType, jint samplingPeriodUs,
jint maxBatchReportLatencyUs) {
@@ -2831,6 +2888,9 @@
{"setKeyRepeatConfiguration", "(II)V", (void*)nativeSetKeyRepeatConfiguration},
{"getSensorList", "(I)[Landroid/hardware/input/InputSensorInfo;",
(void*)nativeGetSensorList},
+ {"getTouchpadHardwareProperties",
+ "(I)Lcom/android/server/input/TouchpadHardwareProperties;",
+ (void*)nativeGetTouchpadHardwareProperties},
{"enableSensor", "(IIII)Z", (void*)nativeEnableSensor},
{"disableSensor", "(II)V", (void*)nativeDisableSensor},
{"flushSensor", "(II)Z", (void*)nativeFlushSensor},
@@ -3090,6 +3150,42 @@
GET_METHOD_ID(gInputSensorInfo.init, gInputSensorInfo.clazz, "<init>", "()V");
+ // TouchpadHardawreProperties
+ FIND_CLASS(gTouchpadHardwarePropertiesOffsets.clazz,
+ "com/android/server/input/TouchpadHardwareProperties");
+ gTouchpadHardwarePropertiesOffsets.clazz =
+ reinterpret_cast<jclass>(env->NewGlobalRef(gTouchpadHardwarePropertiesOffsets.clazz));
+
+ // Get the constructor ID
+ GET_METHOD_ID(gTouchpadHardwarePropertiesOffsets.constructor,
+ gTouchpadHardwarePropertiesOffsets.clazz, "<init>", "()V");
+
+ // Get the field IDs
+ GET_FIELD_ID(gTouchpadHardwarePropertiesOffsets.left, gTouchpadHardwarePropertiesOffsets.clazz,
+ "mLeft", "F");
+ GET_FIELD_ID(gTouchpadHardwarePropertiesOffsets.top, gTouchpadHardwarePropertiesOffsets.clazz,
+ "mTop", "F");
+ GET_FIELD_ID(gTouchpadHardwarePropertiesOffsets.right, gTouchpadHardwarePropertiesOffsets.clazz,
+ "mRight", "F");
+ GET_FIELD_ID(gTouchpadHardwarePropertiesOffsets.bottom,
+ gTouchpadHardwarePropertiesOffsets.clazz, "mBottom", "F");
+ GET_FIELD_ID(gTouchpadHardwarePropertiesOffsets.resX, gTouchpadHardwarePropertiesOffsets.clazz,
+ "mResX", "F");
+ GET_FIELD_ID(gTouchpadHardwarePropertiesOffsets.resY, gTouchpadHardwarePropertiesOffsets.clazz,
+ "mResY", "F");
+ GET_FIELD_ID(gTouchpadHardwarePropertiesOffsets.orientationMinimum,
+ gTouchpadHardwarePropertiesOffsets.clazz, "mOrientationMinimum", "F");
+ GET_FIELD_ID(gTouchpadHardwarePropertiesOffsets.orientationMaximum,
+ gTouchpadHardwarePropertiesOffsets.clazz, "mOrientationMaximum", "F");
+ GET_FIELD_ID(gTouchpadHardwarePropertiesOffsets.maxFingerCount,
+ gTouchpadHardwarePropertiesOffsets.clazz, "mMaxFingerCount", "S");
+ GET_FIELD_ID(gTouchpadHardwarePropertiesOffsets.isButtonPad,
+ gTouchpadHardwarePropertiesOffsets.clazz, "mIsButtonPad", "Z");
+ GET_FIELD_ID(gTouchpadHardwarePropertiesOffsets.isHapticPad,
+ gTouchpadHardwarePropertiesOffsets.clazz, "mIsHapticPad", "Z");
+ GET_FIELD_ID(gTouchpadHardwarePropertiesOffsets.reportsPressure,
+ gTouchpadHardwarePropertiesOffsets.clazz, "mReportsPressure", "Z");
+
return 0;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 62400eb..ab0f0c1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -1557,6 +1557,19 @@
when(ddcMock.getIdleScreenRefreshRateTimeoutLuxThresholdPoint())
.thenReturn(List.of(getIdleScreenRefreshRateTimeoutLuxThresholdPoint(6, 1000),
getIdleScreenRefreshRateTimeoutLuxThresholdPoint(100, 800)));
+ director.defaultDisplayDeviceUpdated(ddcMock); // set the updated ddc
+
+ // idleScreenRefreshRate config is still null because the flag to enable subscription to
+ // light sensor is not enabled
+ sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 4));
+ waitForIdleSync();
+ assertNull(director.getBrightnessObserver().getIdleScreenRefreshRateConfig());
+
+ // Flag to subscribe to light sensor is enabled, and the sensor subscription is attempted
+ // again to load the idle screen refresh rate config
+ when(mDisplayManagerFlags.isIdleScreenConfigInSubscribingLightSensorEnabled())
+ .thenReturn(true);
+ director.defaultDisplayDeviceUpdated(ddcMock); // set the updated ddc
// Sensor reads 5 lux
sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 5));
@@ -1575,7 +1588,6 @@
waitForIdleSync();
assertEquals(new SurfaceControl.IdleScreenRefreshRateConfig(800),
director.getBrightnessObserver().getIdleScreenRefreshRateConfig());
-
}
@Test
@@ -3231,6 +3243,8 @@
@Test
public void testNotifyDefaultDisplayDeviceUpdated() {
+ when(mDisplayManagerFlags.isIdleScreenConfigInSubscribingLightSensorEnabled())
+ .thenReturn(true);
when(mResources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
.thenReturn(75);
when(mResources.getInteger(R.integer.config_defaultRefreshRate))
@@ -3289,6 +3303,8 @@
new float[]{ BrightnessSynchronizer.brightnessIntToFloat(5) }, /* delta= */ 0);
assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThresholds(),
new float[]{10}, /* delta= */ 0);
+ assertNull(director.getBrightnessObserver()
+ .getIdleScreenRefreshRateTimeoutLuxThresholdPoints());
// Notify that the default display is updated, such that DisplayDeviceConfig has new values
@@ -3300,6 +3316,10 @@
/* defaultRefreshRateInHbmSunlight= */ 75,
/* lowPowerSupportedModes= */ List.of(),
/* lowLightBlockingZoneSupportedModes= */ List.of());
+ List<IdleScreenRefreshRateTimeoutLuxThresholdPoint>
+ idleScreenRefreshRateTimeoutLuxThresholdPoints =
+ List.of(getIdleScreenRefreshRateTimeoutLuxThresholdPoint(0, 1500),
+ getIdleScreenRefreshRateTimeoutLuxThresholdPoint(50, 1000));
when(displayDeviceConfig.getRefreshRateData()).thenReturn(refreshRateData);
when(displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50);
when(displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55);
@@ -3311,6 +3331,8 @@
.thenReturn(new float[]{0.21f});
when(displayDeviceConfig.getHighAmbientBrightnessThresholds())
.thenReturn(new float[]{2100});
+ when(displayDeviceConfig.getIdleScreenRefreshRateTimeoutLuxThresholdPoint())
+ .thenReturn(idleScreenRefreshRateTimeoutLuxThresholdPoints);
director.defaultDisplayDeviceUpdated(displayDeviceConfig);
// Verify the new values are from the freshly loaded DisplayDeviceConfig.
@@ -3329,6 +3351,9 @@
new float[]{30}, /* delta= */ 0);
assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 65);
assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 75);
+ assertEquals(director.getBrightnessObserver()
+ .getIdleScreenRefreshRateTimeoutLuxThresholdPoints(),
+ idleScreenRefreshRateTimeoutLuxThresholdPoints);
// Notify that the default display is updated, such that DeviceConfig has new values
FakeDeviceConfig config = mInjector.getDeviceConfig();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
index 37d8f2f..c5157b3 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
@@ -206,22 +206,8 @@
20,
1234L,
UID_0,
- BatteryConsumer.PROCESS_STATE_FOREGROUND,
- 1000L,
- "CustomConsumer1",
- 1650.0f,
- 450.0f,
- 0L
- );
- verify(statsLogger).buildStatsEvent(
- 1000L,
- 20000L,
- 10000L,
- 20,
- 1234L,
- UID_0,
- BatteryConsumer.PROCESS_STATE_BACKGROUND,
- 2000L,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ 0L,
"CustomConsumer1",
1650.0f,
450.0f,
@@ -236,10 +222,10 @@
UID_0,
BatteryConsumer.PROCESS_STATE_FOREGROUND,
1000L,
- "CustomConsumer2",
+ "CustomConsumer1",
1650.0f,
- 500.0f,
- 800L
+ 100.0f,
+ 0L
);
verify(statsLogger).buildStatsEvent(
1000L,
@@ -250,6 +236,20 @@
UID_0,
BatteryConsumer.PROCESS_STATE_BACKGROUND,
2000L,
+ "CustomConsumer1",
+ 1650.0f,
+ 350.0f,
+ 0L
+ );
+ verify(statsLogger).buildStatsEvent(
+ 1000L,
+ 20000L,
+ 10000L,
+ 20,
+ 1234L,
+ UID_0,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ 0,
"CustomConsumer2",
1650.0f,
500.0f,
@@ -352,21 +352,12 @@
for (PowerComponentUsage componentProto : consumerProto.powerComponents) {
final int componentId = componentProto.component;
- if (componentId < BatteryConsumer.POWER_COMPONENT_COUNT) {
- assertEquals(message + " for component " + componentId,
- convertMahToDc(consumer.getConsumedPower(componentId)),
- componentProto.powerDeciCoulombs);
- assertEquals(message + " for component " + componentId,
- consumer.getUsageDurationMillis(componentId),
- componentProto.durationMillis);
- } else {
- assertEquals(message + " for custom component " + componentId,
- convertMahToDc(consumer.getConsumedPowerForCustomComponent(componentId)),
- componentProto.powerDeciCoulombs);
- assertEquals(message + " for custom component " + componentId,
- consumer.getUsageDurationForCustomComponentMillis(componentId),
- componentProto.durationMillis);
- }
+ assertEquals(message + " for component " + componentId,
+ convertMahToDc(consumer.getConsumedPower(componentId)),
+ componentProto.powerDeciCoulombs);
+ assertEquals(message + " for component " + componentId,
+ consumer.getUsageDurationMillis(componentId),
+ componentProto.durationMillis);
}
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
@@ -506,13 +497,13 @@
BatteryConsumer.POWER_COMPONENT_SCREEN, 300)
.setConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU, 400)
- .setConsumedPowerForCustomComponent(
+ .setConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 450)
- .setConsumedPowerForCustomComponent(
+ .setConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1, 500)
.setUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU, 600)
- .setUsageDurationForCustomComponentMillis(
+ .setUsageDurationMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1, 800);
final BatteryConsumer.Key keyFg = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU,
@@ -533,6 +524,17 @@
.setConsumedPower(keyCached, 9400, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
.setUsageDurationMillis(keyFgs, 8400);
+ final BatteryConsumer.Key keyCustomFg = uidBuilder.getKey(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND);
+ final BatteryConsumer.Key keyCustomBg = uidBuilder.getKey(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND);
+ uidBuilder.setConsumedPower(
+ keyCustomFg, 100, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+ uidBuilder.setConsumedPower(
+ keyCustomBg, 350, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+
builder.getOrCreateUidBatteryConsumerBuilder(UID_1)
.setPackageWithHighestDrain("myPackage1")
.setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 1234);
@@ -554,11 +556,11 @@
.setConsumedPower(
BatteryConsumer.POWER_COMPONENT_CAMERA, 20150,
BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
- .setConsumedPowerForCustomComponent(
+ .setConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20200)
.setUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU, 20300)
- .setUsageDurationForCustomComponentMillis(
+ .setUsageDurationMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20400);
// Not used; just to make sure extraneous data doesn't mess things up.
@@ -567,7 +569,7 @@
.setConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU, 10100,
BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setConsumedPowerForCustomComponent(
+ .setConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200);
return builder.build();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 374426a..17c7efa 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -532,15 +532,15 @@
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
assertThat(device.getCustomPowerComponentName(componentId0)).isEqualTo("FOO");
assertThat(device.getCustomPowerComponentName(componentId1)).isEqualTo("BAR");
- assertThat(device.getConsumedPowerForCustomComponent(componentId0))
+ assertThat(device.getConsumedPower(componentId0))
.isWithin(PRECISION).of(27.77777);
- assertThat(device.getConsumedPowerForCustomComponent(componentId1))
+ assertThat(device.getConsumedPower(componentId1))
.isWithin(PRECISION).of(55.55555);
UidBatteryConsumer uid = stats.getUidBatteryConsumers().get(0);
- assertThat(uid.getConsumedPowerForCustomComponent(componentId0))
+ assertThat(uid.getConsumedPower(componentId0))
.isWithin(PRECISION).of(8.33333);
- assertThat(uid.getConsumedPowerForCustomComponent(componentId1))
+ assertThat(uid.getConsumedPower(componentId1))
.isWithin(PRECISION).of(8.33333);
return null;
}).when(powerStatsStore).storeBatteryUsageStats(anyLong(), any());
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index 52bb5e8..3ae4c32 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -173,14 +173,15 @@
assertThat(dump).containsMatch(quote("(not on battery, screen off/doze)") + "\\s*"
+ "cpu: 123 apps: 123 duration: 456ms");
assertThat(dump).containsMatch(
- "UID 271: 1200 fg: 1777 bg: 1888 fgs: 1999 cached: 123\\s*"
- + quote("screen=300 cpu=5787 (27s 99ms) cpu:fg=1777 (7s 771ms) "
+ "UID 271: 1200 fg: 1777 bg: 2388 fgs: 1999 cached: 123\\s*"
+ + quote("screen=300 cpu=400 (600ms) cpu:fg=1777 (7s 771ms) "
+ "cpu:bg=1888 (8s 881ms) cpu:fgs=1999 (9s 991ms) "
- + "cpu:cached=123 (456ms) FOO=500") + "\\s*"
+ + "cpu:cached=123 (456ms) FOO=500 (800ms) FOO:bg=500 (800ms)") + "\\s*"
+ quote("(on battery, screen on)") + "\\s*"
- + quote("cpu:fg=1777 (7s 771ms)"));
+ + quote("cpu=1777 (7s 771ms) cpu:fg=1777 (7s 771ms) "
+ + "FOO=500 (800ms) FOO:bg=500 (800ms)"));
assertThat(dump).containsMatch("User 42: 30.0\\s*"
- + quote("cpu=10.0 (30ms) FOO=20.0"));
+ + quote("cpu=10.0 (30ms) FOO=20.0 (40ms)"));
}
@Test
@@ -198,10 +199,10 @@
assertThat(dump).contains("cpu: 20100 apps: 10100 duration: 20s 300ms");
assertThat(dump).contains("FOO: 20200 apps: 10200 duration: 20s 400ms");
assertThat(dump).containsMatch(
- "UID 271: 1200 fg: 1777 bg: 1888 fgs: 1999 cached: 123\\s*"
- + quote("screen=300 cpu=5787 (600ms) cpu:fg=1777 (7s 771ms) "
+ "UID 271: 1200 fg: 1777 bg: 2388 fgs: 1999 cached: 123\\s*"
+ + quote("screen=300 cpu=400 (600ms) cpu:fg=1777 (7s 771ms) "
+ "cpu:bg=1888 (8s 881ms) cpu:fgs=1999 (9s 991ms) "
- + "cpu:cached=123 (456ms) FOO=500"));
+ + "cpu:cached=123 (456ms) FOO=500 (800ms) FOO:bg=500 (800ms)"));
assertThat(dump).containsMatch("User 42: 30.0\\s*"
+ quote("cpu=10.0 (30ms) FOO=20.0"));
}
@@ -225,25 +226,24 @@
.add(stats1)
.add(stats2)
.build();
-
assertBatteryUsageStats(sum, 42345, 50, 2234, 4345, 1234, 1000, 5000, 5000);
final List<UidBatteryConsumer> uidBatteryConsumers =
sum.getUidBatteryConsumers();
for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
if (uidBatteryConsumer.getUid() == APP_UID1) {
- assertUidBatteryConsumer(uidBatteryConsumer, 2124, null,
- 5321, 6900, 532, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 11772,
+ assertUidBatteryConsumer(uidBatteryConsumer, 1200 + 924, null,
+ 5321, 6900, 532, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400 + 345,
POWER_MODEL_UNDEFINED,
- 956, 1167, 1478,
- true, 3554, 3776, 3998, 444, 3554, 15542, 3776, 17762, 3998, 19982,
+ 500 + 456, 1167, 1478,
+ true, 3554, 4732, 3998, 444, 3554, 15542, 3776, 17762, 3998, 19982,
444, 1110);
} else if (uidBatteryConsumer.getUid() == APP_UID2) {
assertUidBatteryConsumer(uidBatteryConsumer, 1332, "bar",
- 1111, 2220, 2, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 5985,
+ 1111, 2220, 2, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444,
BatteryConsumer.POWER_MODEL_POWER_PROFILE,
555, 666, 777,
- true, 1777, 1888, 1999, 321, 1777, 7771, 1888, 8881, 1999, 9991,
+ true, 1777, 2443, 1999, 321, 1777, 7771, 1888, 8881, 1999, 9991,
321, 654);
} else {
fail("Unexpected UID " + uidBatteryConsumer.getUid());
@@ -291,9 +291,6 @@
TypedXmlPullParser parser = Xml.newBinaryPullParser();
parser.setInput(in, StandardCharsets.UTF_8.name());
final BatteryUsageStats fromXml = BatteryUsageStats.createFromXml(parser);
-
- System.out.println("stats = " + stats);
- System.out.println("fromXml = " + fromXml);
assertBatteryUsageStats1(fromXml, true);
}
@@ -336,11 +333,11 @@
builder.getOrCreateUserBatteryConsumerBuilder(USER_ID)
.setConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU, 10)
- .setConsumedPowerForCustomComponent(
+ .setConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20)
.setUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU, 30)
- .setUsageDurationForCustomComponentMillis(
+ .setUsageDurationMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 40);
}
return builder;
@@ -405,11 +402,11 @@
BatteryConsumer.POWER_COMPONENT_SCREEN, screenPower, screenPowerModel)
.setConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU, cpuPower, cpuPowerModel)
- .setConsumedPowerForCustomComponent(
+ .setConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentPower)
.setUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU, cpuDuration)
- .setUsageDurationForCustomComponentMillis(
+ .setUsageDurationMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentDuration);
if (builder.isProcessStateDataNeeded()) {
final BatteryConsumer.Key cpuFgKey = builder.isScreenStateDataNeeded()
@@ -430,6 +427,15 @@
final BatteryConsumer.Key cachedKey = uidBuilder.getKey(
BatteryConsumer.POWER_COMPONENT_CPU,
BatteryConsumer.PROCESS_STATE_CACHED);
+ final BatteryConsumer.Key customBgKey = builder.isScreenStateDataNeeded()
+ ? uidBuilder.getKey(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ BatteryConsumer.SCREEN_STATE_ON,
+ BatteryConsumer.POWER_STATE_BATTERY)
+ : uidBuilder.getKey(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND);
uidBuilder
.setConsumedPower(cpuFgKey, cpuPowerForeground,
BatteryConsumer.POWER_MODEL_POWER_PROFILE)
@@ -442,7 +448,10 @@
.setUsageDurationMillis(cpuFgsKey, cpuDurationFgs)
.setConsumedPower(cachedKey, cpuPowerCached,
BatteryConsumer.POWER_MODEL_POWER_PROFILE)
- .setUsageDurationMillis(cachedKey, cpuDurationCached);
+ .setUsageDurationMillis(cachedKey, cpuDurationCached)
+ .setConsumedPower(customBgKey, customComponentPower,
+ BatteryConsumer.POWER_MODEL_UNDEFINED)
+ .setUsageDurationMillis(customBgKey, customComponentDuration);
}
}
@@ -456,12 +465,12 @@
.setConsumedPower(consumedPower)
.setConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU, cpuPower)
- .setConsumedPowerForCustomComponent(
+ .setConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
customComponentPower)
.setUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU, cpuDuration)
- .setUsageDurationForCustomComponentMillis(
+ .setUsageDurationMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
customComponentDuration);
if (builder.isPowerStateDataNeeded() || builder.isScreenStateDataNeeded()) {
@@ -511,10 +520,10 @@
for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
if (uidBatteryConsumer.getUid() == APP_UID1) {
assertUidBatteryConsumer(uidBatteryConsumer, 1200, "foo",
- 1000, 1500, 500, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 5787,
+ 1000, 1500, 500, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400,
BatteryConsumer.POWER_MODEL_POWER_PROFILE,
500, 600, 800,
- true, 1777, 1888, 1999, 123, 1777, 7771, 1888, 8881, 1999, 9991, 123, 456);
+ true, 1777, 2388, 1999, 123, 1777, 7771, 1888, 8881, 1999, 9991, 123, 456);
} else {
fail("Unexpected UID " + uidBatteryConsumer.getUid());
}
@@ -593,11 +602,11 @@
BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPower);
assertThat(uidBatteryConsumer.getPowerModel(
BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPowerModel);
- assertThat(uidBatteryConsumer.getConsumedPowerForCustomComponent(
+ assertThat(uidBatteryConsumer.getConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(customComponentPower);
assertThat(uidBatteryConsumer.getUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuDuration);
- assertThat(uidBatteryConsumer.getUsageDurationForCustomComponentMillis(
+ assertThat(uidBatteryConsumer.getUsageDurationMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(
customComponentDuration);
assertThat(uidBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1);
@@ -678,11 +687,11 @@
int cpuDuration, int customComponentDuration) {
assertThat(userBatteryConsumer.getConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPower);
- assertThat(userBatteryConsumer.getConsumedPowerForCustomComponent(
+ assertThat(userBatteryConsumer.getConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(customComponentPower);
assertThat(userBatteryConsumer.getUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuDuration);
- assertThat(userBatteryConsumer.getUsageDurationForCustomComponentMillis(
+ assertThat(userBatteryConsumer.getUsageDurationMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(
customComponentDuration);
assertThat(userBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1);
@@ -697,11 +706,11 @@
aggregateBatteryConsumerScopeAllApps);
assertThat(appsBatteryConsumer.getConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPower);
- assertThat(appsBatteryConsumer.getConsumedPowerForCustomComponent(
+ assertThat(appsBatteryConsumer.getConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(customComponentPower);
assertThat(appsBatteryConsumer.getUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuDuration);
- assertThat(appsBatteryConsumer.getUsageDurationForCustomComponentMillis(
+ assertThat(appsBatteryConsumer.getUsageDurationMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(
customComponentDuration);
assertThat(appsBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java
index 4ab706e..5636242 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java
@@ -68,26 +68,26 @@
mStatsRule.apply(calculator);
UidBatteryConsumer uid = mStatsRule.getUidBatteryConsumer(APP_UID);
- assertThat(uid.getConsumedPowerForCustomComponent(
+ assertThat(uid.getConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID))
.isWithin(PRECISION).of(8.333333);
- assertThat(uid.getConsumedPowerForCustomComponent(
+ assertThat(uid.getConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1))
.isWithin(PRECISION).of(33.33333);
final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
- assertThat(deviceBatteryConsumer.getConsumedPowerForCustomComponent(
+ assertThat(deviceBatteryConsumer.getConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID))
.isWithin(PRECISION).of(27.77777);
- assertThat(deviceBatteryConsumer.getConsumedPowerForCustomComponent(
+ assertThat(deviceBatteryConsumer.getConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1))
.isWithin(PRECISION).of(55.55555);
final BatteryConsumer appsBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
- assertThat(appsBatteryConsumer.getConsumedPowerForCustomComponent(
+ assertThat(appsBatteryConsumer.getConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID))
.isWithin(PRECISION).of(27.77777);
- assertThat(appsBatteryConsumer.getConsumedPowerForCustomComponent(
+ assertThat(appsBatteryConsumer.getConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1))
.isWithin(PRECISION).of(55.55555);
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
index 7f7967b..1f5fba6d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
@@ -21,6 +21,7 @@
import static org.mockito.Mockito.mock;
+import android.annotation.NonNull;
import android.os.AggregateBatteryConsumer;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
@@ -129,6 +130,218 @@
}
@Test
+ public void breakdownByState_processScreenAndPower() throws Exception {
+ BatteryUsageStats actual = prepareBatteryUsageStats(true, true, true);
+ String message = "Actual BatteryUsageStats: " + actual;
+
+ assertAggregatedPowerEstimate(message, actual,
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE,
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ 87600000);
+ assertAggregatedPowerEstimate(message, actual,
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS,
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ 54321);
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_ANY,
+ BatteryConsumer.PROCESS_STATE_ANY, 54321);
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_ANY, 54321);
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND, 50020);
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ 4301); // Includes "unspecified" proc state
+
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_ANY, BatteryConsumer.SCREEN_STATE_ON,
+ BatteryConsumer.POWER_STATE_BATTERY, 321);
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND, BatteryConsumer.SCREEN_STATE_ON,
+ BatteryConsumer.POWER_STATE_BATTERY, 20);
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND, BatteryConsumer.SCREEN_STATE_ON,
+ BatteryConsumer.POWER_STATE_BATTERY, 301); // bg + unsp
+
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_ANY, BatteryConsumer.SCREEN_STATE_OTHER,
+ BatteryConsumer.POWER_STATE_BATTERY, 4000);
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND, BatteryConsumer.SCREEN_STATE_OTHER,
+ BatteryConsumer.POWER_STATE_BATTERY, 4000);
+
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_ANY, BatteryConsumer.SCREEN_STATE_OTHER,
+ BatteryConsumer.POWER_STATE_OTHER, 50000);
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND, BatteryConsumer.SCREEN_STATE_OTHER,
+ BatteryConsumer.POWER_STATE_OTHER, 50000);
+
+ actual.close();
+ }
+
+ @Test
+ public void breakdownByState_processAndScreen() throws Exception {
+ BatteryUsageStats actual = prepareBatteryUsageStats(true, true, false);
+ String message = "Actual BatteryUsageStats: " + actual;
+
+ assertAggregatedPowerEstimate(message, actual,
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE,
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ 7600000); // off-battery not included
+ assertAggregatedPowerEstimate(message, actual,
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE,
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.SCREEN_STATE_ON, BatteryConsumer.POWER_STATE_ANY,
+ 600000);
+ assertAggregatedPowerEstimate(message, actual,
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE,
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.SCREEN_STATE_OTHER, BatteryConsumer.POWER_STATE_ANY,
+ 7000000);
+ assertAggregatedPowerEstimate(message, actual,
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS,
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ 4321); // off-battery not included
+ assertAggregatedPowerEstimate(message, actual,
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS,
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.SCREEN_STATE_ON, BatteryConsumer.POWER_STATE_ANY,
+ 321);
+ assertAggregatedPowerEstimate(message, actual,
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS,
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.SCREEN_STATE_OTHER, BatteryConsumer.POWER_STATE_ANY,
+ 4000);
+
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_ANY,
+ BatteryConsumer.PROCESS_STATE_ANY, 4321); // off-battery not included
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_ANY, 4321); // off-battery not included
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND, 20); // off-battery not included
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ 4301); // includes unspecified proc state
+
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_ANY, BatteryConsumer.SCREEN_STATE_ON,
+ BatteryConsumer.POWER_STATE_ANY, 321);
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND, BatteryConsumer.SCREEN_STATE_ON,
+ BatteryConsumer.POWER_STATE_ANY, 20);
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND, BatteryConsumer.SCREEN_STATE_ON,
+ BatteryConsumer.POWER_STATE_ANY, 301);
+
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_ANY, BatteryConsumer.SCREEN_STATE_OTHER,
+ BatteryConsumer.POWER_STATE_ANY, 4000);
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND, BatteryConsumer.SCREEN_STATE_OTHER,
+ BatteryConsumer.POWER_STATE_ANY, 4000);
+
+ actual.close();
+ }
+
+ @Test
+ public void breakdownByState_processStateOnly() throws Exception {
+ BatteryUsageStats actual = prepareBatteryUsageStats(true, false, false);
+ String message = "Actual BatteryUsageStats: " + actual;
+
+ assertAggregatedPowerEstimate(message, actual,
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE,
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ 7600000); // off-battery not included
+ assertAggregatedPowerEstimate(message, actual,
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS,
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ 4321); // off-battery not included
+
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_ANY,
+ BatteryConsumer.PROCESS_STATE_ANY, 4321); // off-battery not included
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_ANY, 4321); // off-battery not included
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND, 20); // off-battery not included
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ 4301); // includes unspecified proc state
+
+ actual.close();
+ }
+
+ private @NonNull BatteryUsageStats prepareBatteryUsageStats(boolean includeProcessStateData,
+ boolean includeScreenStateData, boolean includesPowerStateData) {
+ long[] deviceStats = new long[mCpuStatsArrayLayout.getDeviceStatsArrayLength()];
+ long[] uidStats = new long[mCpuStatsArrayLayout.getUidStatsArrayLength()];
+
+ AggregatedPowerStats aps = new AggregatedPowerStats(mPowerStatsAggregator.getConfig());
+ PowerComponentAggregatedPowerStats stats = aps.getPowerComponentStats(
+ BatteryConsumer.POWER_COMPONENT_CPU);
+ stats.setPowerStatsDescriptor(mPowerStatsDescriptor);
+
+ mCpuStatsArrayLayout.setUidPowerEstimate(uidStats, 1);
+ stats.setUidStats(APP_UID1, new int[]{
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED}, uidStats);
+
+ mCpuStatsArrayLayout.setUidPowerEstimate(uidStats, 20);
+ stats.setUidStats(APP_UID1, new int[]{
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND}, uidStats);
+
+ mCpuStatsArrayLayout.setUidPowerEstimate(uidStats, 300);
+ stats.setUidStats(APP_UID1, new int[]{
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND}, uidStats);
+
+ mCpuStatsArrayLayout.setUidPowerEstimate(uidStats, 4000);
+ stats.setUidStats(APP_UID1, new int[]{
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND}, uidStats);
+
+ mCpuStatsArrayLayout.setUidPowerEstimate(uidStats, 50000);
+ stats.setUidStats(APP_UID1, new int[]{
+ AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND}, uidStats);
+
+ mCpuStatsArrayLayout.setDevicePowerEstimate(deviceStats, 600000);
+ stats.setDeviceStats(new int[]{
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON}, deviceStats);
+
+ mCpuStatsArrayLayout.setDevicePowerEstimate(deviceStats, 7000000);
+ stats.setDeviceStats(new int[]{
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER}, deviceStats);
+
+ mCpuStatsArrayLayout.setDevicePowerEstimate(deviceStats, 80000000);
+ stats.setDeviceStats(new int[]{
+ AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON}, deviceStats);
+
+ return exportToBatteryUsageStats(aps, includeProcessStateData,
+ includeScreenStateData, includesPowerStateData);
+ }
+
+ private @NonNull BatteryUsageStats exportToBatteryUsageStats(AggregatedPowerStats aps,
+ boolean includeProcessStateData, boolean includeScreenStateData,
+ boolean includesPowerStateData) {
+ PowerStatsExporter exporter = new PowerStatsExporter(mPowerStatsStore,
+ mPowerStatsAggregator, /* batterySessionTimeSpanSlackMillis */ 0);
+
+ BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(new String[0], false,
+ includeProcessStateData, includeScreenStateData, includesPowerStateData, 0);
+ exporter.populateBatteryUsageStatsBuilder(builder, aps);
+ return builder.build();
+ }
+
+ @Test
public void breakdownByProcState_fullRange() throws Exception {
breakdownByProcState_fullRange(false, false);
}
@@ -232,19 +445,28 @@
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS,
BatteryConsumer.POWER_COMPONENT_CPU, 7.51016);
+ assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_ANY,
+ BatteryConsumer.PROCESS_STATE_ANY, 4.33);
assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
BatteryConsumer.PROCESS_STATE_ANY, 3.97099);
+ assertUidPowerEstimate(message, actual, APP_UID1,
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
+ BatteryConsumer.PROCESS_STATE_ANY, 0.360);
+
+ assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_ANY,
+ BatteryConsumer.PROCESS_STATE_ANY, 3.538999);
assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU,
BatteryConsumer.PROCESS_STATE_ANY, 3.538999);
UidBatteryConsumer uidScope = actual.getUidBatteryConsumers().stream()
.filter(us -> us.getUid() == APP_UID1).findFirst().orElse(null);
// There shouldn't be any per-procstate data
for (int procState = 0; procState < BatteryConsumer.PROCESS_STATE_COUNT; procState++) {
- if (procState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
- assertThat(uidScope.getConsumedPower(new BatteryConsumer.Dimensions(
- BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_FOREGROUND))).isEqualTo(0);
+ if (procState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+ continue;
}
+ double power = uidScope.getConsumedPower(
+ new BatteryConsumer.Dimensions(BatteryConsumer.POWER_COMPONENT_CPU, procState));
+ assertWithMessage("procState=" + procState).that(power).isEqualTo(0);
}
actual.close();
}
@@ -333,22 +555,34 @@
private void assertAggregatedPowerEstimate(String message, BatteryUsageStats bus, int scope,
int componentId, double expected) {
AggregateBatteryConsumer consumer = bus.getAggregateBatteryConsumer(scope);
- double actual = componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
- ? consumer.getConsumedPower(componentId)
- : consumer.getConsumedPowerForCustomComponent(componentId);
+ double actual = consumer.getConsumedPower(componentId);
+ assertWithMessage(message).that(actual).isWithin(TOLERANCE).of(expected);
+ }
+
+ private void assertAggregatedPowerEstimate(String message, BatteryUsageStats bus, int scope,
+ int componentId, int screenState, int powerState, double expected) {
+ AggregateBatteryConsumer consumer = bus.getAggregateBatteryConsumer(scope);
+ double actual = consumer.getConsumedPower(
+ new BatteryConsumer.Dimensions(componentId, BatteryConsumer.PROCESS_STATE_ANY,
+ screenState, powerState));
assertWithMessage(message).that(actual).isWithin(TOLERANCE).of(expected);
}
private void assertUidPowerEstimate(String message, BatteryUsageStats bus, int uid,
int componentId, int processState, double expected) {
+ assertUidPowerEstimate(message, bus, uid, componentId, processState,
+ BatteryConsumer.SCREEN_STATE_ANY, BatteryConsumer.POWER_STATE_ANY,
+ expected);
+ }
+
+ private void assertUidPowerEstimate(String message, BatteryUsageStats bus, int uid,
+ int componentId, int processState, int screenState, int powerState, double expected) {
List<UidBatteryConsumer> uidScopes = bus.getUidBatteryConsumers();
final UidBatteryConsumer uidScope = uidScopes.stream()
.filter(us -> us.getUid() == uid).findFirst().orElse(null);
assertWithMessage(message).that(uidScope).isNotNull();
- double actual = componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
- ? uidScope.getConsumedPower(
- new BatteryConsumer.Dimensions(componentId, processState))
- : uidScope.getConsumedPowerForCustomComponent(componentId);
+ double actual = uidScope.getConsumedPower(
+ new BatteryConsumer.Dimensions(componentId, processState, screenState, powerState));
assertWithMessage(message).that(actual).isWithin(TOLERANCE).of(expected);
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java
index 9fde61a..c05a910 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java
@@ -16,8 +16,6 @@
package com.android.server.power.stats;
-import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
-
import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
@@ -280,7 +278,7 @@
ScreenPowerStatsLayout layout = new ScreenPowerStatsLayout(descriptor);
long[] stats = new long[descriptor.uidStatsArrayLength];
aggregatedStats.getUidStats(stats, uid,
- new int[]{powerState, screenState, PROCESS_STATE_ANY});
+ new int[]{powerState, screenState, BatteryConsumer.PROCESS_STATE_UNSPECIFIED});
assertThat(layout.getUidPowerEstimate(stats)).isWithin(PRECISION)
.of(expectedScreenPowerEstimate);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index 6a99731..411a610 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -89,7 +89,7 @@
import javax.annotation.Nullable;
@RunWith(AndroidJUnit4.class)
-@EnableFlags(Flags.FLAG_VISIT_PERSON_URI)
+@EnableFlags({Flags.FLAG_VISIT_PERSON_URI, Flags.FLAG_API_RICH_ONGOING})
public class NotificationVisitUrisTest extends UiServiceTestCase {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index ed8ebc8..dd2b845 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -82,6 +82,7 @@
import static com.android.server.notification.ZenModeEventLogger.ACTIVE_RULE_TYPE_MANUAL;
import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
+import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -6672,6 +6673,91 @@
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
}
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void setAutomaticZenRuleState_withActivationOverride_userActionFromAppCanDeactivate() {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
+ .setPackage(mPkg)
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
+ CUSTOM_PKG_UID);
+
+ // User manually turns on rule from SysUI / Settings...
+ mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ new Condition(rule.getConditionId(), "manual-on-from-sysui", STATE_TRUE,
+ SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
+ assertThat(getZenRule(ruleId).isAutomaticActive()).isTrue();
+ assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
+
+ // ... and they can turn it off manually from inside the app.
+ mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ new Condition(rule.getConditionId(), "manual-off-from-app", STATE_FALSE,
+ SOURCE_USER_ACTION), ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
+ assertThat(getZenRule(ruleId).isAutomaticActive()).isFalse();
+ assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void setAutomaticZenRuleState_withDeactivationOverride_userActionFromAppCanActivate() {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
+ .setPackage(mPkg)
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
+ CUSTOM_PKG_UID);
+
+ // Rule is activated due to its schedule.
+ mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ new Condition(rule.getConditionId(), "auto-on-from-app", STATE_TRUE,
+ SOURCE_SCHEDULE), ORIGIN_APP, CUSTOM_PKG_UID);
+ assertThat(getZenRule(ruleId).isAutomaticActive()).isTrue();
+ assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+
+ // User manually turns off rule from SysUI / Settings...
+ mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ new Condition(rule.getConditionId(), "manual-off-from-sysui", STATE_FALSE,
+ SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
+ assertThat(getZenRule(ruleId).isAutomaticActive()).isFalse();
+ assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
+
+ // ... and they can turn it on manually from inside the app.
+ mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ new Condition(rule.getConditionId(), "manual-on-from-app", STATE_TRUE,
+ SOURCE_USER_ACTION), ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
+ assertThat(getZenRule(ruleId).isAutomaticActive()).isTrue();
+ assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void setAutomaticZenRuleState_manualActionFromApp_isNotOverride() {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
+ .setPackage(mPkg)
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
+ CUSTOM_PKG_UID);
+
+ // Rule is manually activated by the user in the app.
+ // This turns the rule on, but is NOT an override...
+ mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ new Condition(rule.getConditionId(), "manual-on-from-app", STATE_TRUE,
+ SOURCE_USER_ACTION), ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
+ assertThat(getZenRule(ruleId).isAutomaticActive()).isTrue();
+ assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+
+ // ... so the app can turn it off when its schedule is over.
+ mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ new Condition(rule.getConditionId(), "auto-off-from-app", STATE_FALSE,
+ SOURCE_SCHEDULE), ORIGIN_APP, CUSTOM_PKG_UID);
+ assertThat(getZenRule(ruleId).isAutomaticActive()).isFalse();
+ assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE);
+ }
+
+ private ZenRule getZenRule(String ruleId) {
+ return checkNotNull(mZenModeHelper.mConfig.automaticRules.get(ruleId),
+ "Didn't find rule with id %s", ruleId);
+ }
+
private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
@Nullable ZenPolicy zenPolicy) {
ZenRule rule = new ZenRule();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 8d1ba5b..c788f3b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -243,6 +243,18 @@
doReturn(embedded).when(mActivityStack.top()).isEmbedded();
}
+ void setTopActivityVisible(boolean isVisible) {
+ doReturn(isVisible).when(mActivityStack.top()).isVisible();
+ }
+
+ void setTopActivityVisibleRequested(boolean isVisibleRequested) {
+ doReturn(isVisibleRequested).when(mActivityStack.top()).isVisibleRequested();
+ }
+
+ void setTopActivityFillsParent(boolean fillsParent) {
+ doReturn(fillsParent).when(mActivityStack.top()).fillsParent();
+ }
+
void setTopActivityInMultiWindowMode(boolean multiWindowMode) {
doReturn(multiWindowMode).when(mActivityStack.top()).inMultiWindowMode();
if (multiWindowMode) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
index 7760051..05f6ed6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
@@ -26,6 +26,8 @@
import androidx.annotation.NonNull;
+import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType;
+
/**
* Robot implementation for {@link AppCompatConfiguration}.
*/
@@ -99,6 +101,32 @@
.getThinLetterboxHeightPx();
}
+ void setLetterboxActivityCornersRounded(boolean rounded) {
+ doReturn(rounded).when(mAppCompatConfiguration).isLetterboxActivityCornersRounded();
+ }
+
+ void setLetterboxEducationEnabled(boolean enabled) {
+ doReturn(enabled).when(mAppCompatConfiguration).getIsEducationEnabled();
+ }
+
+ void setLetterboxActivityCornersRadius(int cornerRadius) {
+ doReturn(cornerRadius).when(mAppCompatConfiguration).getLetterboxActivityCornersRadius();
+ }
+
+ void setLetterboxBackgroundType(@LetterboxBackgroundType int backgroundType) {
+ doReturn(backgroundType).when(mAppCompatConfiguration).getLetterboxBackgroundType();
+ }
+
+ void setLetterboxBackgroundWallpaperBlurRadiusPx(int blurRadiusPx) {
+ doReturn(blurRadiusPx).when(mAppCompatConfiguration)
+ .getLetterboxBackgroundWallpaperBlurRadiusPx();
+ }
+
+ void setLetterboxBackgroundWallpaperDarkScrimAlpha(float darkScrimAlpha) {
+ doReturn(darkScrimAlpha).when(mAppCompatConfiguration)
+ .getLetterboxBackgroundWallpaperDarkScrimAlpha();
+ }
+
void checkToNextLeftStop(boolean invoked) {
verify(mAppCompatConfiguration, times(invoked ? 1 : 0))
.movePositionForHorizontalReachabilityToNextLeftStop(anyBoolean());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxOverrideTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxOverrideTest.java
new file mode 100644
index 0000000..af7f881
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxOverrideTest.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_OVERRIDE_UNSET;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link AppCompatLetterboxOverrides}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:AppCompatLetterboxOverrideTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatLetterboxOverrideTest extends WindowTestsBase {
+
+ @Test
+ public void testIsLetterboxEducationEnabled() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+
+ robot.conf().setLetterboxEducationEnabled(/* enabled */ true);
+ robot.checkLetterboxEducationEnabled(/* enabled */ true);
+
+ robot.conf().setLetterboxEducationEnabled(/* enabled */ false);
+ robot.checkLetterboxEducationEnabled(/* enabled */ false);
+ });
+ }
+
+ @Test
+ public void testShouldLetterboxHaveRoundedCorners() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+
+ robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
+ robot.activity().setTopActivityFillsParent(/* fillsParent */ true);
+ robot.checkShouldLetterboxHaveRoundedCorners(/* expected */ true);
+
+ robot.conf().setLetterboxActivityCornersRounded(/* rounded */ false);
+ robot.checkShouldLetterboxHaveRoundedCorners(/* expected */ false);
+
+ robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
+ robot.activity().setTopActivityFillsParent(/* fillsParent */ false);
+ robot.checkShouldLetterboxHaveRoundedCorners(/* expected */ false);
+ });
+ }
+
+ @Test
+ public void testHasWallpaperBackgroundForLetterbox() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.checkHasWallpaperBackgroundForLetterbox(/* expected */ false);
+
+ robot.invokeCheckWallpaperBackgroundForLetterbox(/* wallpaperShouldBeShown */ false);
+ robot.checkHasWallpaperBackgroundForLetterbox(/* expected */ false);
+
+ robot.invokeCheckWallpaperBackgroundForLetterbox(/* wallpaperShouldBeShown */ true);
+ robot.checkHasWallpaperBackgroundForLetterbox(/* expected */ true);
+
+ robot.invokeCheckWallpaperBackgroundForLetterbox(/* wallpaperShouldBeShown */ true);
+ robot.checkHasWallpaperBackgroundForLetterbox(/* expected */ true);
+
+ robot.invokeCheckWallpaperBackgroundForLetterbox(/* wallpaperShouldBeShown */ false);
+ robot.checkHasWallpaperBackgroundForLetterbox(/* expected */ false);
+ });
+ }
+
+ @Test
+ public void testCheckWallpaperBackgroundForLetterbox() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.checkHasWallpaperBackgroundForLetterbox(/* expected */ false);
+
+ robot.checkWallpaperBackgroundForLetterbox(/* wallpaperShouldBeShown */
+ true, /* expected */ true);
+ robot.checkHasWallpaperBackgroundForLetterbox(/* expected */ true);
+
+ robot.checkWallpaperBackgroundForLetterbox(/* wallpaperShouldBeShown */
+ true, /* expected */ false);
+ robot.checkHasWallpaperBackgroundForLetterbox(/* expected */ true);
+
+ robot.checkWallpaperBackgroundForLetterbox(/* wallpaperShouldBeShown */
+ false, /* expected */ true);
+ robot.checkHasWallpaperBackgroundForLetterbox(/* expected */ false);
+
+ robot.checkWallpaperBackgroundForLetterbox(/* wallpaperShouldBeShown */
+ false, /* expected */ false);
+ robot.checkHasWallpaperBackgroundForLetterbox(/* expected */ false);
+ });
+ }
+
+ @Test
+ public void testLetterboxActivityCornersRadius() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+
+ robot.conf().setLetterboxActivityCornersRadius(/* cornerRadius */ 0);
+ robot.checkLetterboxActivityCornersRadius(/* cornerRadius */ 0);
+
+ robot.conf().setLetterboxActivityCornersRadius(/* cornerRadius */ 37);
+ robot.checkLetterboxActivityCornersRadius(/* cornerRadius */ 37);
+
+ robot.conf().setLetterboxActivityCornersRadius(/* cornerRadius */ 5);
+ robot.checkLetterboxActivityCornersRadius(/* cornerRadius */ 5);
+ });
+ }
+
+ @Test
+ public void testLetterboxActivityCornersRaunded() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+
+ robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
+ robot.checkLetterboxActivityCornersRounded(/* expected */ true);
+
+ robot.conf().setLetterboxActivityCornersRounded(/* rounded */ false);
+ robot.checkLetterboxActivityCornersRounded(/* expected */ false);
+ });
+ }
+
+ @Test
+ public void testLetterboxBackgroundType() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+
+ robot.conf().setLetterboxBackgroundType(LETTERBOX_BACKGROUND_OVERRIDE_UNSET);
+ robot.checkLetterboxBackgroundType(LETTERBOX_BACKGROUND_OVERRIDE_UNSET);
+
+ robot.conf().setLetterboxBackgroundType(LETTERBOX_BACKGROUND_SOLID_COLOR);
+ robot.checkLetterboxBackgroundType(LETTERBOX_BACKGROUND_SOLID_COLOR);
+
+ robot.conf().setLetterboxBackgroundType(LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND);
+ robot.checkLetterboxBackgroundType(LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND);
+
+ robot.conf().setLetterboxBackgroundType(
+ LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING);
+ robot.checkLetterboxBackgroundType(LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING);
+
+ robot.conf().setLetterboxBackgroundType(LETTERBOX_BACKGROUND_WALLPAPER);
+ robot.checkLetterboxBackgroundType(LETTERBOX_BACKGROUND_WALLPAPER);
+ });
+ }
+
+ @Test
+ public void testLetterboxBackgroundWallpaperBlurRadiusPx() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+
+ robot.conf().setLetterboxBackgroundWallpaperBlurRadiusPx(-1);
+ robot.checkLetterboxWallpaperBlurRadiusPx(0);
+
+ robot.conf().setLetterboxBackgroundWallpaperBlurRadiusPx(0);
+ robot.checkLetterboxWallpaperBlurRadiusPx(0);
+
+ robot.conf().setLetterboxBackgroundWallpaperBlurRadiusPx(10);
+ robot.checkLetterboxWallpaperBlurRadiusPx(10);
+ });
+ }
+
+ @Test
+ public void testLetterboxBackgroundWallpaperDarkScrimAlpha() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+
+ robot.conf().setLetterboxBackgroundWallpaperDarkScrimAlpha(-1f);
+ robot.checkLetterboxWallpaperDarkScrimAlpha(0);
+
+ robot.conf().setLetterboxBackgroundWallpaperDarkScrimAlpha(1.1f);
+ robot.checkLetterboxWallpaperDarkScrimAlpha(0);
+
+ robot.conf().setLetterboxBackgroundWallpaperDarkScrimAlpha(0.001f);
+ robot.checkLetterboxWallpaperDarkScrimAlpha(0.001f);
+
+ robot.conf().setLetterboxBackgroundWallpaperDarkScrimAlpha(0.999f);
+ robot.checkLetterboxWallpaperDarkScrimAlpha(0.999f);
+ });
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<LetterboxOverridesRobotTest> consumer) {
+ final LetterboxOverridesRobotTest robot =
+ new LetterboxOverridesRobotTest(mWm, mAtm, mSupervisor);
+ consumer.accept(robot);
+ }
+
+ private static class LetterboxOverridesRobotTest extends AppCompatRobotBase {
+ LetterboxOverridesRobotTest(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor) {
+ super(wm, atm, supervisor);
+ }
+
+ void invokeCheckWallpaperBackgroundForLetterbox(boolean wallpaperShouldBeShown) {
+ getLetterboxOverrides().checkWallpaperBackgroundForLetterbox(wallpaperShouldBeShown);
+ }
+
+ void checkLetterboxEducationEnabled(boolean enabled) {
+ assertEquals(enabled, getLetterboxOverrides().isLetterboxEducationEnabled());
+ }
+
+ void checkShouldLetterboxHaveRoundedCorners(boolean expected) {
+ assertEquals(expected,
+ getLetterboxOverrides().shouldLetterboxHaveRoundedCorners());
+ }
+
+ void checkHasWallpaperBackgroundForLetterbox(boolean expected) {
+ assertEquals(expected,
+ getLetterboxOverrides().hasWallpaperBackgroundForLetterbox());
+ }
+
+ void checkWallpaperBackgroundForLetterbox(boolean wallpaperShouldBeShown,
+ boolean expected) {
+ assertEquals(expected,
+ getLetterboxOverrides().checkWallpaperBackgroundForLetterbox(
+ wallpaperShouldBeShown));
+ }
+
+ void checkLetterboxActivityCornersRadius(int expected) {
+ assertEquals(expected, getLetterboxOverrides().getLetterboxActivityCornersRadius());
+ }
+
+ void checkLetterboxActivityCornersRounded(boolean expected) {
+ assertEquals(expected, getLetterboxOverrides().isLetterboxActivityCornersRounded());
+ }
+
+ void checkLetterboxBackgroundType(@LetterboxBackgroundType int expected) {
+ assertEquals(expected, getLetterboxOverrides().getLetterboxBackgroundType());
+ }
+
+ void checkLetterboxWallpaperBlurRadiusPx(int expected) {
+ assertEquals(expected, getLetterboxOverrides().getLetterboxWallpaperBlurRadiusPx());
+ }
+
+ void checkLetterboxWallpaperDarkScrimAlpha(float expected) {
+ assertEquals(expected, getLetterboxOverrides().getLetterboxWallpaperDarkScrimAlpha(),
+ FLOAT_TOLLERANCE);
+ }
+
+ @NonNull
+ private AppCompatLetterboxOverrides getLetterboxOverrides() {
+ return activity().top().mAppCompatController.getAppCompatLetterboxOverrides();
+ }
+
+ }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java
new file mode 100644
index 0000000..e046f7c
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.RoundedCorner;
+import android.view.RoundedCorners;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link AppCompatLetterboxPolicy}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:AppCompatLetterboxPolicyTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatLetterboxPolicyTest extends WindowTestsBase {
+
+ @Test
+ public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() {
+ runTestScenario((robot) -> {
+ robot.configureWindowStateWithTaskBar(/* hasTaskBarInsetsRoundedCorners */ true);
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
+ robot.activity().setTopActivityVisible(/* isVisible */ true);
+ robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
+ robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
+ robot.resources().configureGetDimensionPixelSize(R.dimen.taskbar_frame_height, 20);
+ robot.setTopActivityTransparentPolicyRunning(/* running */ true);
+ robot.activity().configureTopActivityBounds(new Rect(0, 0, 500, 300));
+
+ robot.resizeMainWindow(/* newWidth */ 499, /* newHeight */ 299);
+ robot.checkWindowStateHasCropBounds(/* expected */ false);
+
+ robot.resizeMainWindow(/* newWidth */ 500, /* newHeight */ 300);
+ robot.checkWindowStateHasCropBounds(/* expected */ true);
+ });
+ }
+
+ @Test
+ public void testGetCropBoundsIfNeeded_noCrop() {
+ runTestScenario((robot) -> {
+ robot.configureWindowStateWithTaskBar(/* hasTaskBarInsetsRoundedCorners */ false);
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
+ robot.activity().setTopActivityVisible(/* isVisible */ true);
+ robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
+ robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
+ robot.resources().configureGetDimensionPixelSize(R.dimen.taskbar_frame_height, 20);
+
+ // Do not apply crop if taskbar is collapsed
+ robot.collapseTaskBar();
+ robot.checkTaskBarIsExpanded(/* expected */ false);
+
+ robot.activity().configureTopActivityBounds(new Rect(50, 25, 150, 75));
+ robot.checkWindowStateHasCropBounds(/* expected */ true);
+ // Expected the same size of the activity.
+ robot.validateWindowStateCropBounds(0, 0, 100, 50);
+ });
+ }
+
+ @Test
+ public void testGetCropBoundsIfNeeded_appliesCrop() {
+ runTestScenario((robot) -> {
+ robot.configureWindowStateWithTaskBar(/* hasTaskBarInsetsRoundedCorners */ true);
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
+ robot.activity().setTopActivityVisible(/* isVisible */ true);
+ robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
+ robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
+ robot.resources().configureGetDimensionPixelSize(R.dimen.taskbar_frame_height, 20);
+
+ // Apply crop if taskbar is expanded.
+ robot.expandTaskBar();
+ robot.checkTaskBarIsExpanded(/* expected */ true);
+
+ robot.activity().configureTopActivityBounds(new Rect(50, 0, 150, 100));
+ robot.checkWindowStateHasCropBounds(/* expected */ true);
+ // The task bar expanded height is removed from the crop height.
+ robot.validateWindowStateCropBounds(0, 0, 100, 80);
+ });
+ }
+
+ @Test
+ public void testGetCropBoundsIfNeeded_appliesCropWithSizeCompatScaling() {
+ runTestScenario((robot) -> {
+ robot.configureWindowStateWithTaskBar(/* hasTaskBarInsetsRoundedCorners */ true);
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
+ robot.activity().setTopActivityVisible(/* isVisible */ true);
+ robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
+ robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
+ robot.resources().configureGetDimensionPixelSize(R.dimen.taskbar_frame_height, 20);
+
+ // Apply crop if taskbar is expanded.
+ robot.expandTaskBar();
+ robot.checkTaskBarIsExpanded(/* expected */ true);
+ robot.activity().setTopActivityInSizeCompatMode(/* isScm */ true);
+ robot.setInvCompatState(/* scale */ 2.0f);
+
+ robot.activity().configureTopActivityBounds(new Rect(50, 0, 150, 100));
+
+ robot.checkWindowStateHasCropBounds(/* expected */ true);
+ // The width and height, considering task bar, are scaled by 2.
+ robot.validateWindowStateCropBounds(0, 0, 200, 160);
+ });
+ }
+
+ @Test
+ public void testGetRoundedCornersRadius_withRoundedCornersFromInsets() {
+ runTestScenario((robot) -> {
+ robot.conf().setLetterboxActivityCornersRadius(-1);
+ robot.configureWindowState();
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
+ robot.activity().setTopActivityVisible(/* isVisible */ true);
+ robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
+ robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
+ robot.resources().configureGetDimensionPixelSize(R.dimen.taskbar_frame_height, 20);
+
+ robot.setInvCompatState(/* scale */ 0.5f);
+ robot.configureInsetsRoundedCorners(new RoundedCorners(
+ /*topLeft=*/ null,
+ /*topRight=*/ null,
+ /*bottomRight=*/ new RoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT,
+ /* configurationRadius */ 15, /*centerX=*/ 1, /*centerY=*/ 1),
+ /*bottomLeft=*/ new RoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT,
+ 30 /*2 is to test selection of the min radius*/,
+ /*centerX=*/ 1, /*centerY=*/ 1)
+ ));
+ robot.checkWindowStateRoundedCornersRadius(/* expected */ 7);
+ });
+ }
+
+
+ @Test
+ public void testGetRoundedCornersRadius_withLetterboxActivityCornersRadius() {
+ runTestScenario((robot) -> {
+ robot.conf().setLetterboxActivityCornersRadius(15);
+ robot.configureWindowState();
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
+ robot.activity().setTopActivityVisible(/* isVisible */ true);
+ robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
+ robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
+ robot.resources().configureGetDimensionPixelSize(R.dimen.taskbar_frame_height, 20);
+ robot.setInvCompatState(/* scale */ 0.5f);
+
+ robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ true);
+ robot.checkWindowStateRoundedCornersRadius(/* expected */ 7);
+
+ robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
+ robot.checkWindowStateRoundedCornersRadius(/* expected */ 7);
+
+ robot.activity().setTopActivityVisibleRequested(/* isVisibleRequested */ false);
+ robot.activity().setTopActivityVisible(/* isVisible */ false);
+ robot.checkWindowStateRoundedCornersRadius(/* expected */ 0);
+
+ robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ true);
+ robot.checkWindowStateRoundedCornersRadius(/* expected */ 7);
+ });
+ }
+
+ @Test
+ public void testGetRoundedCornersRadius_noScalingApplied() {
+ runTestScenario((robot) -> {
+ robot.conf().setLetterboxActivityCornersRadius(15);
+ robot.configureWindowState();
+ robot.activity().createActivityWithComponent();
+ robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
+ robot.activity().setTopActivityVisible(/* isVisible */ true);
+ robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
+ robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
+ robot.resources().configureGetDimensionPixelSize(R.dimen.taskbar_frame_height, 20);
+
+ robot.setInvCompatState(/* scale */ -1f);
+ robot.checkWindowStateRoundedCornersRadius(/* expected */ 15);
+
+ robot.setInvCompatState(/* scale */ 0f);
+ robot.checkWindowStateRoundedCornersRadius(/* expected */ 15);
+
+ robot.setInvCompatState(/* scale */ 1f);
+ robot.checkWindowStateRoundedCornersRadius(/* expected */ 15);
+ });
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<LetterboxPolicyRobotTest> consumer) {
+ final LetterboxPolicyRobotTest robot = new LetterboxPolicyRobotTest(mWm, mAtm, mSupervisor);
+ consumer.accept(robot);
+ }
+
+ private static class LetterboxPolicyRobotTest extends AppCompatRobotBase {
+
+ static final int TASKBAR_COLLAPSED_HEIGHT = 10;
+ static final int TASKBAR_EXPANDED_HEIGHT = 20;
+ private static final int SCREEN_WIDTH = 200;
+ private static final int SCREEN_HEIGHT = 100;
+ static final Rect TASKBAR_COLLAPSED_BOUNDS = new Rect(0,
+ SCREEN_HEIGHT - TASKBAR_COLLAPSED_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
+ static final Rect TASKBAR_EXPANDED_BOUNDS = new Rect(0,
+ SCREEN_HEIGHT - TASKBAR_EXPANDED_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
+
+ @NonNull
+ private final WindowState mWindowState;
+ @Nullable
+ private InsetsSource mTaskbar;
+ @Nullable
+ private InsetsState mInsetsState;
+
+ LetterboxPolicyRobotTest(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor) {
+ super(wm, atm, supervisor);
+ mWindowState = mock(WindowState.class);
+ }
+
+ @Override
+ void onPostActivityCreation(@NonNull ActivityRecord activity) {
+ super.onPostActivityCreation(activity);
+ spyOn(getAspectRatioPolicy());
+ spyOn(getTransparentPolicy());
+ }
+
+ void configureWindowStateWithTaskBar(boolean hasInsetsRoundedCorners) {
+ configureWindowState(/* withTaskBar */ true, hasInsetsRoundedCorners);
+ }
+
+ void configureWindowState() {
+ configureWindowState(/* withTaskBar */ false, /* hasInsetsRoundedCorners */ false);
+ }
+
+ void configureInsetsRoundedCorners(@NonNull RoundedCorners roundedCorners) {
+ mInsetsState.setRoundedCorners(roundedCorners);
+ }
+
+ private void configureWindowState(boolean withTaskBar, boolean hasInsetsRoundedCorners) {
+ mInsetsState = new InsetsState();
+ if (withTaskBar) {
+ mTaskbar = new InsetsSource(/*id=*/ 0,
+ WindowInsets.Type.navigationBars());
+ if (hasInsetsRoundedCorners) {
+ mTaskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
+ }
+ mTaskbar.setVisible(true);
+ mInsetsState.addSource(mTaskbar);
+ }
+ mWindowState.mInvGlobalScale = 1f;
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+ doReturn(mInsetsState).when(mWindowState).getInsetsState();
+ doReturn(attrs).when(mWindowState).getAttrs();
+ doReturn(true).when(mWindowState).isDrawn();
+ doReturn(true).when(mWindowState).isOnScreen();
+ doReturn(false).when(mWindowState).isLetterboxedForDisplayCutout();
+ doReturn(true).when(mWindowState).areAppWindowBoundsLetterboxed();
+ }
+
+ void setInvCompatState(float scale) {
+ mWindowState.mInvGlobalScale = scale;
+ }
+
+ void setTopActivityInLetterboxAnimation(boolean inLetterboxAnimation) {
+ doReturn(inLetterboxAnimation).when(activity().top()).isInLetterboxAnimation();
+ }
+
+ void setTopActivityTransparentPolicyRunning(boolean running) {
+ doReturn(running).when(getTransparentPolicy()).isRunning();
+ }
+
+ void setIsLetterboxedForFixedOrientationAndAspectRatio(boolean isLetterboxed) {
+ doReturn(isLetterboxed).when(getAspectRatioPolicy())
+ .isLetterboxedForFixedOrientationAndAspectRatio();
+ }
+
+ void resizeMainWindow(int newWidth, int newHeight) {
+ mWindowState.mRequestedWidth = newWidth;
+ mWindowState.mRequestedHeight = newHeight;
+ }
+
+ void collapseTaskBar() {
+ mTaskbar.setFrame(TASKBAR_COLLAPSED_BOUNDS);
+ }
+
+ void expandTaskBar() {
+ mTaskbar.setFrame(TASKBAR_EXPANDED_BOUNDS);
+ }
+
+ void checkWindowStateHasCropBounds(boolean expected) {
+ final Rect cropBounds = getAppCompatLetterboxPolicy().getCropBoundsIfNeeded(
+ mWindowState);
+ if (expected) {
+ assertNotNull(cropBounds);
+ } else {
+ assertNull(cropBounds);
+ }
+ }
+
+ void checkTaskBarIsExpanded(boolean expected) {
+ final InsetsSource expandedTaskBar = AppCompatUtils.getExpandedTaskbarOrNull(
+ mWindowState);
+ if (expected) {
+ assertNotNull(expandedTaskBar);
+ } else {
+ assertNull(expandedTaskBar);
+ }
+ }
+
+ void checkWindowStateRoundedCornersRadius(int expected) {
+ assertEquals(expected, getAppCompatLetterboxPolicy()
+ .getRoundedCornersRadius(mWindowState));
+ }
+
+ void validateWindowStateCropBounds(int left, int top, int right, int bottom) {
+ final Rect cropBounds = getAppCompatLetterboxPolicy().getCropBoundsIfNeeded(
+ mWindowState);
+ assertEquals(left, cropBounds.left);
+ assertEquals(top, cropBounds.top);
+ assertEquals(right, cropBounds.right);
+ assertEquals(bottom, cropBounds.bottom);
+ }
+
+ @NonNull
+ private AppCompatAspectRatioPolicy getAspectRatioPolicy() {
+ return activity().top().mAppCompatController.getAppCompatAspectRatioPolicy();
+ }
+
+ @NonNull
+ private TransparentPolicy getTransparentPolicy() {
+ return activity().top().mAppCompatController.getTransparentPolicy();
+ }
+
+ @NonNull
+ private AppCompatLetterboxPolicy getAppCompatLetterboxPolicy() {
+ return activity().top().mAppCompatController.getAppCompatLetterboxPolicy();
+ }
+
+ }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatResourcesRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatResourcesRobot.java
new file mode 100644
index 0000000..05e9a0f
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatResourcesRobot.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.content.res.Resources;
+
+import androidx.annotation.DimenRes;
+import androidx.annotation.NonNull;
+
+/**
+ * Robot for managing {@link Resources} in unit tests.
+ */
+public class AppCompatResourcesRobot {
+
+ @NonNull
+ private final Resources mResources;
+
+ AppCompatResourcesRobot(@NonNull Resources resources) {
+ mResources = resources;
+ spyOn(mResources);
+ }
+
+ void configureGetDimensionPixelSize(@DimenRes int resId, int value) {
+ doReturn(value).when(mResources).getDimensionPixelSize(resId);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
index 4e58e1d..5f2a63a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
@@ -37,6 +37,8 @@
private final AppCompatConfigurationRobot mConfigurationRobot;
@NonNull
private final AppCompatComponentPropRobot mOptPropRobot;
+ @NonNull
+ private final AppCompatResourcesRobot mResourcesRobot;
AppCompatRobotBase(@NonNull WindowManagerService wm,
@NonNull ActivityTaskManagerService atm,
@@ -48,6 +50,7 @@
mConfigurationRobot =
new AppCompatConfigurationRobot(wm.mAppCompatConfiguration);
mOptPropRobot = new AppCompatComponentPropRobot(wm);
+ mResourcesRobot = new AppCompatResourcesRobot(wm.mContext.getResources());
}
AppCompatRobotBase(@NonNull WindowManagerService wm,
@@ -60,7 +63,7 @@
* Specific Robots can override this method to add operation to run on a newly created
* {@link ActivityRecord}. Common case is to invoke spyOn().
*
- * @param activity THe newly created {@link ActivityRecord}.
+ * @param activity The newly created {@link ActivityRecord}.
*/
@CallSuper
void onPostActivityCreation(@NonNull ActivityRecord activity) {
@@ -81,7 +84,6 @@
return mConfigurationRobot;
}
- @NonNull
void applyOnConf(@NonNull Consumer<AppCompatConfigurationRobot> consumer) {
consumer.accept(mConfigurationRobot);
}
@@ -91,7 +93,6 @@
return mActivityRobot;
}
- @NonNull
void applyOnActivity(@NonNull Consumer<AppCompatActivityRobot> consumer) {
consumer.accept(mActivityRobot);
}
@@ -101,8 +102,16 @@
return mOptPropRobot;
}
- @NonNull
void applyOnProp(@NonNull Consumer<AppCompatComponentPropRobot> consumer) {
consumer.accept(mOptPropRobot);
}
+
+ @NonNull
+ AppCompatResourcesRobot resources() {
+ return mResourcesRobot;
+ }
+
+ void applyOnResources(@NonNull Consumer<AppCompatResourcesRobot> consumer) {
+ consumer.accept(mResourcesRobot);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
deleted file mode 100644
index 8947522..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.Nullable;
-import android.compat.testing.PlatformCompatChangeRule;
-import android.content.ComponentName;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.platform.test.annotations.Presubmit;
-import android.view.InsetsSource;
-import android.view.InsetsState;
-import android.view.RoundedCorner;
-import android.view.RoundedCorners;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.R;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-
-/**
- * Test class for {@link LetterboxUiController}.
- *
- * Build/Install/Run:
- * atest WmTests:LetterboxUiControllerTest
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class LetterboxUiControllerTest extends WindowTestsBase {
- private static final int TASKBAR_COLLAPSED_HEIGHT = 10;
- private static final int TASKBAR_EXPANDED_HEIGHT = 20;
- private static final int SCREEN_WIDTH = 200;
- private static final int SCREEN_HEIGHT = 100;
- private static final Rect TASKBAR_COLLAPSED_BOUNDS = new Rect(0,
- SCREEN_HEIGHT - TASKBAR_COLLAPSED_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
- private static final Rect TASKBAR_EXPANDED_BOUNDS = new Rect(0,
- SCREEN_HEIGHT - TASKBAR_EXPANDED_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
-
- @Rule
- public TestRule compatChangeRule = new PlatformCompatChangeRule();
-
- private ActivityRecord mActivity;
- private Task mTask;
- private DisplayContent mDisplayContent;
- private AppCompatConfiguration mAppCompatConfiguration;
- private final Rect mLetterboxedPortraitTaskBounds = new Rect();
-
- @Before
- public void setUp() throws Exception {
- mActivity = setUpActivityWithComponent();
-
- mAppCompatConfiguration = mWm.mAppCompatConfiguration;
- spyOn(mAppCompatConfiguration);
- }
-
- @Test
- public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() {
- final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
- WindowInsets.Type.navigationBars());
- taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
- final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
- final Rect opaqueBounds = new Rect(0, 0, 500, 300);
- doReturn(opaqueBounds).when(mActivity).getBounds();
- // Activity is translucent
- spyOn(mActivity.mAppCompatController.getTransparentPolicy());
- when(mActivity.mAppCompatController.getTransparentPolicy()
- .isRunning()).thenReturn(true);
-
- // Makes requested sizes different
- mainWindow.mRequestedWidth = opaqueBounds.width() - 1;
- mainWindow.mRequestedHeight = opaqueBounds.height() - 1;
- final AppCompatLetterboxPolicy letterboxPolicy =
- mActivity.mAppCompatController.getAppCompatLetterboxPolicy();
- assertNull(letterboxPolicy.getCropBoundsIfNeeded(mainWindow));
-
- // Makes requested sizes equals
- mainWindow.mRequestedWidth = opaqueBounds.width();
- mainWindow.mRequestedHeight = opaqueBounds.height();
- assertNotNull(letterboxPolicy.getCropBoundsIfNeeded(mainWindow));
- }
-
- @Test
- public void testGetCropBoundsIfNeeded_noCrop() {
- final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
- WindowInsets.Type.navigationBars());
- final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
-
- // Do not apply crop if taskbar is collapsed
- taskbar.setFrame(TASKBAR_COLLAPSED_BOUNDS);
- assertNull(AppCompatUtils.getExpandedTaskbarOrNull(mainWindow));
-
- mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, SCREEN_HEIGHT / 4,
- SCREEN_WIDTH - SCREEN_WIDTH / 4, SCREEN_HEIGHT - SCREEN_HEIGHT / 4);
-
- final AppCompatLetterboxPolicy letterboxPolicy =
- mActivity.mAppCompatController.getAppCompatLetterboxPolicy();
-
- final Rect noCrop = letterboxPolicy.getCropBoundsIfNeeded(mainWindow);
- assertNotEquals(null, noCrop);
- assertEquals(0, noCrop.left);
- assertEquals(0, noCrop.top);
- assertEquals(mLetterboxedPortraitTaskBounds.width(), noCrop.right);
- assertEquals(mLetterboxedPortraitTaskBounds.height(), noCrop.bottom);
- }
-
- @Test
- public void testGetCropBoundsIfNeeded_appliesCrop() {
- final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
- WindowInsets.Type.navigationBars());
- taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
- final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
-
- // Apply crop if taskbar is expanded
- taskbar.setFrame(TASKBAR_EXPANDED_BOUNDS);
- assertNotNull(AppCompatUtils.getExpandedTaskbarOrNull(mainWindow));
-
- mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, 0, SCREEN_WIDTH - SCREEN_WIDTH / 4,
- SCREEN_HEIGHT);
-
- final AppCompatLetterboxPolicy letterboxPolicy =
- mActivity.mAppCompatController.getAppCompatLetterboxPolicy();
- final Rect crop = letterboxPolicy.getCropBoundsIfNeeded(mainWindow);
- assertNotEquals(null, crop);
- assertEquals(0, crop.left);
- assertEquals(0, crop.top);
- assertEquals(mLetterboxedPortraitTaskBounds.width(), crop.right);
- assertEquals(mLetterboxedPortraitTaskBounds.height() - TASKBAR_EXPANDED_HEIGHT,
- crop.bottom);
- }
-
- @Test
- public void testGetCropBoundsIfNeeded_appliesCropWithSizeCompatScaling() {
- final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
- WindowInsets.Type.navigationBars());
- taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
- final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
- final float scaling = 2.0f;
-
- // Apply crop if taskbar is expanded
- taskbar.setFrame(TASKBAR_EXPANDED_BOUNDS);
- assertNotNull(AppCompatUtils.getExpandedTaskbarOrNull(mainWindow));
- // With SizeCompat scaling
- doReturn(true).when(mActivity).inSizeCompatMode();
- mainWindow.mInvGlobalScale = scaling;
-
- mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, 0, SCREEN_WIDTH - SCREEN_WIDTH / 4,
- SCREEN_HEIGHT);
-
- final int appWidth = mLetterboxedPortraitTaskBounds.width();
- final int appHeight = mLetterboxedPortraitTaskBounds.height();
-
- final AppCompatLetterboxPolicy letterboxPolicy =
- mActivity.mAppCompatController.getAppCompatLetterboxPolicy();
- final Rect crop = letterboxPolicy.getCropBoundsIfNeeded(mainWindow);
- assertNotEquals(null, crop);
- assertEquals(0, crop.left);
- assertEquals(0, crop.top);
- assertEquals((int) (appWidth * scaling), crop.right);
- assertEquals((int) ((appHeight - TASKBAR_EXPANDED_HEIGHT) * scaling), crop.bottom);
- }
-
- @Test
- public void testGetRoundedCornersRadius_withRoundedCornersFromInsets() {
- final float invGlobalScale = 0.5f;
- final int expectedRadius = 7;
- final int configurationRadius = 15;
-
- final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null);
- mainWindow.mInvGlobalScale = invGlobalScale;
- final InsetsState insets = mainWindow.getInsetsState();
-
- RoundedCorners roundedCorners = new RoundedCorners(
- /*topLeft=*/ null,
- /*topRight=*/ null,
- /*bottomRight=*/ new RoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT,
- configurationRadius, /*centerX=*/ 1, /*centerY=*/ 1),
- /*bottomLeft=*/ new RoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT,
- configurationRadius * 2 /*2 is to test selection of the min radius*/,
- /*centerX=*/ 1, /*centerY=*/ 1)
- );
- insets.setRoundedCorners(roundedCorners);
- mAppCompatConfiguration.setLetterboxActivityCornersRadius(-1);
-
- assertEquals(expectedRadius, mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
- .getRoundedCornersRadius(mainWindow));
- }
-
- @Test
- public void testGetRoundedCornersRadius_withLetterboxActivityCornersRadius() {
- final float invGlobalScale = 0.5f;
- final int expectedRadius = 7;
- final int configurationRadius = 15;
-
- final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null);
- mainWindow.mInvGlobalScale = invGlobalScale;
- mAppCompatConfiguration.setLetterboxActivityCornersRadius(configurationRadius);
-
- final AppCompatLetterboxPolicy letterboxPolicy =
- mActivity.mAppCompatController.getAppCompatLetterboxPolicy();
-
- doReturn(true).when(mActivity).isInLetterboxAnimation();
- assertEquals(expectedRadius, letterboxPolicy.getRoundedCornersRadius(mainWindow));
-
- doReturn(false).when(mActivity).isInLetterboxAnimation();
- assertEquals(expectedRadius, letterboxPolicy.getRoundedCornersRadius(mainWindow));
-
- doReturn(false).when(mActivity).isVisibleRequested();
- doReturn(false).when(mActivity).isVisible();
- assertEquals(0, letterboxPolicy.getRoundedCornersRadius(mainWindow));
-
- doReturn(true).when(mActivity).isInLetterboxAnimation();
- assertEquals(expectedRadius, letterboxPolicy.getRoundedCornersRadius(mainWindow));
- }
-
- @Test
- public void testGetRoundedCornersRadius_noScalingApplied() {
- final int configurationRadius = 15;
-
- final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null);
- mAppCompatConfiguration.setLetterboxActivityCornersRadius(configurationRadius);
-
- final AppCompatLetterboxPolicy letterboxPolicy =
- mActivity.mAppCompatController.getAppCompatLetterboxPolicy();
-
- mainWindow.mInvGlobalScale = -1f;
- assertEquals(configurationRadius, letterboxPolicy.getRoundedCornersRadius(mainWindow));
-
- mainWindow.mInvGlobalScale = 0f;
- assertEquals(configurationRadius, letterboxPolicy.getRoundedCornersRadius(mainWindow));
-
- mainWindow.mInvGlobalScale = 1f;
- assertEquals(configurationRadius, letterboxPolicy.getRoundedCornersRadius(mainWindow));
- }
-
- private WindowState mockForGetCropBoundsAndRoundedCorners(@Nullable InsetsSource taskbar) {
- final WindowState mainWindow = mock(WindowState.class);
- final InsetsState insets = new InsetsState();
- final Resources resources = mWm.mContext.getResources();
- final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
-
- final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivity.mAppCompatController
- .getAppCompatAspectRatioPolicy();
-
- mainWindow.mInvGlobalScale = 1f;
- spyOn(resources);
- spyOn(mActivity);
- spyOn(aspectRatioPolicy);
-
- if (taskbar != null) {
- taskbar.setVisible(true);
- insets.addSource(taskbar);
- }
- doReturn(mLetterboxedPortraitTaskBounds).when(mActivity).getBounds();
- doReturn(false).when(mActivity).isInLetterboxAnimation();
- doReturn(true).when(mActivity).isVisible();
- doReturn(true).when(aspectRatioPolicy)
- .isLetterboxedForFixedOrientationAndAspectRatio();
- doReturn(insets).when(mainWindow).getInsetsState();
- doReturn(attrs).when(mainWindow).getAttrs();
- doReturn(true).when(mainWindow).isDrawn();
- doReturn(true).when(mainWindow).isOnScreen();
- doReturn(false).when(mainWindow).isLetterboxedForDisplayCutout();
- doReturn(true).when(mainWindow).areAppWindowBoundsLetterboxed();
- doReturn(true).when(mAppCompatConfiguration).isLetterboxActivityCornersRounded();
- doReturn(TASKBAR_EXPANDED_HEIGHT).when(resources).getDimensionPixelSize(
- R.dimen.taskbar_frame_height);
-
- return mainWindow;
- }
-
- @Test
- public void testIsLetterboxEducationEnabled() {
- mActivity.mAppCompatController.getAppCompatLetterboxOverrides()
- .isLetterboxEducationEnabled();
- verify(mAppCompatConfiguration).getIsEducationEnabled();
- }
-
- private ActivityRecord setUpActivityWithComponent() {
- mDisplayContent = new TestDisplayContent
- .Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build();
- mTask = new TaskBuilder(mSupervisor).setDisplay(mDisplayContent).build();
- final ActivityRecord activity = new ActivityBuilder(mAtm)
- .setOnTop(true)
- .setTask(mTask)
- // Set the component to be that of the test class in order to enable compat changes
- .setComponent(ComponentName.createRelative(mContext,
- com.android.server.wm.LetterboxUiControllerTest.class.getName()))
- .build();
- spyOn(activity.mAppCompatController.getAppCompatCameraOverrides());
- return activity;
- }
-}
diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
index 4143f59..30cc002 100644
--- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
+++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
@@ -171,11 +171,11 @@
.setConsumedPower(123)
.setConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU, 10100)
- .setConsumedPowerForCustomComponent(
+ .setConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200)
.setUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU, 10300)
- .setUsageDurationForCustomComponentMillis(
+ .setUsageDurationMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400);
for (int i = 0; i < 1000; i++) {
@@ -191,10 +191,9 @@
consumerBuilder.setUsageDurationMillis(componentId, componentId * 1000);
}
- consumerBuilder.setConsumedPowerForCustomComponent(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 1234)
- .setUsageDurationForCustomComponentMillis(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 4321);
+ consumerBuilder
+ .setConsumedPower(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 1234)
+ .setUsageDurationMillis(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 4321);
}
return builder.build();
}