Merge "Remove STService dependency on model database"
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index d7163d8..5599e54 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -1933,8 +1933,9 @@
Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
- mTimeTickOptions = BroadcastOptions
- .makeRemovingMatchingFilter(new IntentFilter(Intent.ACTION_TIME_TICK))
+ mTimeTickOptions = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeferUntilActive(true)
.toBundle();
mTimeTickTrigger = new IAlarmListener.Stub() {
@Override
@@ -4252,8 +4253,8 @@
}
}
// And send a TIME_TICK right now, since it is important to get the UI updated.
- mHandler.post(() ->
- getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL));
+ mHandler.post(() -> getContext().sendBroadcastAsUser(mTimeTickIntent,
+ UserHandle.ALL, null, mTimeTickOptions));
} else {
mNonInteractiveStartTime = nowELAPSED;
}
diff --git a/core/api/current.txt b/core/api/current.txt
index e27f6eb..110f818 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10853,6 +10853,7 @@
field public static final String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
field public static final String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list";
field public static final String EXTRA_CHOOSER_CUSTOM_ACTIONS = "android.intent.extra.CHOOSER_CUSTOM_ACTIONS";
+ field public static final String EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION = "android.intent.extra.CHOOSER_PAYLOAD_RESELECTION_ACTION";
field public static final String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER";
field public static final String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
field public static final String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
@@ -19320,7 +19321,9 @@
public final class DisplayManager {
method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int);
+ method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, float, @Nullable android.view.Surface, int);
method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler);
+ method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, float, @Nullable android.view.Surface, int, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback);
method public android.view.Display getDisplay(int);
method public android.view.Display[] getDisplays();
method public android.view.Display[] getDisplays(String);
@@ -24557,8 +24560,8 @@
public final class RouteListingPreference implements android.os.Parcelable {
method public int describeContents();
- method @Nullable public android.content.ComponentName getInAppOnlyItemRoutingReceiver();
method @NonNull public java.util.List<android.media.RouteListingPreference.Item> getItems();
+ method @Nullable public android.content.ComponentName getLinkedItemComponentName();
method public boolean getUseSystemOrdering();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
@@ -24569,37 +24572,39 @@
public static final class RouteListingPreference.Builder {
ctor public RouteListingPreference.Builder();
method @NonNull public android.media.RouteListingPreference build();
- method @NonNull public android.media.RouteListingPreference.Builder setInAppOnlyItemRoutingReceiver(@Nullable android.content.ComponentName);
method @NonNull public android.media.RouteListingPreference.Builder setItems(@NonNull java.util.List<android.media.RouteListingPreference.Item>);
+ method @NonNull public android.media.RouteListingPreference.Builder setLinkedItemComponentName(@Nullable android.content.ComponentName);
method @NonNull public android.media.RouteListingPreference.Builder setUseSystemOrdering(boolean);
}
public static final class RouteListingPreference.Item implements android.os.Parcelable {
method public int describeContents();
- method @Nullable public CharSequence getCustomDisableReasonMessage();
- method public int getDisableReason();
+ method @Nullable public CharSequence getCustomSubtextMessage();
method public int getFlags();
method @NonNull public String getRouteId();
- method public int getSessionParticipantCount();
+ method public int getSelectionBehavior();
+ method public int getSubText();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference.Item> CREATOR;
- field public static final int DISABLE_REASON_AD = 3; // 0x3
- field public static final int DISABLE_REASON_CUSTOM = 5; // 0x5
- field public static final int DISABLE_REASON_DOWNLOADED_CONTENT = 2; // 0x2
- field public static final int DISABLE_REASON_IN_APP_ONLY = 4; // 0x4
- field public static final int DISABLE_REASON_NONE = 0; // 0x0
- field public static final int DISABLE_REASON_SUBSCRIPTION_REQUIRED = 1; // 0x1
field public static final int FLAG_ONGOING_SESSION = 1; // 0x1
field public static final int FLAG_SUGGESTED_ROUTE = 2; // 0x2
+ field public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2; // 0x2
+ field public static final int SELECTION_BEHAVIOR_NONE = 0; // 0x0
+ field public static final int SELECTION_BEHAVIOR_TRANSFER = 1; // 0x1
+ field public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 3; // 0x3
+ field public static final int SUBTEXT_CUSTOM = 10000; // 0x2710
+ field public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 2; // 0x2
+ field public static final int SUBTEXT_NONE = 0; // 0x0
+ field public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 1; // 0x1
}
public static final class RouteListingPreference.Item.Builder {
ctor public RouteListingPreference.Item.Builder(@NonNull String);
method @NonNull public android.media.RouteListingPreference.Item build();
- method @NonNull public android.media.RouteListingPreference.Item.Builder setCustomDisableReasonMessage(@Nullable CharSequence);
- method @NonNull public android.media.RouteListingPreference.Item.Builder setDisableReason(int);
+ method @NonNull public android.media.RouteListingPreference.Item.Builder setCustomSubtextMessage(@Nullable CharSequence);
method @NonNull public android.media.RouteListingPreference.Item.Builder setFlags(int);
- method @NonNull public android.media.RouteListingPreference.Item.Builder setSessionParticipantCount(@IntRange(from=0) int);
+ method @NonNull public android.media.RouteListingPreference.Item.Builder setSelectionBehavior(int);
+ method @NonNull public android.media.RouteListingPreference.Item.Builder setSubText(int);
}
public final class RoutingSessionInfo implements android.os.Parcelable {
@@ -51335,8 +51340,12 @@
method public android.view.SurfaceControl getSurfaceControl();
method public void setChildSurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage);
method public void setSecure(boolean);
+ method public void setSurfaceLifecycle(int);
method public void setZOrderMediaOverlay(boolean);
method public void setZOrderOnTop(boolean);
+ field public static final int SURFACE_LIFECYCLE_DEFAULT = 0; // 0x0
+ field public static final int SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT = 2; // 0x2
+ field public static final int SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY = 1; // 0x1
}
public class TextureView extends android.view.View {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b25e904..bd42578 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -14347,7 +14347,6 @@
field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED";
field public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3; // 0x3
field public static final int ALLOWED_NETWORK_TYPES_REASON_POWER = 1; // 0x1
- field public static final int ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS = 4; // 0x4
field public static final int CALL_WAITING_STATUS_DISABLED = 2; // 0x2
field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1
field public static final int CALL_WAITING_STATUS_FDN_CHECK_FAILURE = 5; // 0x5
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 5f2f623..7a460ee 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -464,8 +464,11 @@
public class WallpaperManager {
method @Nullable public android.graphics.Bitmap getBitmap();
+ method @Nullable public android.graphics.Bitmap getBitmapAsUser(int, boolean, int);
+ method public boolean isLockscreenLiveWallpaperEnabled();
method @Nullable public android.graphics.Rect peekBitmapDimensions();
method @Nullable public android.graphics.Rect peekBitmapDimensions(int);
+ method public void setWallpaperZoomOut(@NonNull android.os.IBinder, float);
method public boolean shouldEnableWideColorGamut();
method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int);
}
@@ -1869,7 +1872,10 @@
public class Build {
method public static boolean is64BitAbi(String);
method public static boolean isDebuggable();
+ field @Nullable public static final String BRAND_FOR_ATTESTATION;
field public static final boolean IS_EMULATOR;
+ field @Nullable public static final String MODEL_FOR_ATTESTATION;
+ field @Nullable public static final String PRODUCT_FOR_ATTESTATION;
}
public static class Build.VERSION {
@@ -2067,6 +2073,7 @@
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String);
+ method public boolean isVisibleBackgroundUsersOnDefaultDisplaySupported();
method public boolean isVisibleBackgroundUsersSupported();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index f9e4081..73f34eb 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -42,7 +42,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.database.DatabaseUtils;
-import android.healthconnect.HealthConnectManager;
+import android.health.connect.HealthConnectManager;
import android.media.AudioAttributes.AttributeUsage;
import android.os.Binder;
import android.os.Build;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 52d1d68..031d351 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -116,7 +116,7 @@
import android.hardware.radio.RadioManager;
import android.hardware.usb.IUsbManager;
import android.hardware.usb.UsbManager;
-import android.healthconnect.HealthServicesInitializer;
+import android.health.connect.HealthServicesInitializer;
import android.location.CountryDetector;
import android.location.ICountryDetector;
import android.location.ILocationManager;
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index f3a83d8..a2cacc5 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -784,6 +784,7 @@
* @return true if the lockscreen wallpaper always uses a wallpaperService, not a static image
* @hide
*/
+ @TestApi
public boolean isLockscreenLiveWallpaperEnabled() {
return mLockscreenLiveWallpaper;
}
@@ -1258,6 +1259,8 @@
* @param which Specifies home or lock screen
* @hide
*/
+ @TestApi
+ @Nullable
public Bitmap getBitmapAsUser(int userId, boolean hardware, @SetWallpaperFlags int which) {
final ColorManagementProxy cmProxy = getColorManagementProxy();
return sGlobals.peekWallpaperBitmap(mContext, true, which, userId, hardware, cmProxy);
@@ -2421,6 +2424,7 @@
*
* @hide
*/
+ @TestApi
public void setWallpaperZoomOut(@NonNull IBinder windowToken, float zoom) {
if (zoom < 0 || zoom > 1f) {
throw new IllegalArgumentException("zoom must be between 0 and 1: " + zoom);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5f1502f..387e8ae 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6151,10 +6151,10 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
- * {@link android.healthconnect.HealthConnectManager}.
+ * {@link android.health.connect.HealthConnectManager}.
*
* @see #getSystemService(String)
- * @see android.healthconnect.HealthConnectManager
+ * @see android.health.connect.HealthConnectManager
*/
public static final String HEALTHCONNECT_SERVICE = "healthconnect";
@@ -6198,6 +6198,16 @@
public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection";
/**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.telephony.satellite.SatelliteManager} for accessing satellite functionality.
+ *
+ * @see #getSystemService(String)
+ * @see android.telephony.satellite.SatelliteManager
+ * @hide
+ */
+ public static final String SATELLITE_SERVICE = "satellite";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5714032..30984b2 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5829,7 +5829,6 @@
* in the sharesheet.
* A reselection action allows the user to return to the source app to change the content being
* shared.
- * @hide
*/
public static final String EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION =
"android.intent.extra.CHOOSER_PAYLOAD_RESELECTION_ACTION";
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 08238ca..d13a97d 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -66,6 +66,7 @@
public final class DisplayManager {
private static final String TAG = "DisplayManager";
private static final boolean DEBUG = false;
+ private static final boolean ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE = false;
private final Context mContext;
private final DisplayManagerGlobal mGlobal;
@@ -938,6 +939,24 @@
/**
* Creates a virtual display.
+ *
+ * @see #createVirtualDisplay(String, int, int, int, float, Surface, int,
+ * Handler, VirtualDisplay.Callback)
+ */
+ @Nullable
+ public VirtualDisplay createVirtualDisplay(@NonNull String name,
+ @IntRange(from = 1) int width,
+ @IntRange(from = 1) int height,
+ @IntRange(from = 1) int densityDpi,
+ float requestedRefreshRate,
+ @Nullable Surface surface,
+ @VirtualDisplayFlag int flags) {
+ return createVirtualDisplay(name, width, height, densityDpi, requestedRefreshRate,
+ surface, flags, null, null);
+ }
+
+ /**
+ * Creates a virtual display.
* <p>
* The content of a virtual display is rendered to a {@link Surface} provided
* by the application.
@@ -987,12 +1006,82 @@
@VirtualDisplayFlag int flags,
@Nullable VirtualDisplay.Callback callback,
@Nullable Handler handler) {
+ return createVirtualDisplay(name, width, height, densityDpi, 0.0f, surface,
+ flags, handler, callback);
+ }
+
+ /**
+ * Creates a virtual display.
+ * <p>
+ * The content of a virtual display is rendered to a {@link Surface} provided
+ * by the application.
+ * </p><p>
+ * The virtual display should be {@link VirtualDisplay#release released}
+ * when no longer needed. Because a virtual display renders to a surface
+ * provided by the application, it will be released automatically when the
+ * process terminates and all remaining windows on it will be forcibly removed.
+ * </p><p>
+ * The behavior of the virtual display depends on the flags that are provided
+ * to this method. By default, virtual displays are created to be private,
+ * non-presentation and unsecure. Permissions may be required to use certain flags.
+ * </p><p>
+ * As of {@link android.os.Build.VERSION_CODES#KITKAT_WATCH}, the surface may
+ * be attached or detached dynamically using {@link VirtualDisplay#setSurface}.
+ * Previously, the surface had to be non-null when {@link #createVirtualDisplay}
+ * was called and could not be changed for the lifetime of the display.
+ * </p><p>
+ * Detaching the surface that backs a virtual display has a similar effect to
+ * turning off the screen.
+ * </p>
+ *
+ * @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 densityDpi The density of the virtual display in dpi, must be greater than 0.
+ * @param requestedRefreshRate The requested refresh rate in frames per second.
+ * For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
+ * 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded
+ * up or down to a divisor of the physical display. If 0 is specified, the virtual
+ * display is refreshed at the physical display refresh rate.
+ * @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:
+ * {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}, {@link #VIRTUAL_DISPLAY_FLAG_PRESENTATION},
+ * {@link #VIRTUAL_DISPLAY_FLAG_SECURE}, {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
+ * or {@link #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
+ * @param handler The handler on which the listener should be invoked, or null
+ * if the listener should be invoked on the calling thread's looper.
+ * @param callback Callback to call when the state of the {@link VirtualDisplay} changes
+ * @return The newly created virtual display, or null if the application could
+ * not create the virtual display.
+ *
+ * @throws SecurityException if the caller does not have permission to create
+ * a virtual display with the specified flags.
+ */
+ @Nullable
+ public VirtualDisplay createVirtualDisplay(@NonNull String name,
+ @IntRange(from = 1) int width,
+ @IntRange(from = 1) int height,
+ @IntRange(from = 1) int densityDpi,
+ float requestedRefreshRate,
+ @Nullable Surface surface,
+ @VirtualDisplayFlag int flags,
+ @Nullable Handler handler,
+ @Nullable VirtualDisplay.Callback callback) {
+ if (!ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE && requestedRefreshRate != 0.0f) {
+ Slog.e(TAG, "Please turn on ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE to use the new api");
+ return null;
+ }
+
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
height, densityDpi);
builder.setFlags(flags);
if (surface != null) {
builder.setSurface(surface);
}
+ if (requestedRefreshRate != 0.0f) {
+ builder.setRequestedRefreshRate(requestedRefreshRate);
+ }
return createVirtualDisplay(null /* projection */, builder.build(), callback, handler,
null /* windowContext */);
}
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index abd647f..f6a2e33 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -109,6 +109,13 @@
@DataClass.PluralOf("displayCategory")
@NonNull private List<String> mDisplayCategories = new ArrayList<>();
+ /**
+ * The refresh rate of a virtual display in frames per second.
+ * If this value is non-zero, this is the requested refresh rate to set.
+ * If this value is zero, the system chooses a default refresh rate.
+ */
+ private float mRequestedRefreshRate = 0.0f;
+
// Code below generated by codegen v1.0.23.
@@ -135,7 +142,8 @@
@Nullable String uniqueId,
int displayIdToMirror,
boolean windowManagerMirroring,
- @NonNull List<String> displayCategories) {
+ @NonNull List<String> displayCategories,
+ float requestedRefreshRate) {
this.mName = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mName);
@@ -161,6 +169,7 @@
this.mDisplayCategories = displayCategories;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mDisplayCategories);
+ this.mRequestedRefreshRate = requestedRefreshRate;
// onConstructed(); // You can define this method to get a callback
}
@@ -256,6 +265,16 @@
return mDisplayCategories;
}
+ /**
+ * The refresh rate of a virtual display in frames per second.
+ * If this value is none zero, this is the requested refresh rate to set.
+ * If this value is zero, the system chooses a default refresh rate.
+ */
+ @DataClass.Generated.Member
+ public float getRequestedRefreshRate() {
+ return mRequestedRefreshRate;
+ }
+
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -276,6 +295,7 @@
if (mUniqueId != null) dest.writeString(mUniqueId);
dest.writeInt(mDisplayIdToMirror);
dest.writeStringList(mDisplayCategories);
+ dest.writeFloat(mRequestedRefreshRate);
}
@Override
@@ -301,6 +321,7 @@
int displayIdToMirror = in.readInt();
List<String> displayCategories = new ArrayList<>();
in.readStringList(displayCategories);
+ float requestedRefreshRate = in.readFloat();
this.mName = name;
com.android.internal.util.AnnotationValidations.validate(
@@ -327,6 +348,7 @@
this.mDisplayCategories = displayCategories;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mDisplayCategories);
+ this.mRequestedRefreshRate = requestedRefreshRate;
// onConstructed(); // You can define this method to get a callback
}
@@ -362,6 +384,7 @@
private int mDisplayIdToMirror;
private boolean mWindowManagerMirroring;
private @NonNull List<String> mDisplayCategories;
+ private float mRequestedRefreshRate;
private long mBuilderFieldsSet = 0L;
@@ -528,10 +551,23 @@
return this;
}
+ /**
+ * The refresh rate of a virtual display in frames per second.
+ * If this value is none zero, this is the requested refresh rate to set.
+ * If this value is zero, the system chooses a default refresh rate.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setRequestedRefreshRate(float value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x400;
+ mRequestedRefreshRate = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull VirtualDisplayConfig build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x400; // Mark builder used
+ mBuilderFieldsSet |= 0x800; // Mark builder used
if ((mBuilderFieldsSet & 0x10) == 0) {
mFlags = 0;
@@ -551,6 +587,9 @@
if ((mBuilderFieldsSet & 0x200) == 0) {
mDisplayCategories = new ArrayList<>();
}
+ if ((mBuilderFieldsSet & 0x400) == 0) {
+ mRequestedRefreshRate = 0.0f;
+ }
VirtualDisplayConfig o = new VirtualDisplayConfig(
mName,
mWidth,
@@ -561,12 +600,13 @@
mUniqueId,
mDisplayIdToMirror,
mWindowManagerMirroring,
- mDisplayCategories);
+ mDisplayCategories,
+ mRequestedRefreshRate);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x400) != 0) {
+ if ((mBuilderFieldsSet & 0x800) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -574,10 +614,10 @@
}
@DataClass.Generated(
- time = 1668534501320L,
+ time = 1671047069703L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java",
- inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nprivate boolean mWindowManagerMirroring\nprivate @com.android.internal.util.DataClass.PluralOf(\"displayCategory\") @android.annotation.NonNull java.util.List<java.lang.String> mDisplayCategories\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
+ inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nprivate boolean mWindowManagerMirroring\nprivate @com.android.internal.util.DataClass.PluralOf(\"displayCategory\") @android.annotation.NonNull java.util.List<java.lang.String> mDisplayCategories\nprivate float mRequestedRefreshRate\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 249f486..832f23c 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -61,6 +61,17 @@
/** The name of the overall product. */
public static final String PRODUCT = getString("ro.product.name");
+ /**
+ * The product name for attestation. In non-default builds (like the AOSP build) the value of
+ * the 'PRODUCT' system property may be different to the one provisioned to KeyMint,
+ * and Keymint attestation would still attest to the product name, it's running on.
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public static final String PRODUCT_FOR_ATTESTATION =
+ getString("ro.product.name_for_attestation");
+
/** The name of the industrial design. */
public static final String DEVICE = getString("ro.product.device");
@@ -89,9 +100,31 @@
/** The consumer-visible brand with which the product/hardware will be associated, if any. */
public static final String BRAND = getString("ro.product.brand");
+ /**
+ * The product brand for attestation. In non-default builds (like the AOSP build) the value of
+ * the 'BRAND' system property may be different to the one provisioned to KeyMint,
+ * and Keymint attestation would still attest to the product brand, it's running on.
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public static final String BRAND_FOR_ATTESTATION =
+ getString("ro.product.brand_for_attestation");
+
/** The end-user-visible name for the end product. */
public static final String MODEL = getString("ro.product.model");
+ /**
+ * The product model for attestation. In non-default builds (like the AOSP build) the value of
+ * the 'MODEL' system property may be different to the one provisioned to KeyMint,
+ * and Keymint attestation would still attest to the product model, it's running on.
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public static final String MODEL_FOR_ATTESTATION =
+ getString("ro.product.model_for_attestation");
+
/** The manufacturer of the device's primary system-on-chip. */
@NonNull
public static final String SOC_MANUFACTURER = SocProperties.soc_manufacturer().orElse(UNKNOWN);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d63d87d..0efd264 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2976,6 +2976,29 @@
}
/**
+ * @hide
+ */
+ public static boolean isVisibleBackgroundUsersOnDefaultDisplayEnabled() {
+ return SystemProperties.getBoolean("fw.visible_bg_users_on_default_display",
+ Resources.getSystem()
+ .getBoolean(R.bool.config_multiuserVisibleBackgroundUsersOnDefaultDisplay));
+ }
+
+ /**
+ * Returns whether the device allows (full) users to be started in background visible in the
+ * {@link android.view.Display#DEFAULT_DISPLAY default display}.
+ *
+ * @return {@code false} for most devices, except passenger-only automotive build (i.e., when
+ * Android runs in a separate system in the back seat to manage the passenger displays).
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean isVisibleBackgroundUsersOnDefaultDisplaySupported() {
+ return isVisibleBackgroundUsersOnDefaultDisplayEnabled();
+ }
+
+ /**
* Checks if the user is visible at the moment.
*
* <p>Roughly speaking, a "visible user" is a user that can present UI on at least one display.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b2d89962..4f96805 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7392,6 +7392,9 @@
*
* Format like "ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0"
* where imeId is ComponentName and subtype is int32.
+ *
+ * <p>Note: This setting is not readable to the app targeting API level 34 or higher. use
+ * {@link android.view.inputmethod.InputMethodManager#getEnabledInputMethodList()} instead.
*/
@Readable(maxTargetSdk = Build.VERSION_CODES.TIRAMISU)
public static final String ENABLED_INPUT_METHODS = "enabled_input_methods";
@@ -15498,6 +15501,25 @@
public static final String AUDIO_SAFE_VOLUME_STATE = "audio_safe_volume_state";
/**
+ * Persisted safe hearding current CSD value. Values are stored as float percentages where
+ * 1.f represents 100% sound dose has been reached.
+ * @hide
+ */
+ public static final String AUDIO_SAFE_CSD_CURRENT_VALUE = "audio_safe_csd_current_value";
+
+ /**
+ * Persisted safe hearding next CSD warning value. Values are stored as float percentages.
+ * @hide
+ */
+ public static final String AUDIO_SAFE_CSD_NEXT_WARNING = "audio_safe_csd_next_warning";
+
+ /**
+ * Persisted safe hearding dose records (see {@link android.media.SoundDoseRecord})
+ * @hide
+ */
+ public static final String AUDIO_SAFE_CSD_DOSE_RECORDS = "audio_safe_csd_dose_records";
+
+ /**
* URL for tzinfo (time zone) updates
* @hide
*/
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 1eb87c9..0bce710 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -20,6 +20,7 @@
import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_SUBLAYER;
import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAYER;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
@@ -51,6 +52,8 @@
import com.android.internal.view.SurfaceCallbackHelper;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
@@ -117,6 +120,36 @@
* may not blend properly as a consequence of not applying alpha to the surface content directly.
*/
public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCallback {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SURFACE_LIFECYCLE_"},
+ value = {SURFACE_LIFECYCLE_DEFAULT,
+ SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY,
+ SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT})
+ public @interface SurfaceLifecycleStrategy {}
+
+ /**
+ * Default lifecycle of the Surface owned by this SurfaceView.
+ *
+ * The default lifecycle matches {@link #SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY}.
+ */
+ public static final int SURFACE_LIFECYCLE_DEFAULT = 0;
+
+ /**
+ * The Surface lifecycle is tied to SurfaceView visibility.
+ *
+ * The Surface is created when the SurfaceView becomes visible, and is destroyed when the
+ * SurfaceView is no longer visible.
+ */
+ public static final int SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY = 1;
+
+ /**
+ * The Surface lifecycle is tied to SurfaceView attachment.
+ * The Surface is created when the SurfaceView first becomes attached, but is not destroyed
+ * until this SurfaceView has been detached from the current window.
+ */
+ public static final int SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT = 2;
+
private static final String TAG = "SurfaceView";
private static final boolean DEBUG = false;
private static final boolean DEBUG_POSITION = false;
@@ -151,6 +184,11 @@
SurfaceControl mBackgroundControl;
private boolean mDisableBackgroundLayer = false;
+ @SurfaceLifecycleStrategy
+ private int mRequestedSurfaceLifecycleStrategy = SURFACE_LIFECYCLE_DEFAULT;
+ @SurfaceLifecycleStrategy
+ private int mSurfaceLifecycleStrategy = SURFACE_LIFECYCLE_DEFAULT;
+
/**
* We use this lock to protect access to mSurfaceControl. Both are accessed on the UI
* thread and the render thread via RenderNode.PositionUpdateListener#positionLost.
@@ -413,6 +451,7 @@
observer.removeOnPreDrawListener(mDrawListener);
mGlobalListenersAdded = false;
}
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " Detaching SV");
mRequestedVisible = false;
@@ -699,6 +738,20 @@
}
}
+ /**
+ * Controls the lifecycle of the Surface owned by this SurfaceView.
+ *
+ * <p>By default, the lifecycycle strategy employed by the SurfaceView is
+ * {@link #SURFACE_LIFECYCLE_DEFAULT}.
+ *
+ * @param lifecycleStrategy The strategy for the lifecycle of the Surface owned by this
+ * SurfaceView.
+ */
+ public void setSurfaceLifecycle(@SurfaceLifecycleStrategy int lifecycleStrategy) {
+ mRequestedSurfaceLifecycleStrategy = lifecycleStrategy;
+ updateSurface();
+ }
+
private void updateOpaqueFlag() {
if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
mSurfaceFlags |= SurfaceControl.OPAQUE;
@@ -780,7 +833,7 @@
mSurfaceLock.lock();
try {
- mDrawingStopped = !mVisible;
+ mDrawingStopped = !surfaceShouldExist();
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "Cur surface: " + mSurface);
@@ -887,6 +940,20 @@
return realSizeChanged;
}
+ private boolean requiresSurfaceControlCreation(boolean formatChanged, boolean visibleChanged) {
+ if (mSurfaceLifecycleStrategy == SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT) {
+ return (mSurfaceControl == null || formatChanged) && mAttachedToWindow;
+ }
+
+ return (mSurfaceControl == null || formatChanged || visibleChanged) && mRequestedVisible;
+ }
+
+ private boolean surfaceShouldExist() {
+ final boolean respectVisibility =
+ mSurfaceLifecycleStrategy != SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT;
+ return mVisible || (!respectVisibility && mAttachedToWindow);
+ }
+
/** @hide */
protected void updateSurface() {
if (!mHaveFrame) {
@@ -921,8 +988,7 @@
final boolean formatChanged = mFormat != mRequestedFormat;
final boolean visibleChanged = mVisible != mRequestedVisible;
final boolean alphaChanged = mAlpha != alpha;
- final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
- && mRequestedVisible;
+ final boolean creating = requiresSurfaceControlCreation(formatChanged, visibleChanged);
final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
getLocationInWindow(mLocation);
@@ -933,10 +999,13 @@
final boolean hintChanged = (viewRoot.getBufferTransformHint() != mTransformHint)
&& mRequestedVisible;
final boolean relativeZChanged = mSubLayer != mRequestedSubLayer;
+ final boolean surfaceLifecycleStrategyChanged =
+ mSurfaceLifecycleStrategy != mRequestedSurfaceLifecycleStrategy;
if (creating || formatChanged || sizeChanged || visibleChanged
|| alphaChanged || windowVisibleChanged || positionChanged
- || layoutSizeChanged || hintChanged || relativeZChanged) {
+ || layoutSizeChanged || hintChanged || relativeZChanged || !mAttachedToWindow
+ || surfaceLifecycleStrategyChanged) {
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "Changes: creating=" + creating
@@ -945,7 +1014,10 @@
+ " hint=" + hintChanged
+ " visible=" + visibleChanged
+ " left=" + (mWindowSpaceLeft != mLocation[0])
- + " top=" + (mWindowSpaceTop != mLocation[1]));
+ + " top=" + (mWindowSpaceTop != mLocation[1])
+ + " z=" + relativeZChanged
+ + " attached=" + mAttachedToWindow
+ + " lifecycleStrategy=" + surfaceLifecycleStrategyChanged);
try {
mVisible = mRequestedVisible;
@@ -959,6 +1031,9 @@
mTransformHint = viewRoot.getBufferTransformHint();
mSubLayer = mRequestedSubLayer;
+ final int previousSurfaceLifecycleStrategy = mSurfaceLifecycleStrategy;
+ mSurfaceLifecycleStrategy = mRequestedSurfaceLifecycleStrategy;
+
mScreenRect.left = mWindowSpaceLeft;
mScreenRect.top = mWindowSpaceTop;
mScreenRect.right = mWindowSpaceLeft + getWidth();
@@ -1000,15 +1075,27 @@
SurfaceHolder.Callback[] callbacks = null;
final boolean surfaceChanged = creating;
- if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) {
- mSurfaceCreated = false;
- notifySurfaceDestroyed();
+ final boolean respectVisibility =
+ mSurfaceLifecycleStrategy != SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT;
+ final boolean previouslyDidNotRespectVisibility =
+ previousSurfaceLifecycleStrategy
+ == SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT;
+ final boolean lifecycleNewlyRespectsVisibility = respectVisibility
+ && previouslyDidNotRespectVisibility;
+ if (mSurfaceCreated) {
+ if (surfaceChanged || (!respectVisibility && !mAttachedToWindow)
+ || (respectVisibility && !mVisible
+ && (visibleChanged || lifecycleNewlyRespectsVisibility))) {
+ mSurfaceCreated = false;
+ notifySurfaceDestroyed();
+ }
}
copySurface(creating /* surfaceControlCreated */, sizeChanged);
- if (mVisible && mSurface.isValid()) {
- if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
+ if (surfaceShouldExist() && mSurface.isValid()) {
+ if (!mSurfaceCreated
+ && (surfaceChanged || (respectVisibility && visibleChanged))) {
mSurfaceCreated = true;
mIsCreating = true;
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
@@ -1019,7 +1106,7 @@
}
}
if (creating || formatChanged || sizeChanged || hintChanged
- || visibleChanged || realSizeChanged) {
+ || (respectVisibility && visibleChanged) || realSizeChanged) {
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "surfaceChanged -- format=" + mFormat
+ " w=" + myWidth + " h=" + myHeight);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 089545a..be58893 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -108,6 +108,7 @@
import android.text.InputFilter;
import android.text.InputType;
import android.text.Layout;
+import android.text.NoCopySpan;
import android.text.ParcelableSpan;
import android.text.PrecomputedText;
import android.text.SegmentFinder;
@@ -905,6 +906,8 @@
private Scroller mScroller;
private TextPaint mTempTextPaint;
+ private Object mTempCursor;
+
@UnsupportedAppUsage
private BoringLayout.Metrics mBoring;
@UnsupportedAppUsage
@@ -10060,10 +10063,8 @@
}
int offset = mLayout.getOffsetForHorizontal(line, point.x);
String textToInsert = gesture.getTextToInsert();
- getEditableText().insert(offset, textToInsert);
- Selection.setSelection(getEditableText(), offset + textToInsert.length());
+ return tryInsertTextForHandwritingGesture(offset, textToInsert, gesture);
// TODO(b/243980426): Insert extra spaces if necessary.
- return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
}
/** @hide */
@@ -10163,12 +10164,11 @@
if (startOffset < endOffset) {
getEditableText().delete(startOffset, endOffset);
Selection.setSelection(getEditableText(), startOffset);
+ return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
} else {
// No whitespace found, so insert a space.
- getEditableText().insert(startOffset, " ");
- Selection.setSelection(getEditableText(), startOffset + 1);
+ return tryInsertTextForHandwritingGesture(startOffset, " ", gesture);
}
- return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
}
/** @hide */
@@ -10252,6 +10252,32 @@
area, segmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
}
+ private int tryInsertTextForHandwritingGesture(
+ int offset, String textToInsert, HandwritingGesture gesture) {
+ // A temporary cursor span is placed at the insertion offset. The span will be pushed
+ // forward when text is inserted, then the real cursor can be placed after the inserted
+ // text. A temporary cursor span is used in order to avoid modifying the real selection span
+ // in the case that the text is filtered out.
+ Editable editableText = getEditableText();
+ if (mTempCursor == null) {
+ mTempCursor = new NoCopySpan.Concrete();
+ }
+ editableText.setSpan(mTempCursor, offset, offset, Spanned.SPAN_POINT_POINT);
+
+ editableText.insert(offset, textToInsert);
+
+ int newOffset = editableText.getSpanStart(mTempCursor);
+ editableText.removeSpan(mTempCursor);
+ if (newOffset == offset) {
+ // The inserted text was filtered out.
+ return handleGestureFailure(gesture);
+ } else {
+ // Place the cursor after the inserted text.
+ Selection.setSelection(editableText, newOffset);
+ return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
+ }
+ }
+
private Pattern getWhitespacePattern() {
if (mWhitespacePattern == null) {
mWhitespacePattern = Pattern.compile("\\s+");
diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS
index 56da8e0..69660ae 100644
--- a/core/java/com/android/internal/app/OWNERS
+++ b/core/java/com/android/internal/app/OWNERS
@@ -11,6 +11,7 @@
per-file *Assist* = file:/core/java/android/service/voice/OWNERS
per-file *Hotword* = file:/core/java/android/service/voice/OWNERS
per-file *Voice* = file:/core/java/android/service/voice/OWNERS
+per-file *VisualQuery* = file:/core/java/android/service/voice/OWNERS
# System language settings
per-file *Locale* = file:platform/packages/apps/Settings:/src/com/android/settings/localepicker/OWNERS
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index fd0d9c5..128de8b 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -88,6 +88,9 @@
optional SettingProto assisted_gps_enabled = 14 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto audio_safe_volume_state = 15 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto audio_safe_csd_current_value = 157 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto audio_safe_csd_next_warning = 158 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto audio_safe_csd_dose_records = 159 [ (android.privacy).dest = DEST_AUTOMATIC ];
reserved 17; // Used to be autofill_compat_mode_allowed_packages
@@ -1087,5 +1090,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 157;
+ // Next tag = 160;
}
diff --git a/core/res/res/drawable/ic_clone_badge.xml b/core/res/res/drawable/ic_clone_badge.xml
new file mode 100644
index 0000000..1682425
--- /dev/null
+++ b/core/res/res/drawable/ic_clone_badge.xml
@@ -0,0 +1,29 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M22,9.5C22,13.642 18.642,17 14.5,17C10.358,17 7,13.642 7,9.5C7,5.358 10.358,2 14.5,2C18.642,2 22,5.358 22,9.5Z"
+ android:fillColor="@android:color/system_neutral2_800"/>
+ <path
+ android:pathData="M9.5,20.333C12.722,20.333 15.333,17.722 15.333,14.5C15.333,11.278 12.722,8.667 9.5,8.667C6.278,8.667 3.667,11.278 3.667,14.5C3.667,17.722 6.278,20.333 9.5,20.333ZM9.5,22C13.642,22 17,18.642 17,14.5C17,10.358 13.642,7 9.5,7C5.358,7 2,10.358 2,14.5C2,18.642 5.358,22 9.5,22Z"
+ android:fillColor="@android:color/system_neutral2_800"
+ android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_clone_icon_badge.xml b/core/res/res/drawable/ic_clone_icon_badge.xml
new file mode 100644
index 0000000..e4eabc8
--- /dev/null
+++ b/core/res/res/drawable/ic_clone_icon_badge.xml
@@ -0,0 +1,35 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="64dp"
+ android:height="64dp"
+ android:viewportWidth="64"
+ android:viewportHeight="64">
+<group
+ android:scaleX=".66"
+ android:scaleY=".66"
+ android:translateX="42"
+ android:translateY="42">
+ <path
+ android:pathData="M22,9.5C22,13.642 18.642,17 14.5,17C10.358,17 7,13.642 7,9.5C7,5.358 10.358,2 14.5,2C18.642,2 22,5.358 22,9.5Z"
+ android:fillColor="@android:color/system_neutral2_800"/>
+ <path
+ android:pathData="M9.5,20.333C12.722,20.333 15.333,17.722 15.333,14.5C15.333,11.278 12.722,8.667 9.5,8.667C6.278,8.667 3.667,11.278 3.667,14.5C3.667,17.722 6.278,20.333 9.5,20.333ZM9.5,22C13.642,22 17,18.642 17,14.5C17,10.358 13.642,7 9.5,7C5.358,7 2,10.358 2,14.5C2,18.642 5.358,22 9.5,22Z"
+ android:fillColor="@android:color/system_neutral2_800"
+ android:fillType="evenOdd"/>
+</group>
+</vector>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 9e735d0..c3366cf 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -207,4 +207,25 @@
be set by OEMs for devices which use eUICCs. -->
<integer-array name="non_removable_euicc_slots"></integer-array>
+ <!-- Device state identifiers and strings for system notifications. The string arrays must have
+ the same length and order as the identifier array. -->
+ <integer-array name="device_state_notification_state_identifiers">
+ <!-- TODO(b/267231269) change to concurrent display identifier when ready -->
+ <item>-1</item>
+ </integer-array>
+ <string-array name="device_state_notification_names">
+ <item>@string/concurrent_display_notification_name</item>
+ </string-array>
+ <string-array name="device_state_notification_active_titles">
+ <item>@string/concurrent_display_notification_active_title</item>
+ </string-array>
+ <string-array name="device_state_notification_active_contents">
+ <item>@string/concurrent_display_notification_active_content</item>
+ </string-array>
+ <string-array name="device_state_notification_thermal_titles">
+ <item>@string/concurrent_display_notification_thermal_title</item>
+ </string-array>
+ <string-array name="device_state_notification_thermal_contents">
+ <item>@string/concurrent_display_notification_thermal_content</item>
+ </string-array>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b8ce7e3..37d5b84 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2735,6 +2735,11 @@
Should be false for most devices, except automotive vehicle with passenger displays. -->
<bool name="config_multiuserVisibleBackgroundUsers">false</bool>
+ <!-- Whether the device allows users to start in background visible on the default display.
+ Should be false for most devices, except passenger-only automotive build (i.e., when
+ Android runs in a separate system in the back seat to manage the passenger displays) -->
+ <bool name="config_multiuserVisibleBackgroundUsersOnDefaultDisplay">false</bool>
+
<!-- Whether to automatically switch to the designated Dock User (the user chosen for
displaying dreams, etc.) after a timeout when the device is docked. -->
<bool name="config_enableTimeoutToDockUserWhenDocked">false</bool>
@@ -5001,6 +5006,26 @@
-->
</integer-array>
+ <!-- The properties for handling UDFPS touch detection
+ <string-array name="config_udfps_touch_detection_options">
+ <item>[detector_type],[sensor_shape],[target_size],[min_ellipse_overlap_percentage]</item>
+ </string-array>
+
+ [detector_type]: 0 for bounding box detector, 1 for ellipse detector
+ [sensor_shape]: 0 for square, 1 for circle
+ [target_size]: percentage (defined as a float of 0-1) of sensor that is considered the target, expands outward from center
+ [min_ellipse_overlap_percentage]: minimum percentage (float from 0-1) needed to be considered a valid overlap
+ [step_size]: size of each step when iterating over sensor pixel grid
+ -->
+ <string-array name="config_udfps_touch_detection_options">
+ <item>0,0,1.0,0,1</item>
+ <item>1,1,1.0,0,1</item>
+ <item>1,1,1.0,.4,1</item>
+ </string-array>
+
+ <!-- The integer index of the selected option in config_udfps_touch_detection_options -->
+ <integer name="config_selected_udfps_touch_detection">2</integer>
+
<!-- An array of arrays of side fingerprint sensor properties relative to each display.
Note: this value is temporary and is expected to be queried directly
from the HAL in the future. -->
@@ -6210,4 +6235,27 @@
trusted certificate using the SHA-256 digest algorithm. -->
<string-array name="config_healthConnectMigrationKnownSigners">
</string-array>
+
+ <!-- The Universal Stylus Initiative (USI) protocol version supported by each display.
+ (@see https://universalstylus.org/).
+
+ The i-th value in this array corresponds to the supported USI version of the i-th display
+ listed in config_displayUniqueIdArray. On a single-display device, the
+ config_displayUniqueIdArray may be empty, in which case the only value in this array should
+ be the USI version for the main built-in display.
+
+ If the display does not support USI, the version value should be an empty string. If the
+ display supports USI, the version must be in the following format:
+ "<major-version>.<minor-version>"
+
+ For example, "", "1.0", and "2.0" are valid values. -->
+ <string-array name="config_displayUsiVersionArray" translatable="false">
+ <item>""</item>
+ </string-array>
+
+ <!-- Whether system apps should be scanned in the stopped state during initial boot.
+ Packages can be added by OEMs in an allowlist, to prevent them from being scanned as
+ "stopped" during initial boot of a device, or after an OTA update. Stopped state of
+ an app is not changed during subsequent reboots. -->
+ <bool name="config_stopSystemPackagesByDefault">false</bool>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e330ee3..da44c2e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5067,6 +5067,13 @@
<string name="managed_profile_label_badge_2">2nd Work <xliff:g id="label" example="Email">%1$s</xliff:g></string>
<string name="managed_profile_label_badge_3">3rd Work <xliff:g id="label" example="Email">%1$s</xliff:g></string>
+ <!--
+ Used to wrap a label for content description for a Clone profile, e.g. "Clone Messenger"
+ instead of Messenger when the Messenger app is cloned.
+ [CHAR LIMIT=20]
+ -->
+ <string name="clone_profile_label_badge">Clone <xliff:g id="label" example="Messenger">%1$s</xliff:g></string>
+
<!-- DO NOT TRANSLATE -->
<string name="time_placeholder">--</string>
@@ -6234,4 +6241,17 @@
<string name="mic_access_on_toast">Microphone is available</string>
<!-- Toast message that is shown when the user mutes the microphone from the keyboard. [CHAR LIMIT=TOAST] -->
<string name="mic_access_off_toast">Microphone is blocked</string>
+
+ <!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] -->
+ <string name="concurrent_display_notification_name">Dual screen</string>
+ <!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] -->
+ <string name="concurrent_display_notification_active_title">Dual screen is on</string>
+ <!-- Content of concurrent display active notification. [CHAR LIMIT=NONE] -->
+ <string name="concurrent_display_notification_active_content"><xliff:g id="app_name" example="Camera app">%1$s</xliff:g> is using both displays to show content</string>
+ <!-- Title of concurrent display thermal notification. [CHAR LIMIT=NONE] -->
+ <string name="concurrent_display_notification_thermal_title">Device is too warm</string>
+ <!-- Content of concurrent display thermal notification. [CHAR LIMIT=NONE] -->
+ <string name="concurrent_display_notification_thermal_content">Dual Screen is unavailable because your phone is getting too warm</string>
+ <!-- Text of device state notification turn off button. [CHAR LIMIT=NONE] -->
+ <string name="device_state_notification_turn_off_button">Turn off</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 76af814..2af91e1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -473,6 +473,7 @@
<java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
<java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
<java-symbol type="bool" name="config_multiuserVisibleBackgroundUsers" />
+ <java-symbol type="bool" name="config_multiuserVisibleBackgroundUsersOnDefaultDisplay" />
<java-symbol type="bool" name="config_enableTimeoutToDockUserWhenDocked" />
<java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
<java-symbol type="xml" name="config_user_types" />
@@ -1063,6 +1064,7 @@
<java-symbol type="string" name="managed_profile_label_badge" />
<java-symbol type="string" name="managed_profile_label_badge_2" />
<java-symbol type="string" name="managed_profile_label_badge_3" />
+ <java-symbol type="string" name="clone_profile_label_badge" />
<java-symbol type="string" name="mediasize_unknown_portrait" />
<java-symbol type="string" name="mediasize_unknown_landscape" />
<java-symbol type="string" name="mediasize_iso_a0" />
@@ -1386,6 +1388,8 @@
<java-symbol type="drawable" name="ic_qs_auto_rotate" />
<java-symbol type="drawable" name="ic_qs_dnd" />
<java-symbol type="drawable" name="ic_qs_one_handed_mode" />
+ <java-symbol type="drawable" name="ic_clone_icon_badge" />
+ <java-symbol type="drawable" name="ic_clone_badge" />
<java-symbol type="drawable" name="sim_light_blue" />
<java-symbol type="drawable" name="sim_light_green" />
@@ -2646,6 +2650,8 @@
<java-symbol type="array" name="config_biometric_sensors" />
<java-symbol type="bool" name="allow_test_udfps" />
<java-symbol type="array" name="config_udfps_sensor_props" />
+ <java-symbol type="array" name="config_udfps_touch_detection_options" />
+ <java-symbol type="integer" name="config_selected_udfps_touch_detection" />
<java-symbol type="array" name="config_sfps_sensor_props" />
<java-symbol type="bool" name="config_is_powerbutton_fps" />
<java-symbol type="array" name="config_udfps_enroll_stage_thresholds" />
@@ -4874,6 +4880,18 @@
<java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" />
<java-symbol type="array" name="config_serviceStateLocationAllowedPackages" />
<java-symbol type="integer" name="config_deviceStateRearDisplay"/>
+ <java-symbol type="array" name="device_state_notification_state_identifiers"/>
+ <java-symbol type="array" name="device_state_notification_names"/>
+ <java-symbol type="array" name="device_state_notification_active_titles"/>
+ <java-symbol type="array" name="device_state_notification_active_contents"/>
+ <java-symbol type="array" name="device_state_notification_thermal_titles"/>
+ <java-symbol type="array" name="device_state_notification_thermal_contents"/>
+ <java-symbol type="string" name="concurrent_display_notification_name"/>
+ <java-symbol type="string" name="concurrent_display_notification_active_title"/>
+ <java-symbol type="string" name="concurrent_display_notification_active_content"/>
+ <java-symbol type="string" name="concurrent_display_notification_thermal_title"/>
+ <java-symbol type="string" name="concurrent_display_notification_thermal_content"/>
+ <java-symbol type="string" name="device_state_notification_turn_off_button"/>
<java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/>
<!-- For app language picker -->
@@ -4892,4 +4910,6 @@
<java-symbol type="string" name="config_mainDisplayShape"/>
<java-symbol type="string" name="config_secondaryDisplayShape"/>
<java-symbol type="array" name="config_displayShapeArray" />
+
+ <java-symbol type="bool" name="config_stopSystemPackagesByDefault"/>
</resources>
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index d0c3e5f..f233c6e 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -36,6 +36,12 @@
}
prebuilt_etc {
+ name: "initial-package-stopped-states.xml",
+ sub_dir: "sysconfig",
+ src: "initial-package-stopped-states.xml",
+}
+
+prebuilt_etc {
name: "preinstalled-packages-platform-overlays.xml",
product_specific: true,
sub_dir: "sysconfig",
diff --git a/data/etc/initial-package-stopped-states.xml b/data/etc/initial-package-stopped-states.xml
new file mode 100644
index 0000000..6bda2c0
--- /dev/null
+++ b/data/etc/initial-package-stopped-states.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+This XML defines an allowlist for packages that should not be scanned in a "stopped" state.
+When this feature is turned on (indicated by the config config_stopSystemPackagesByDefault in
+core/res/res/values/config.xml) packages on the system partition that are encountered by
+the PackageManagerService for the first time are scanned in the "stopped" state. This allowlist
+is also considered while creating new users on the device. Stopped state is not set during
+subsequent reboots.
+
+Example usage
+ 1. <initial-package-state package="com.example.app" stopped="false"/>
+ Indicates that a system package - com.example.app's initial stopped state should not be set
+ by the Package Manager. By default, system apps are marked as stopped.
+ 2. <initial-package-state package="com.example.app" stopped="true"/>
+ Indicates that a system package - com.example.app's initial state should be set by the
+ Package Manager to "stopped=true". It will have the same effect on the
+ package's stopped state even if this package was not included in the allow list.
+ 3. <initial-package-state package="com.example.app"/>
+ Invalid usage.
+-->
+
+<config></config>
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 2830d7eff..4715045 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -801,25 +801,32 @@
));
if (mSpec.isDevicePropertiesAttestationIncluded()) {
+ final String platformReportedBrand = TextUtils.isEmpty(Build.BRAND_FOR_ATTESTATION)
+ ? Build.BRAND : Build.BRAND_FOR_ATTESTATION;
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
- Build.BRAND.getBytes(StandardCharsets.UTF_8)
+ platformReportedBrand.getBytes(StandardCharsets.UTF_8)
));
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
Build.DEVICE.getBytes(StandardCharsets.UTF_8)
));
+ final String platformReportedProduct =
+ TextUtils.isEmpty(Build.PRODUCT_FOR_ATTESTATION) ? Build.PRODUCT :
+ Build.PRODUCT_FOR_ATTESTATION;
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
- Build.PRODUCT.getBytes(StandardCharsets.UTF_8)
+ platformReportedProduct.getBytes(StandardCharsets.UTF_8)
));
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER,
Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8)
));
+ final String platformReportedModel = TextUtils.isEmpty(Build.MODEL_FOR_ATTESTATION)
+ ? Build.MODEL : Build.MODEL_FOR_ATTESTATION;
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL,
- Build.MODEL.getBytes(StandardCharsets.UTF_8)
+ platformReportedModel.getBytes(StandardCharsets.UTF_8)
));
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index c0f3086..13a695a 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -180,8 +180,6 @@
ATRACE_CALL();
if (window) {
- // Ensure the hint session is running here, away from any critical paths
- mHintSessionWrapper.init();
mNativeSurface = std::make_unique<ReliableSurface>(window);
mNativeSurface->init();
if (enableTimeout) {
@@ -1050,6 +1048,10 @@
mSyncDelayDuration = duration;
}
+void CanvasContext::startHintSession() {
+ mHintSessionWrapper.init();
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 0f6b736..deb2109 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -224,6 +224,8 @@
void setSyncDelayDuration(nsecs_t duration);
+ void startHintSession();
+
private:
CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline,
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index f8e2dee..ce1a551 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -45,8 +45,12 @@
pid_t uiThreadId = pthread_gettid_np(pthread_self());
pid_t renderThreadId = getRenderThreadTid();
mContext = mRenderThread.queue().runSync([=, this]() -> CanvasContext* {
- return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory,
- uiThreadId, renderThreadId);
+ CanvasContext* context = CanvasContext::create(mRenderThread, translucent, rootRenderNode,
+ contextFactory, uiThreadId, renderThreadId);
+ if (context != nullptr) {
+ mRenderThread.queue().post([=] { context->startHintSession(); });
+ }
+ return context;
});
mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
}
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index ff0a492..96117ef 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -17,7 +17,6 @@
package android.media;
import android.annotation.IntDef;
-import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
@@ -45,14 +44,24 @@
public final class RouteListingPreference implements Parcelable {
/**
- * {@link Intent} action for apps to take the user to a screen for transferring media playback
- * to the route with the id provided by the extra with key {@link #EXTRA_ROUTE_ID}.
+ * {@link Intent} action that the system uses to take the user the app when the user selects an
+ * {@link Item} whose {@link Item#getSelectionBehavior() selection behavior} is {@link
+ * Item#SELECTION_BEHAVIOR_GO_TO_APP}.
+ *
+ * <p>The launched intent will identify the selected item using the extra identified by {@link
+ * #EXTRA_ROUTE_ID}.
+ *
+ * @see #getLinkedItemComponentName()
+ * @see Item#SELECTION_BEHAVIOR_GO_TO_APP
*/
public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
/**
* {@link Intent} string extra key that contains the {@link Item#getRouteId() id} of the route
* to transfer to, as part of an {@link #ACTION_TRANSFER_MEDIA} intent.
+ *
+ * @see #getLinkedItemComponentName()
+ * @see Item#SELECTION_BEHAVIOR_GO_TO_APP
*/
public static final String EXTRA_ROUTE_ID = "android.media.extra.ROUTE_ID";
@@ -72,12 +81,12 @@
@NonNull private final List<Item> mItems;
private final boolean mUseSystemOrdering;
- @Nullable private final ComponentName mInAppOnlyItemRoutingReceiver;
+ @Nullable private final ComponentName mLinkedItemComponentName;
private RouteListingPreference(Builder builder) {
mItems = builder.mItems;
mUseSystemOrdering = builder.mUseSystemOrdering;
- mInAppOnlyItemRoutingReceiver = builder.mInAppOnlyItemRoutingReceiver;
+ mLinkedItemComponentName = builder.mLinkedItemComponentName;
}
private RouteListingPreference(Parcel in) {
@@ -85,7 +94,7 @@
in.readParcelableList(new ArrayList<>(), Item.class.getClassLoader(), Item.class);
mItems = List.copyOf(items);
mUseSystemOrdering = in.readBoolean();
- mInAppOnlyItemRoutingReceiver = ComponentName.readFromParcel(in);
+ mLinkedItemComponentName = ComponentName.readFromParcel(in);
}
/**
@@ -110,18 +119,20 @@
}
/**
- * Returns a {@link ComponentName} for handling routes disabled via {@link
- * Item#DISABLE_REASON_IN_APP_ONLY}, or null if the user needs to manually navigate to the app
- * in order to route to select the corresponding routes.
+ * Returns a {@link ComponentName} for navigating to the application.
*
- * <p>If the user selects an {@link Item} disabled via {@link Item#DISABLE_REASON_IN_APP_ONLY},
- * and this method returns a non-null {@link ComponentName}, the system takes the user back to
- * the app by launching an intent to the returned {@link ComponentName}, using action {@link
- * #ACTION_TRANSFER_MEDIA}, with the extra {@link #EXTRA_ROUTE_ID}.
+ * <p>Must not be null if any of the {@link #getItems() items} of this route listing preference
+ * has {@link Item#getSelectionBehavior() selection behavior} {@link
+ * Item#SELECTION_BEHAVIOR_GO_TO_APP}.
+ *
+ * <p>The system navigates to the application when the user selects {@link Item} with {@link
+ * Item#SELECTION_BEHAVIOR_GO_TO_APP} by launching an intent to the returned {@link
+ * ComponentName}, using action {@link #ACTION_TRANSFER_MEDIA}, with the extra {@link
+ * #EXTRA_ROUTE_ID}.
*/
@Nullable
- public ComponentName getInAppOnlyItemRoutingReceiver() {
- return mInAppOnlyItemRoutingReceiver;
+ public ComponentName getLinkedItemComponentName() {
+ return mLinkedItemComponentName;
}
// RouteListingPreference Parcelable implementation.
@@ -135,7 +146,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelableList(mItems, flags);
dest.writeBoolean(mUseSystemOrdering);
- ComponentName.writeToParcel(mInAppOnlyItemRoutingReceiver, dest);
+ ComponentName.writeToParcel(mLinkedItemComponentName, dest);
}
// Equals and hashCode.
@@ -151,13 +162,12 @@
RouteListingPreference that = (RouteListingPreference) other;
return mItems.equals(that.mItems)
&& mUseSystemOrdering == that.mUseSystemOrdering
- && Objects.equals(
- mInAppOnlyItemRoutingReceiver, that.mInAppOnlyItemRoutingReceiver);
+ && Objects.equals(mLinkedItemComponentName, that.mLinkedItemComponentName);
}
@Override
public int hashCode() {
- return Objects.hash(mItems, mUseSystemOrdering, mInAppOnlyItemRoutingReceiver);
+ return Objects.hash(mItems, mUseSystemOrdering, mLinkedItemComponentName);
}
/** Builder for {@link RouteListingPreference}. */
@@ -165,7 +175,7 @@
private List<Item> mItems;
private boolean mUseSystemOrdering;
- private ComponentName mInAppOnlyItemRoutingReceiver;
+ private ComponentName mLinkedItemComponentName;
/** Creates a new instance with default values (documented in the setters). */
public Builder() {
@@ -198,14 +208,13 @@
}
/**
- * See {@link #getInAppOnlyItemRoutingReceiver()}.
+ * See {@link #getLinkedItemComponentName()}.
*
* <p>The default value is {@code null}.
*/
@NonNull
- public Builder setInAppOnlyItemRoutingReceiver(
- @Nullable ComponentName inAppOnlyItemRoutingReceiver) {
- mInAppOnlyItemRoutingReceiver = inAppOnlyItemRoutingReceiver;
+ public Builder setLinkedItemComponentName(@Nullable ComponentName linkedItemComponentName) {
+ mLinkedItemComponentName = linkedItemComponentName;
return this;
}
@@ -225,6 +234,29 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(
+ prefix = {"SELECTION_BEHAVIOR_"},
+ value = {
+ SELECTION_BEHAVIOR_NONE,
+ SELECTION_BEHAVIOR_TRANSFER,
+ SELECTION_BEHAVIOR_GO_TO_APP
+ })
+ public @interface SelectionBehavior {}
+
+ /** The corresponding route is not selectable by the user. */
+ public static final int SELECTION_BEHAVIOR_NONE = 0;
+ /** If the user selects the corresponding route, the media transfers to the said route. */
+ public static final int SELECTION_BEHAVIOR_TRANSFER = 1;
+ /**
+ * If the user selects the corresponding route, the system takes the user to the
+ * application.
+ *
+ * <p>The system uses {@link #getLinkedItemComponentName()} in order to navigate to the app.
+ */
+ public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
flag = true,
prefix = {"FLAG_"},
value = {FLAG_ONGOING_SESSION, FLAG_SUGGESTED_ROUTE})
@@ -251,46 +283,40 @@
@IntDef(
prefix = {"DISABLE_REASON_"},
value = {
- DISABLE_REASON_NONE,
- DISABLE_REASON_SUBSCRIPTION_REQUIRED,
- DISABLE_REASON_DOWNLOADED_CONTENT,
- DISABLE_REASON_AD,
- DISABLE_REASON_IN_APP_ONLY,
- DISABLE_REASON_CUSTOM
+ SUBTEXT_NONE,
+ SUBTEXT_SUBSCRIPTION_REQUIRED,
+ SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED,
+ SUBTEXT_AD_ROUTING_DISALLOWED,
+ SUBTEXT_CUSTOM
})
- public @interface DisableReason {}
+ public @interface SubText {}
- /** The corresponding route is available for routing. */
- public static final int DISABLE_REASON_NONE = 0;
+ /** The corresponding route has no associated subtext. */
+ public static final int SUBTEXT_NONE = 0;
/**
- * The corresponding route requires a special subscription in order to be available for
- * routing.
+ * The corresponding route's subtext must indicate that it requires a special subscription
+ * in order to be available for routing.
*/
- public static final int DISABLE_REASON_SUBSCRIPTION_REQUIRED = 1;
+ public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 1;
/**
- * The corresponding route is not available because downloaded content cannot be routed to
- * it.
+ * The corresponding route's subtext must indicate that downloaded content cannot be routed
+ * to it.
*/
- public static final int DISABLE_REASON_DOWNLOADED_CONTENT = 2;
- /** The corresponding route is not available because an ad is in progress. */
- public static final int DISABLE_REASON_AD = 3;
+ public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 2;
/**
- * The corresponding route is only available for routing from within the app.
- *
- * <p>The user may still select the corresponding route if the app provides an {@link
- * #getInAppOnlyItemRoutingReceiver() in-app routing receiver}, in which case the system
- * will take the user to the app.
+ * The corresponding route's subtext must indicate that it is not available because an ad is
+ * in progress.
*/
- public static final int DISABLE_REASON_IN_APP_ONLY = 4;
+ public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 3;
/**
- * The corresponding route is not available because of the reason described by {@link
- * #getCustomDisableReasonMessage()}.
+ * The corresponding route's subtext must be obtained from {@link
+ * #getCustomSubtextMessage()}.
*
* <p>Applications should strongly prefer one of the other disable reasons (for the full
- * list, see {@link #getDisableReason()}) in order to guarantee correct localization and
- * rendering across all form factors.
+ * list, see {@link #getSubText()}) in order to guarantee correct localization and rendering
+ * across all form factors.
*/
- public static final int DISABLE_REASON_CUSTOM = 5;
+ public static final int SUBTEXT_CUSTOM = 10000;
@NonNull
public static final Creator<Item> CREATOR =
@@ -307,29 +333,27 @@
};
@NonNull private final String mRouteId;
+ @SelectionBehavior private final int mSelectionBehavior;
@Flags private final int mFlags;
- @DisableReason private final int mDisableReason;
- private final int mSessionParticipantCount;
- @Nullable private final CharSequence mCustomDisableReasonMessage;
+ @SubText private final int mSubText;
+
+ @Nullable private final CharSequence mCustomSubtextMessage;
private Item(@NonNull Builder builder) {
mRouteId = builder.mRouteId;
+ mSelectionBehavior = builder.mSelectionBehavior;
mFlags = builder.mFlags;
- mDisableReason = builder.mDisableReason;
- mSessionParticipantCount = builder.mSessionParticipantCount;
- mCustomDisableReasonMessage = builder.mCustomDisableReasonMessage;
- validateCustomDisableReasonMessage();
+ mSubText = builder.mSubText;
+ mCustomSubtextMessage = builder.mCustomSubtextMessage;
}
private Item(Parcel in) {
mRouteId = in.readString();
Preconditions.checkArgument(!TextUtils.isEmpty(mRouteId));
+ mSelectionBehavior = in.readInt();
mFlags = in.readInt();
- mDisableReason = in.readInt();
- mSessionParticipantCount = in.readInt();
- Preconditions.checkArgument(mSessionParticipantCount >= 0);
- mCustomDisableReasonMessage = in.readCharSequence();
- validateCustomDisableReasonMessage();
+ mSubText = in.readInt();
+ mCustomSubtextMessage = in.readCharSequence();
}
/**
@@ -343,6 +367,17 @@
}
/**
+ * Returns the behavior that the corresponding route has if the user selects it.
+ *
+ * @see #SELECTION_BEHAVIOR_NONE
+ * @see #SELECTION_BEHAVIOR_TRANSFER
+ * @see #SELECTION_BEHAVIOR_GO_TO_APP
+ */
+ public int getSelectionBehavior() {
+ return mSelectionBehavior;
+ }
+
+ /**
* Returns the flags associated to the route that corresponds to this item.
*
* @see #FLAG_ONGOING_SESSION
@@ -354,49 +389,42 @@
}
/**
- * Returns the reason for the corresponding route to be disabled, or {@link
- * #DISABLE_REASON_NONE} if the route is not disabled.
+ * Returns the type of subtext associated to this route.
*
- * @see #DISABLE_REASON_NONE
- * @see #DISABLE_REASON_SUBSCRIPTION_REQUIRED
- * @see #DISABLE_REASON_DOWNLOADED_CONTENT
- * @see #DISABLE_REASON_AD
- * @see #DISABLE_REASON_IN_APP_ONLY
- * @see #DISABLE_REASON_CUSTOM
+ * <p>Subtext types other than {@link #SUBTEXT_NONE} and {@link #SUBTEXT_CUSTOM} must not
+ * have {@link #SELECTION_BEHAVIOR_TRANSFER}.
+ *
+ * <p>If this method returns {@link #SUBTEXT_CUSTOM}, then the subtext is obtained form
+ * {@link #getCustomSubtextMessage()}.
+ *
+ * @see #SUBTEXT_NONE
+ * @see #SUBTEXT_SUBSCRIPTION_REQUIRED
+ * @see #SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED
+ * @see #SUBTEXT_AD_ROUTING_DISALLOWED
+ * @see #SUBTEXT_CUSTOM
*/
- @DisableReason
- public int getDisableReason() {
- return mDisableReason;
+ @SubText
+ public int getSubText() {
+ return mSubText;
}
/**
- * Returns a non-negative number of participants in the ongoing session (if any) on the
- * corresponding route.
+ * Returns a human-readable {@link CharSequence} providing the subtext for the corresponding
+ * route.
*
- * <p>The system ignores this value if zero, or if {@link #getFlags()} does not include
- * {@link #FLAG_ONGOING_SESSION}.
- */
- public int getSessionParticipantCount() {
- return mSessionParticipantCount;
- }
-
- /**
- * Returns a human-readable {@link CharSequence} describing the reason for this route to be
- * disabled. May be null if {@link #getDisableReason()} is not {@link
- * #DISABLE_REASON_CUSTOM}.
- *
- * <p>This value is ignored if the {@link #getDisableReason() disable reason} for this item
- * is not {@link #DISABLE_REASON_CUSTOM}.
+ * <p>This value is ignored if the {@link #getSubText() subtext} for this item is not {@link
+ * #SUBTEXT_CUSTOM}..
*
* <p>Applications must provide a localized message that matches the system's locale. See
* {@link Locale#getDefault()}.
*
- * <p>This message is a hint for the system. Applications should strongly prefer one of the
- * other disable reasons listed in {@link #getDisableReason()}.
+ * <p>Applications should avoid using custom messages (and instead use one of non-custom
+ * subtexts listed in {@link #getSubText()} in order to guarantee correct visual
+ * representation and localization on all form factors.
*/
@Nullable
- public CharSequence getCustomDisableReasonMessage() {
- return mCustomDisableReasonMessage;
+ public CharSequence getCustomSubtextMessage() {
+ return mCustomSubtextMessage;
}
// Item Parcelable implementation.
@@ -409,10 +437,10 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mRouteId);
+ dest.writeInt(mSelectionBehavior);
dest.writeInt(mFlags);
- dest.writeInt(mDisableReason);
- dest.writeInt(mSessionParticipantCount);
- dest.writeCharSequence(mCustomDisableReasonMessage);
+ dest.writeInt(mSubText);
+ dest.writeCharSequence(mCustomSubtextMessage);
}
// Equals and hashCode.
@@ -427,40 +455,26 @@
}
Item item = (Item) other;
return mRouteId.equals(item.mRouteId)
+ && mSelectionBehavior == item.mSelectionBehavior
&& mFlags == item.mFlags
- && mDisableReason == item.mDisableReason
- && mSessionParticipantCount == item.mSessionParticipantCount
- && TextUtils.equals(
- mCustomDisableReasonMessage, item.mCustomDisableReasonMessage);
+ && mSubText == item.mSubText
+ && TextUtils.equals(mCustomSubtextMessage, item.mCustomSubtextMessage);
}
@Override
public int hashCode() {
return Objects.hash(
- mRouteId,
- mFlags,
- mDisableReason,
- mSessionParticipantCount,
- mCustomDisableReasonMessage);
- }
-
- private void validateCustomDisableReasonMessage() {
- if (mDisableReason == DISABLE_REASON_CUSTOM) {
- Preconditions.checkArgument(
- !TextUtils.isEmpty(mCustomDisableReasonMessage),
- "customDisableReasonMessage must not be null or empty if disable reason is"
- + " DISABLE_REASON_CUSTOM.");
- }
+ mRouteId, mSelectionBehavior, mFlags, mSubText, mCustomSubtextMessage);
}
/** Builder for {@link Item}. */
public static final class Builder {
private final String mRouteId;
+ private int mSelectionBehavior;
private int mFlags;
- private int mDisableReason;
- private int mSessionParticipantCount;
- private CharSequence mCustomDisableReasonMessage;
+ private int mSubText;
+ private CharSequence mCustomSubtextMessage;
/**
* Constructor.
@@ -470,39 +484,51 @@
public Builder(@NonNull String routeId) {
Preconditions.checkArgument(!TextUtils.isEmpty(routeId));
mRouteId = routeId;
- mDisableReason = DISABLE_REASON_NONE;
+ mSelectionBehavior = SELECTION_BEHAVIOR_TRANSFER;
+ mSubText = SUBTEXT_NONE;
}
- /** See {@link Item#getFlags()}. */
+ /**
+ * See {@link Item#getSelectionBehavior()}.
+ *
+ * <p>The default value is {@link #ACTION_TRANSFER_MEDIA}.
+ */
+ @NonNull
+ public Builder setSelectionBehavior(int selectionBehavior) {
+ mSelectionBehavior = selectionBehavior;
+ return this;
+ }
+
+ /**
+ * See {@link Item#getFlags()}.
+ *
+ * <p>The default value is zero (no flags).
+ */
@NonNull
public Builder setFlags(int flags) {
mFlags = flags;
return this;
}
- /** See {@link Item#getDisableReason()}. */
+ /**
+ * See {@link Item#getSubText()}.
+ *
+ * <p>The default value is {@link #SUBTEXT_NONE}.
+ */
@NonNull
- public Builder setDisableReason(int disableReason) {
- mDisableReason = disableReason;
+ public Builder setSubText(int subText) {
+ mSubText = subText;
return this;
}
- /** See {@link Item#getSessionParticipantCount()}. */
+ /**
+ * See {@link Item#getCustomSubtextMessage()}.
+ *
+ * <p>The default value is {@code null}.
+ */
@NonNull
- public Builder setSessionParticipantCount(
- @IntRange(from = 0) int sessionParticipantCount) {
- Preconditions.checkArgument(
- sessionParticipantCount >= 0,
- "sessionParticipantCount must be non-negative.");
- mSessionParticipantCount = sessionParticipantCount;
- return this;
- }
-
- /** See {@link Item#getCustomDisableReasonMessage()}. */
- @NonNull
- public Builder setCustomDisableReasonMessage(
- @Nullable CharSequence customDisableReasonMessage) {
- mCustomDisableReasonMessage = customDisableReasonMessage;
+ public Builder setCustomSubtextMessage(@Nullable CharSequence customSubtextMessage) {
+ mCustomSubtextMessage = customSubtextMessage;
return this;
}
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index ae6f71c..ab680b0 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -38,10 +38,11 @@
<!-- Message for updating an existing app [CHAR LIMIT=NONE] -->
<string name="install_confirm_question_update">Do you want to update this app?</string>
<!-- TODO(b/244413073) Revise the description after getting UX input and UXR on this. -->
- <!-- Message for updating an existing app when updating owner changed [CHAR LIMIT=NONE] -->
+ <!-- Message for updating an existing app when updating owner changed [DO NOT TRANSLATE][CHAR LIMIT=NONE] -->
<string name="install_confirm_question_update_owner_changed">Updates to this app are currently managed by <xliff:g id="existing_update_owner">%1$s</xliff:g>.\n\nBy updating, you\'ll get future updates from <xliff:g id="new_update_owner">%2$s</xliff:g> instead.</string>
- <!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
- <string name="install_confirm_question_update_owner_reminder">Updates to this app are currently managed by <xliff:g id="existing_update_owner">%1$s</xliff:g>.\n\nDo you want to install this update from <xliff:g id="new_update_owner">%2$s</xliff:g>.</string>
+ <!-- TODO(b/244413073) Revise the description after getting UX input and UXR on this. -->
+ <!-- Message for updating an existing app with update owner reminder [DO NOT TRANSLATE][CHAR LIMIT=NONE] -->
+ <string name="install_confirm_question_update_owner_reminder">Updates to this app are currently managed by <xliff:g id="existing_update_owner">%1$s</xliff:g>.\n\nDo you want to install this update from <xliff:g id="new_update_owner">%2$s</xliff:g>?</string>
<!-- [CHAR LIMIT=100] -->
<string name="install_failed">App not installed.</string>
<!-- Reason displayed when installation fails because the package was blocked
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index d708e57..17ef283 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -30,8 +30,8 @@
import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
-import static android.media.RouteListingPreference.Item.DISABLE_REASON_NONE;
import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE;
+import static android.media.RouteListingPreference.Item.SUBTEXT_NONE;
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
@@ -198,10 +198,11 @@
*
* @return disabled reason of device
*/
- @RouteListingPreference.Item.DisableReason
+ @RouteListingPreference.Item.SubText
public int getDisableReason() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null
- ? mItem.getDisableReason() : -1;
+ ? mItem.getSubText()
+ : -1;
}
/**
@@ -210,8 +211,9 @@
* @return true if device has disabled reason
*/
public boolean hasDisabledReason() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null
- && mItem.getDisableReason() != DISABLE_REASON_NONE;
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+ && mItem != null
+ && mItem.getSubText() != SUBTEXT_NONE;
}
/**
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/LargeScreenSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/LargeScreenSettings.java
new file mode 100644
index 0000000..bdd4869
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/LargeScreenSettings.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.settings.backup;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+/** Settings that should not be restored when target device is a large screen
+ * i.e. tablets and foldables in unfolded state
+ */
+public class LargeScreenSettings {
+ private static final float LARGE_SCREEN_MIN_DPS = 600;
+ private static final String LARGE_SCREEN_DO_NOT_RESTORE = "accelerometer_rotation";
+
+ /**
+ * Autorotation setting should not be restored when the target device is a large screen.
+ * (b/243489549)
+ */
+ public static boolean doNotRestoreIfLargeScreenSetting(String key, Context context) {
+ return isLargeScreen(context) && LARGE_SCREEN_DO_NOT_RESTORE.equals(key);
+ }
+
+ // copied from systemui/shared/...Utilities.java
+ // since we don't want to add compile time dependency on sys ui package
+ private static boolean isLargeScreen(Context context) {
+ final WindowManager windowManager = context.getSystemService(WindowManager.class);
+ final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
+ float smallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()),
+ context.getResources().getConfiguration().densityDpi);
+ return smallestWidth >= LARGE_SCREEN_MIN_DPS;
+ }
+
+ private static float dpiFromPx(float size, int densityDpi) {
+ float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
+ return (size / densityRatio);
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 6aa08f2..574fd5a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -37,6 +37,7 @@
import android.provider.Settings;
import android.provider.settings.backup.DeviceSpecificSettings;
import android.provider.settings.backup.GlobalSettings;
+import android.provider.settings.backup.LargeScreenSettings;
import android.provider.settings.backup.SecureSettings;
import android.provider.settings.backup.SystemSettings;
import android.provider.settings.validators.GlobalSettingsValidators;
@@ -812,6 +813,12 @@
continue;
}
+ if (LargeScreenSettings.doNotRestoreIfLargeScreenSetting(key, getBaseContext())) {
+ Log.i(TAG, "Skipping restore for setting " + key + " as the target device "
+ + "is a large screen (i.e tablet or foldable in unfolded state)");
+ continue;
+ }
+
String value = null;
boolean hasValueToRestore = false;
if (cachedEntries.indexOfKey(key) >= 0) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 25b9906..7aeba95 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -238,6 +238,15 @@
dumpSetting(s, p,
Settings.Global.AUDIO_SAFE_VOLUME_STATE,
GlobalSettingsProto.AUDIO_SAFE_VOLUME_STATE);
+ dumpSetting(s, p,
+ Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE,
+ GlobalSettingsProto.AUDIO_SAFE_CSD_CURRENT_VALUE);
+ dumpSetting(s, p,
+ Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING,
+ GlobalSettingsProto.AUDIO_SAFE_CSD_NEXT_WARNING);
+ dumpSetting(s, p,
+ Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS,
+ GlobalSettingsProto.AUDIO_SAFE_CSD_DOSE_RECORDS);
final long autofillToken = p.start(GlobalSettingsProto.AUTOFILL);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 3782ce4..f1ea482 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -131,6 +131,9 @@
Settings.Global.ART_VERIFIER_VERIFY_DEBUGGABLE,
Settings.Global.ASSISTED_GPS_ENABLED,
Settings.Global.AUDIO_SAFE_VOLUME_STATE,
+ Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE,
+ Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING,
+ Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS,
Settings.Global.AUTOFILL_LOGGING_LEVEL,
Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE,
Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index 0b1a3e2..46c604b 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -25,6 +25,7 @@
"androidx.coordinatorlayout_coordinatorlayout",
"androidx.core_core",
"androidx.viewpager_viewpager",
+ "SettingsLib",
],
uses_libs: [
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index 26748a9..0b4d7cd 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -16,6 +16,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.android.systemui.accessibility.accessibilitymenu">
+
+ <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
+
<application>
<service
android:name="com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService"
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 76139c6..ecb2bf4 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -22,19 +22,28 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
+import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManager;
+import android.media.AudioManager;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
+import android.provider.Settings;
import android.view.Display;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import com.android.settingslib.display.BrightnessUtils;
+import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;
import com.android.systemui.accessibility.accessibilitymenu.view.A11yMenuOverlayLayout;
+import java.util.List;
+
/** @hide */
public class AccessibilityMenuService extends AccessibilityService
implements View.OnTouchListener {
@@ -42,6 +51,11 @@
private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L;
+ private static final int BRIGHTNESS_UP_INCREMENT_GAMMA =
+ (int) Math.ceil(BrightnessUtils.GAMMA_SPACE_MAX * 0.11f);
+ private static final int BRIGHTNESS_DOWN_INCREMENT_GAMMA =
+ (int) -Math.ceil(BrightnessUtils.GAMMA_SPACE_MAX * 0.11f);
+
private long mLastTimeTouchedOutside = 0L;
// Timeout used to ignore the A11y button onClick() when ACTION_OUTSIDE is also received on
// clicking on the A11y button.
@@ -51,6 +65,8 @@
private static boolean sInitialized = false;
+ private AudioManager mAudioManager;
+
// TODO(b/136716947): Support multi-display once a11y framework side is ready.
private DisplayManager mDisplayManager;
final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() {
@@ -140,6 +156,7 @@
mDisplayManager = getSystemService(DisplayManager.class);
mDisplayManager.registerDisplayListener(mDisplayListener, null);
+ mAudioManager = getSystemService(AudioManager.class);
sInitialized = true;
}
@@ -170,9 +187,93 @@
* @param view the shortcut button being clicked.
*/
public void handleClick(View view) {
+ // Shortcuts are repeatable in a11y menu rather than unique, so use tag ID to handle.
+ int viewTag = (int) view.getTag();
+
+ if (viewTag == ShortcutId.ID_ASSISTANT_VALUE.ordinal()) {
+ // Always restart the voice command activity, so that the UI is reloaded.
+ startActivityIfIntentIsSafe(
+ new Intent(Intent.ACTION_VOICE_COMMAND),
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ } else if (viewTag == ShortcutId.ID_A11YSETTING_VALUE.ordinal()) {
+ startActivityIfIntentIsSafe(
+ new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS),
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ } else if (viewTag == ShortcutId.ID_POWER_VALUE.ordinal()) {
+ performGlobalAction(GLOBAL_ACTION_POWER_DIALOG);
+ } else if (viewTag == ShortcutId.ID_RECENT_VALUE.ordinal()) {
+ performGlobalAction(GLOBAL_ACTION_RECENTS);
+ } else if (viewTag == ShortcutId.ID_LOCKSCREEN_VALUE.ordinal()) {
+ performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
+ } else if (viewTag == ShortcutId.ID_QUICKSETTING_VALUE.ordinal()) {
+ performGlobalAction(GLOBAL_ACTION_QUICK_SETTINGS);
+ } else if (viewTag == ShortcutId.ID_NOTIFICATION_VALUE.ordinal()) {
+ performGlobalAction(GLOBAL_ACTION_NOTIFICATIONS);
+ } else if (viewTag == ShortcutId.ID_SCREENSHOT_VALUE.ordinal()) {
+ performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT);
+ } else if (viewTag == ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal()) {
+ adjustBrightness(BRIGHTNESS_UP_INCREMENT_GAMMA);
+ return;
+ } else if (viewTag == ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal()) {
+ adjustBrightness(BRIGHTNESS_DOWN_INCREMENT_GAMMA);
+ return;
+ } else if (viewTag == ShortcutId.ID_VOLUME_UP_VALUE.ordinal()) {
+ adjustVolume(AudioManager.ADJUST_RAISE);
+ return;
+ } else if (viewTag == ShortcutId.ID_VOLUME_DOWN_VALUE.ordinal()) {
+ adjustVolume(AudioManager.ADJUST_LOWER);
+ return;
+ }
+
mA11yMenuLayout.hideMenu();
}
+ private void adjustBrightness(int increment) {
+ BrightnessInfo info = getDisplay().getBrightnessInfo();
+ int gamma = BrightnessUtils.convertLinearToGammaFloat(
+ info.brightness,
+ info.brightnessMinimum,
+ info.brightnessMaximum
+ );
+ gamma = Math.max(
+ BrightnessUtils.GAMMA_SPACE_MIN,
+ Math.min(BrightnessUtils.GAMMA_SPACE_MAX, gamma + increment));
+
+ float brightness = BrightnessUtils.convertGammaToLinearFloat(
+ gamma,
+ info.brightnessMinimum,
+ info.brightnessMaximum
+ );
+ mDisplayManager.setTemporaryBrightness(getDisplayId(), brightness);
+ mDisplayManager.setBrightness(getDisplayId(), brightness);
+ mA11yMenuLayout.showSnackbar(
+ getString(R.string.brightness_percentage_label,
+ (gamma / (BrightnessUtils.GAMMA_SPACE_MAX / 100))));
+ }
+
+ private void adjustVolume(int direction) {
+ mAudioManager.adjustStreamVolume(
+ AudioManager.STREAM_MUSIC, direction,
+ AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+ final int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ final int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ mA11yMenuLayout.showSnackbar(
+ getString(
+ R.string.music_volume_percentage_label,
+ (int) (100.0 / maxVolume * volume))
+ );
+ }
+
+ private void startActivityIfIntentIsSafe(Intent intent, int flag) {
+ PackageManager packageManager = getPackageManager();
+ List<ResolveInfo> activities =
+ packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ if (!activities.isEmpty()) {
+ intent.setFlags(flag);
+ startActivity(intent);
+ }
+ }
+
@Override
public void onInterrupt() {
}
diff --git a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
index c181724..392d845 100644
--- a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
+++ b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
@@ -4,12 +4,16 @@
android:id="@+id/work_profile_first_run"
android:layout_height="wrap_content"
android:layout_width="match_parent"
+ android:paddingStart="16dp"
+ android:paddingEnd="4dp"
+ android:paddingVertical="16dp"
android:visibility="gone">
<ImageView
android:id="@+id/screenshot_message_icon"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:paddingEnd="4dp"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_marginEnd="12dp"
+ android:layout_gravity="center_vertical"
android:src="@drawable/ic_work_app_badge"/>
<TextView
@@ -17,7 +21,11 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:layout_gravity="start"/>
+ android:layout_gravity="start|center_vertical"
+ android:textSize="18sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:lineHeight="24sp"
+ />
<FrameLayout
android:id="@+id/message_dismiss_button"
@@ -25,9 +33,9 @@
android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
android:contentDescription="@string/screenshot_dismiss_work_profile">
<ImageView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="@dimen/overlay_dismiss_button_margin"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center"
android:src="@drawable/overlay_cancel"/>
</FrameLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 16bffb7..0f94628 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -240,7 +240,7 @@
<!-- Content description for the right boundary of the screenshot being cropped, with the current position as a percentage. [CHAR LIMIT=NONE] -->
<string name="screenshot_right_boundary_pct">Right boundary <xliff:g id="percent" example="50">%1$d</xliff:g> percent</string>
<!-- Notification displayed when a screenshot is saved in a work profile. [CHAR LIMIT=NONE] -->
- <string name="screenshot_work_profile_notification">Work screenshots are saved in the <xliff:g id="app" example="Work Files">%1$s</xliff:g> app</string>
+ <string name="screenshot_work_profile_notification">Saved in <xliff:g id="app" example="Files">%1$s</xliff:g> in the work profile</string>
<!-- Default name referring to the app on the device that lets the user browse stored files. [CHAR LIMIT=NONE] -->
<string name="screenshot_default_files_app_name">Files</string>
<!-- A notice shown to the user to indicate that an app has detected the screenshot that the user has just taken. [CHAR LIMIT=75] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 0685794..dd42c8e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -91,6 +91,7 @@
private ViewGroup mStatusArea;
// If the SMARTSPACE flag is set, keyguard_slice_view is replaced by the following views.
+ private ViewGroup mDateWeatherView;
private View mWeatherView;
private View mSmartspaceView;
@@ -201,7 +202,7 @@
// TODO(b/261757708): add content observer for the Settings toggle and add/remove
// weather according to the Settings.
if (mSmartspaceController.isDateWeatherDecoupled()) {
- addWeatherView(viewIndex);
+ addDateWeatherView(viewIndex);
viewIndex += 1;
}
@@ -239,6 +240,14 @@
void onLocaleListChanged() {
if (mSmartspaceController.isEnabled()) {
+ if (mSmartspaceController.isDateWeatherDecoupled()) {
+ mDateWeatherView.removeView(mWeatherView);
+ int index = mStatusArea.indexOfChild(mDateWeatherView);
+ if (index >= 0) {
+ mStatusArea.removeView(mDateWeatherView);
+ addDateWeatherView(index);
+ }
+ }
int index = mStatusArea.indexOfChild(mSmartspaceView);
if (index >= 0) {
mStatusArea.removeView(mSmartspaceView);
@@ -247,16 +256,28 @@
}
}
- private void addWeatherView(int index) {
- mWeatherView = mSmartspaceController.buildAndConnectWeatherView(mView);
+ private void addDateWeatherView(int index) {
+ mDateWeatherView = (ViewGroup) mSmartspaceController.buildAndConnectDateView(mView);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
MATCH_PARENT, WRAP_CONTENT);
- mStatusArea.addView(mWeatherView, index, lp);
+ mStatusArea.addView(mDateWeatherView, index, lp);
int startPadding = getContext().getResources().getDimensionPixelSize(
R.dimen.below_clock_padding_start);
int endPadding = getContext().getResources().getDimensionPixelSize(
R.dimen.below_clock_padding_end);
- mWeatherView.setPaddingRelative(startPadding, 0, endPadding, 0);
+ mDateWeatherView.setPaddingRelative(startPadding, 0, endPadding, 0);
+
+ addWeatherView();
+ }
+
+ private void addWeatherView() {
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ WRAP_CONTENT, WRAP_CONTENT);
+ mWeatherView = mSmartspaceController.buildAndConnectWeatherView(mView);
+ // Place weather right after the date, before the extras
+ final int index = mDateWeatherView.getChildCount() == 0 ? 0 : 1;
+ mDateWeatherView.addView(mWeatherView, index, lp);
+ mWeatherView.setPaddingRelative(0, 0, 4, 0);
}
private void addSmartspaceView(int index) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/EllipseOverlapDetectorParams.kt b/packages/SystemUI/src/com/android/systemui/biometrics/EllipseOverlapDetectorParams.kt
new file mode 100644
index 0000000..ab9b690
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/EllipseOverlapDetectorParams.kt
@@ -0,0 +1,13 @@
+package com.android.systemui.biometrics
+
+/**
+ * Collection of parameters used by EllipseOverlapDetector
+ *
+ * [minOverlap] minimum percentage (float from 0-1) needed to be considered a valid overlap
+ *
+ * [targetSize] percentage (defined as a float of 0-1) of sensor that is considered the target,
+ * expands outward from center
+ *
+ * [stepSize] size of each step when iterating over sensor pixel grid
+ */
+class EllipseOverlapDetectorParams(val minOverlap: Float, val targetSize: Float, val stepSize: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 7217f99..64d5518 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -557,7 +557,8 @@
final InstanceId sessionIdProvider = mSessionTracker.getSessionId(
getBiometricSessionType());
final int sessionId = (sessionIdProvider != null) ? sessionIdProvider.getId() : -1;
- final int touchConfigId = BOUNDING_BOX_TOUCH_CONFIG_ID;
+ final int touchConfigId = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_selected_udfps_touch_detection);
SysUiStatsLog.write(SysUiStatsLog.BIOMETRIC_TOUCH_REPORTED, biometricTouchReportedTouchType,
touchConfigId, sessionId, data.getX(), data.getY(), data.getMinor(),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 6680787..45ca24d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -442,7 +442,8 @@
): WindowManager.LayoutParams {
val paddingX = animation?.paddingX ?: 0
val paddingY = animation?.paddingY ?: 0
- if (animation != null && animation.listenForTouchesOutsideView()) {
+ if (!featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) && animation != null &&
+ animation.listenForTouchesOutsideView()) {
flags = flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
index 001fed7..20c3e40 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
@@ -16,6 +16,9 @@
package com.android.systemui.biometrics.dagger
+import android.content.res.Resources
+import com.android.internal.R
+import com.android.systemui.biometrics.EllipseOverlapDetectorParams
import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector
import com.android.systemui.biometrics.udfps.EllipseOverlapDetector
import com.android.systemui.biometrics.udfps.OverlapDetector
@@ -33,10 +36,30 @@
@Provides
@SysUISingleton
fun providesOverlapDetector(featureFlags: FeatureFlags): OverlapDetector {
- return if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
- EllipseOverlapDetector()
+ if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+ val selectedOption =
+ Resources.getSystem()
+ .getInteger(R.integer.config_selected_udfps_touch_detection)
+ val values =
+ Resources.getSystem()
+ .getStringArray(R.array.config_udfps_touch_detection_options)[
+ selectedOption]
+ .split(",")
+ .map { it.toFloat() }
+
+ return if (values[0] == 1f) {
+ EllipseOverlapDetector(
+ EllipseOverlapDetectorParams(
+ minOverlap = values[3],
+ targetSize = values[2],
+ stepSize = values[4].toInt()
+ )
+ )
+ } else {
+ BoundingBoxOverlapDetector()
+ }
} else {
- BoundingBoxOverlapDetector()
+ return BoundingBoxOverlapDetector()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
index 682d38a..2e2970f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
@@ -18,22 +18,84 @@
import android.graphics.Point
import android.graphics.Rect
-import androidx.annotation.VisibleForTesting
+import android.util.Log
+import com.android.systemui.biometrics.EllipseOverlapDetectorParams
import com.android.systemui.dagger.SysUISingleton
import kotlin.math.cos
-import kotlin.math.pow
import kotlin.math.sin
+private enum class SensorPixelPosition {
+ OUTSIDE, // Pixel that falls outside of sensor circle
+ SENSOR, // Pixel within sensor circle
+ TARGET // Pixel within sensor center target
+}
+
+private val isDebug = true
+private val TAG = "EllipseOverlapDetector"
+
/**
* Approximates the touch as an ellipse and determines whether the ellipse has a sufficient overlap
* with the sensor.
*/
@SysUISingleton
-class EllipseOverlapDetector(private val neededPoints: Int = 2) : OverlapDetector {
-
+class EllipseOverlapDetector(private val params: EllipseOverlapDetectorParams) : OverlapDetector {
override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean {
- val points = calculateSensorPoints(nativeSensorBounds)
- return points.count { checkPoint(it, touchData) } >= neededPoints
+ var isTargetTouched = false
+ var sensorPixels = 0
+ var coveredPixels = 0
+ for (y in nativeSensorBounds.top..nativeSensorBounds.bottom step params.stepSize) {
+ for (x in nativeSensorBounds.left..nativeSensorBounds.right step params.stepSize) {
+ // Check where pixel is within the sensor TODO: (b/265836919) This could be improved
+ // by precomputing these points
+ val pixelPosition =
+ isPartOfSensorArea(
+ x,
+ y,
+ nativeSensorBounds.centerX(),
+ nativeSensorBounds.centerY(),
+ nativeSensorBounds.width() / 2
+ )
+ if (pixelPosition != SensorPixelPosition.OUTSIDE) {
+ sensorPixels++
+
+ // Check if this pixel falls within ellipse touch
+ if (checkPoint(Point(x, y), touchData)) {
+ coveredPixels++
+
+ // Check that at least one covered pixel is within sensor target
+ isTargetTouched =
+ isTargetTouched or (pixelPosition == SensorPixelPosition.TARGET)
+ }
+ }
+ }
+ }
+
+ val percentage: Float = coveredPixels.toFloat() / sensorPixels
+ if (isDebug) {
+ Log.v(
+ TAG,
+ "covered: $coveredPixels, sensor: $sensorPixels, " +
+ "percentage: $percentage, isCenterTouched: $isTargetTouched"
+ )
+ }
+
+ return percentage >= params.minOverlap && isTargetTouched
+ }
+
+ /** Checks if point is in the sensor center target circle, outer circle, or outside of sensor */
+ private fun isPartOfSensorArea(x: Int, y: Int, cX: Int, cY: Int, r: Int): SensorPixelPosition {
+ val dx = cX - x
+ val dy = cY - y
+
+ val disSquared = dx * dx + dy * dy
+
+ return if (disSquared <= (r * params.targetSize) * (r * params.targetSize)) {
+ SensorPixelPosition.TARGET
+ } else if (disSquared <= r * r) {
+ SensorPixelPosition.SENSOR
+ } else {
+ SensorPixelPosition.OUTSIDE
+ }
}
private fun checkPoint(point: Point, touchData: NormalizedTouchData): Boolean {
@@ -45,29 +107,9 @@
val c: Float = sin(touchData.orientation) * (point.x - touchData.x)
val d: Float = cos(touchData.orientation) * (point.y - touchData.y)
val result =
- (a + b).pow(2) / (touchData.minor / 2).pow(2) +
- (c - d).pow(2) / (touchData.major / 2).pow(2)
+ (a + b) * (a + b) / ((touchData.minor / 2) * (touchData.minor / 2)) +
+ (c - d) * (c - d) / ((touchData.major / 2) * (touchData.major / 2))
return result <= 1
}
-
- @VisibleForTesting
- fun calculateSensorPoints(sensorBounds: Rect): List<Point> {
- val sensorX = sensorBounds.centerX()
- val sensorY = sensorBounds.centerY()
- val cornerOffset: Int = sensorBounds.width() / 4
- val sideOffset: Int = sensorBounds.width() / 3
-
- return listOf(
- Point(sensorX - cornerOffset, sensorY - cornerOffset),
- Point(sensorX, sensorY - sideOffset),
- Point(sensorX + cornerOffset, sensorY - cornerOffset),
- Point(sensorX - sideOffset, sensorY),
- Point(sensorX, sensorY),
- Point(sensorX + sideOffset, sensorY),
- Point(sensorX - cornerOffset, sensorY + cornerOffset),
- Point(sensorX, sensorY + sideOffset),
- Point(sensorX + cornerOffset, sensorY + cornerOffset)
- )
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index c3f24f0..45872f4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -218,6 +218,10 @@
abstract BcSmartspaceConfigPlugin optionalBcSmartspaceConfigPlugin();
@BindsOptionalOf
+ @Named(SmartspaceModule.DATE_SMARTSPACE_DATA_PLUGIN)
+ abstract BcSmartspaceDataPlugin optionalDateSmartspaceConfigPlugin();
+
+ @BindsOptionalOf
@Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN)
abstract BcSmartspaceDataPlugin optionalWeatherSmartspaceConfigPlugin();
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 7ff27cf..cd911c1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -347,7 +347,7 @@
unreleasedFlag(910, "media_ttt_receiver_success_ripple", teamfood = true)
// TODO(b/263512203): Tracking Bug
- val MEDIA_EXPLICIT_INDICATOR = unreleasedFlag(911, "media_explicit_indicator", teamfood = true)
+ val MEDIA_EXPLICIT_INDICATOR = releasedFlag(911, "media_explicit_indicator")
// TODO(b/265813373): Tracking Bug
val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE =
@@ -360,6 +360,9 @@
@JvmField
val MEDIA_RECOMMENDATION_CARD_UPDATE = unreleasedFlag(914, "media_recommendation_card_update")
+ // TODO(b/267007629): Tracking Bug
+ val MEDIA_RESUME_PROGRESS = unreleasedFlag(915, "media_resume_progress")
+
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
@@ -569,9 +572,8 @@
// 2200 - udfps
// TODO(b/259264861): Tracking Bug
- @JvmField val UDFPS_NEW_TOUCH_DETECTION = unreleasedFlag(2200, "udfps_new_touch_detection")
- @JvmField val UDFPS_ELLIPSE_DEBUG_UI = unreleasedFlag(2201, "udfps_ellipse_debug")
- @JvmField val UDFPS_ELLIPSE_DETECTION = unreleasedFlag(2202, "udfps_ellipse_detection")
+ @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection")
+ @JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag(2201, "udfps_ellipse_detection")
// 2300 - stylus
@JvmField val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index ce61f2f..86f65dde 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -21,6 +21,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
@@ -44,6 +45,7 @@
override fun start() {
listenForDozingToLockscreen()
+ listenForDozingToGone()
}
private fun listenForDozingToLockscreen() {
@@ -68,6 +70,28 @@
}
}
+ private fun listenForDozingToGone() {
+ scope.launch {
+ keyguardInteractor.biometricUnlockState
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { (biometricUnlockState, lastStartedTransition) ->
+ if (
+ lastStartedTransition.to == KeyguardState.DOZING &&
+ isWakeAndUnlock(biometricUnlockState)
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.DOZING,
+ KeyguardState.GONE,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index ad6dbea..53c80f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -19,7 +19,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER
@@ -31,9 +30,6 @@
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import javax.inject.Inject
-import kotlin.math.max
-import kotlin.math.min
-import kotlin.time.Duration
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@@ -104,38 +100,4 @@
/* The last completed [KeyguardState] transition */
val finishedKeyguardState: Flow<KeyguardState> =
finishedKeyguardTransitionStep.map { step -> step.to }
-
- /**
- * Transitions will occur over a [totalDuration] with [TransitionStep]s being emitted in the
- * range of [0, 1]. View animations should begin and end within a subset of this range. This
- * function maps the [startTime] and [duration] into [0, 1], when this subset is valid.
- */
- fun transitionStepAnimation(
- flow: Flow<TransitionStep>,
- params: AnimationParams,
- totalDuration: Duration,
- ): Flow<Float> {
- val start = (params.startTime / totalDuration).toFloat()
- val chunks = (totalDuration / params.duration).toFloat()
- var isRunning = false
- return flow
- .map { step ->
- val value = (step.value - start) * chunks
- if (step.transitionState == STARTED) {
- // When starting, make sure to always emit. If a transition is started from the
- // middle, it is possible this animation is being skipped but we need to inform
- // the ViewModels of the last update
- isRunning = true
- max(0f, min(1f, value))
- } else if (isRunning && value >= 1f) {
- // Always send a final value of 1. Because of rounding, [value] may never be
- // exactly 1.
- isRunning = false
- 1f
- } else {
- value
- }
- }
- .filter { value -> value >= 0f && value <= 1f }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
deleted file mode 100644
index 67733e9..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
+++ /dev/null
@@ -1,25 +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.systemui.keyguard.shared.model
-
-import kotlin.time.Duration
-import kotlin.time.Duration.Companion.milliseconds
-
-/** Animation parameters */
-data class AnimationParams(
- val startTime: Duration = 0.milliseconds,
- val duration: Duration,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
new file mode 100644
index 0000000..ca1e27c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.ui
+
+import android.view.animation.Interpolator
+import com.android.systemui.animation.Interpolators.LINEAR
+import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+/**
+ * For the given transition params, construct a flow using [createFlow] for the specified portion of
+ * the overall transition.
+ */
+class KeyguardTransitionAnimationFlow(
+ private val transitionDuration: Duration,
+ private val transitionFlow: Flow<TransitionStep>,
+) {
+ /**
+ * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted in
+ * the range of [0, 1]. View animations should begin and end within a subset of this range. This
+ * function maps the [startTime] and [duration] into [0, 1], when this subset is valid.
+ */
+ fun createFlow(
+ duration: Duration,
+ onStep: (Float) -> Float,
+ startTime: Duration = 0.milliseconds,
+ onCancel: (() -> Float)? = null,
+ onFinish: (() -> Float)? = null,
+ interpolator: Interpolator = LINEAR,
+ ): Flow<Float> {
+ if (!duration.isPositive()) {
+ throw IllegalArgumentException("duration must be a positive number: $duration")
+ }
+ if ((startTime + duration).compareTo(transitionDuration) > 0) {
+ throw IllegalArgumentException(
+ "startTime($startTime) + duration($duration) must be" +
+ " <= transitionDuration($transitionDuration)"
+ )
+ }
+
+ val start = (startTime / transitionDuration).toFloat()
+ val chunks = (transitionDuration / duration).toFloat()
+ var isComplete = true
+
+ fun stepToValue(step: TransitionStep): Float? {
+ val value = (step.value - start) * chunks
+ return when (step.transitionState) {
+ // When starting, make sure to always emit. If a transition is started from the
+ // middle, it is possible this animation is being skipped but we need to inform
+ // the ViewModels of the last update
+ STARTED -> {
+ isComplete = false
+ max(0f, min(1f, value))
+ }
+ // Always send a final value of 1. Because of rounding, [value] may never be
+ // exactly 1.
+ RUNNING ->
+ if (isComplete) {
+ null
+ } else if (value >= 1f) {
+ isComplete = true
+ 1f
+ } else if (value >= 0f) {
+ value
+ } else {
+ null
+ }
+ else -> null
+ }?.let { onStep(interpolator.getInterpolation(it)) }
+ }
+
+ return transitionFlow
+ .map { step ->
+ when (step.transitionState) {
+ STARTED -> stepToValue(step)
+ RUNNING -> stepToValue(step)
+ CANCELED -> onCancel?.invoke()
+ FINISHED -> onFinish?.invoke()
+ }
+ }
+ .filterNotNull()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 6627865..8d6545a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -21,15 +21,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
/**
* Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -41,39 +36,46 @@
constructor(
private val interactor: KeyguardTransitionInteractor,
) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_LOCKSCREEN_DURATION,
+ transitionFlow = interactor.dreamingToLockscreenTransition,
+ )
/** Dream overlay y-translation on exit */
fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> {
- return flowForAnimation(DREAM_OVERLAY_TRANSLATION_Y).map { value ->
- EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx
- }
+ return transitionAnimation.createFlow(
+ duration = 600.milliseconds,
+ onStep = { it * translatePx },
+ interpolator = EMPHASIZED_ACCELERATE,
+ )
}
/** Dream overlay views alpha - fade out */
- val dreamOverlayAlpha: Flow<Float> = flowForAnimation(DREAM_OVERLAY_ALPHA).map { 1f - it }
+ val dreamOverlayAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
+ )
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return merge(
- flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
- -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
- },
- // On end, reset the translation to 0
- interactor.dreamingToLockscreenTransition
- .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
- .map { 0f }
+ return transitionAnimation.createFlow(
+ duration = TO_LOCKSCREEN_DURATION,
+ onStep = { value -> -translatePx + value * translatePx },
+ // Reset on cancel or finish
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED_DECELERATE,
)
}
/** Lockscreen views alpha */
- val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA)
-
- private fun flowForAnimation(params: AnimationParams): Flow<Float> {
- return interactor.transitionStepAnimation(
- interactor.dreamingToLockscreenTransition,
- params,
- totalDuration = TO_LOCKSCREEN_DURATION
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ startTime = 233.milliseconds,
+ duration = 250.milliseconds,
+ onStep = { it },
)
- }
companion object {
/* Length of time before ending the dream activity, in order to start unoccluding */
@@ -81,11 +83,5 @@
@JvmField
val LOCKSCREEN_ANIMATION_DURATION_MS =
(TO_LOCKSCREEN_DURATION - DREAM_ANIMATION_DURATION).inWholeMilliseconds
-
- val DREAM_OVERLAY_TRANSLATION_Y = AnimationParams(duration = 600.milliseconds)
- val DREAM_OVERLAY_ALPHA = AnimationParams(duration = 250.milliseconds)
- val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION)
- val LOCKSCREEN_ALPHA =
- AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
index 5a47960..f16827d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
@@ -20,15 +20,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
/** Breaks down GONE->DREAMING transition into discrete steps for corresponding views to consume. */
@SysUISingleton
@@ -38,32 +33,28 @@
private val interactor: KeyguardTransitionInteractor,
) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_DREAMING_DURATION,
+ transitionFlow = interactor.goneToDreamingTransition,
+ )
+
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return merge(
- flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
- (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
- },
- // On end, reset the translation to 0
- interactor.goneToDreamingTransition
- .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
- .map { 0f }
+ return transitionAnimation.createFlow(
+ duration = 500.milliseconds,
+ onStep = { it * translatePx },
+ // Reset on cancel or finish
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED_ACCELERATE,
)
}
/** Lockscreen views alpha */
- val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
-
- private fun flowForAnimation(params: AnimationParams): Flow<Float> {
- return interactor.transitionStepAnimation(
- interactor.goneToDreamingTransition,
- params,
- totalDuration = TO_DREAMING_DURATION
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
)
- }
-
- companion object {
- val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds)
- val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index e05adbd..bc9dc4f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -20,15 +20,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
/**
* Breaks down LOCKSCREEN->DREAMING transition into discrete steps for corresponding views to
@@ -40,35 +35,32 @@
constructor(
private val interactor: KeyguardTransitionInteractor,
) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_DREAMING_DURATION,
+ transitionFlow = interactor.lockscreenToDreamingTransition,
+ )
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return merge(
- flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
- (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
- },
- // On end, reset the translation to 0
- interactor.lockscreenToDreamingTransition
- .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
- .map { 0f }
+ return transitionAnimation.createFlow(
+ duration = 500.milliseconds,
+ onStep = { it * translatePx },
+ // Reset on cancel or finish
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED_ACCELERATE,
)
}
/** Lockscreen views alpha */
- val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
-
- private fun flowForAnimation(params: AnimationParams): Flow<Float> {
- return interactor.transitionStepAnimation(
- interactor.lockscreenToDreamingTransition,
- params,
- totalDuration = TO_DREAMING_DURATION
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
)
- }
companion object {
@JvmField val DREAMING_ANIMATION_DURATION_MS = TO_DREAMING_DURATION.inWholeMilliseconds
-
- val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds)
- val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index 22d292e..a60665a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -20,14 +20,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
/**
* Breaks down LOCKSCREEN->OCCLUDED transition into discrete steps for corresponding views to
@@ -39,33 +35,28 @@
constructor(
private val interactor: KeyguardTransitionInteractor,
) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_OCCLUDED_DURATION,
+ transitionFlow = interactor.lockscreenToOccludedTransition,
+ )
+
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
+ )
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return merge(
- flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
- (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
- },
- // On end, reset the translation to 0
- interactor.lockscreenToOccludedTransition
- .filter { step -> step.transitionState == TransitionState.FINISHED }
- .map { 0f }
+ return transitionAnimation.createFlow(
+ duration = TO_OCCLUDED_DURATION,
+ onStep = { value -> value * translatePx },
+ // Reset on cancel or finish
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED_ACCELERATE,
)
}
-
- /** Lockscreen views alpha */
- val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
-
- private fun flowForAnimation(params: AnimationParams): Flow<Float> {
- return interactor.transitionStepAnimation(
- interactor.lockscreenToOccludedTransition,
- params,
- totalDuration = TO_OCCLUDED_DURATION
- )
- }
-
- companion object {
- val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_OCCLUDED_DURATION)
- val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index e804562..5770f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -20,11 +20,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
/**
* Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -36,28 +35,26 @@
constructor(
private val interactor: KeyguardTransitionInteractor,
) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_LOCKSCREEN_DURATION,
+ transitionFlow = interactor.occludedToLockscreenTransition,
+ )
+
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
- -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
- }
- }
-
- /** Lockscreen views alpha */
- val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA)
-
- private fun flowForAnimation(params: AnimationParams): Flow<Float> {
- return interactor.transitionStepAnimation(
- interactor.occludedToLockscreenTransition,
- params,
- totalDuration = TO_LOCKSCREEN_DURATION
+ return transitionAnimation.createFlow(
+ duration = TO_LOCKSCREEN_DURATION,
+ onStep = { value -> -translatePx + value * translatePx },
+ interpolator = EMPHASIZED_DECELERATE,
)
}
- companion object {
- @JvmField val LOCKSCREEN_ANIMATION_DURATION_MS = TO_LOCKSCREEN_DURATION.inWholeMilliseconds
- val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION)
- val LOCKSCREEN_ALPHA =
- AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds)
- }
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ startTime = 233.milliseconds,
+ duration = 250.milliseconds,
+ onStep = { it },
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
index be18cbe..b7a2522 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
@@ -92,6 +92,9 @@
/** Whether explicit indicator exists */
val isExplicit: Boolean = false,
+
+ /** Track progress (0 - 1) to display for players where [resumption] is true */
+ val resumeProgress: Double? = null,
) {
companion object {
/** Media is playing on the local device */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index bba5f35..a057c9f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -238,6 +238,24 @@
}
/**
+ * Set the progress to a fixed percentage value that cannot be changed by the user.
+ *
+ * @param percent value between 0 and 1
+ */
+ fun updateStaticProgress(percent: Double) {
+ val position = (percent * 100).toInt()
+ _data =
+ Progress(
+ enabled = true,
+ seekAvailable = false,
+ playing = false,
+ scrubbing = false,
+ elapsedTime = position,
+ duration = 100,
+ )
+ }
+
+ /**
* Puts the seek bar into a resumption state.
*
* This should be called when the media session behind the controller has been destroyed.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index da2164e..16a2e3f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -68,6 +68,7 @@
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
import com.android.systemui.media.controls.resume.MediaResumeListener
import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaDataUtils
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.ActivityStarter
@@ -668,6 +669,11 @@
MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT &&
mediaFlags.isExplicitIndicatorEnabled()
+ val progress =
+ if (mediaFlags.isResumeProgressEnabled()) {
+ MediaDataUtils.getDescriptionProgress(desc.extras)
+ } else null
+
val mediaAction = getResumeMediaAction(resumeAction)
val lastActive = systemClock.elapsedRealtime()
foregroundExecutor.execute {
@@ -698,6 +704,7 @@
instanceId = instanceId,
appUid = appUid,
isExplicit = isExplicit,
+ resumeProgress = progress,
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 2fd4f27..61cc619 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -116,8 +116,6 @@
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
-import dagger.Lazy;
-
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
@@ -125,6 +123,7 @@
import javax.inject.Inject;
+import dagger.Lazy;
import kotlin.Triple;
import kotlin.Unit;
@@ -524,8 +523,13 @@
}
// Seek Bar
- final MediaController controller = getController();
- mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
+ if (data.getResumption() && data.getResumeProgress() != null) {
+ double progress = data.getResumeProgress();
+ mSeekBarViewModel.updateStaticProgress(progress);
+ } else {
+ final MediaController controller = getController();
+ mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
+ }
// Show the broadcast dialog button only when the le audio is enabled.
mShowBroadcastDialogButton =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
index bcfceaa..85282a1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
@@ -19,8 +19,12 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.os.Bundle;
import android.text.TextUtils;
+import androidx.core.math.MathUtils;
+import androidx.media.utils.MediaConstants;
+
/**
* Utility class with common methods for media controls
*/
@@ -50,4 +54,35 @@
: unknownName);
return applicationName;
}
+
+ /**
+ * Check the bundle for extras indicating the progress percentage
+ *
+ * @param extras
+ * @return the progress value between 0-1 inclusive if prsent, otherwise null
+ */
+ public static Double getDescriptionProgress(Bundle extras) {
+ if (!extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS)) {
+ return null;
+ }
+
+ int status = extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS);
+ switch (status) {
+ case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED:
+ return 0.0;
+ case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED:
+ return 1.0;
+ case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED: {
+ if (extras
+ .containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE)) {
+ double percent = extras
+ .getDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE);
+ return MathUtils.clamp(percent, 0.0, 1.0);
+ } else {
+ return 0.5;
+ }
+ }
+ }
+ return null;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 81efa36..a689dc3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -55,4 +55,7 @@
/** Check whether we show the updated recommendation card. */
fun isRecommendationCardUpdateEnabled() =
featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
+
+ /** Check whether to get progress information for resume players */
+ fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index d6c6a81..e57b169 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,7 +16,7 @@
package com.android.systemui.media.dialog;
-import static android.media.RouteListingPreference.Item.DISABLE_REASON_SUBSCRIPTION_REQUIRED;
+import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -411,10 +411,10 @@
@RequiresApi(34)
private static class Api34Impl {
@DoNotInline
- static String composeDisabledReason(@RouteListingPreference.Item.DisableReason int reason,
- Context context) {
+ static String composeDisabledReason(
+ @RouteListingPreference.Item.SubText int reason, Context context) {
switch(reason) {
- case DISABLE_REASON_SUBSCRIPTION_REQUIRED:
+ case SUBTEXT_SUBSCRIPTION_REQUIRED:
return context.getString(R.string.media_output_status_require_premium);
}
return "";
diff --git a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
index 1324d2c..c4a1ed4 100644
--- a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
@@ -19,7 +19,6 @@
import android.view.WindowManagerGlobal
import com.android.app.motiontool.DdmHandleMotionTool
import com.android.app.motiontool.MotionToolManager
-import com.android.app.viewcapture.ViewCapture
import com.android.systemui.CoreStartable
import dagger.Binds
import dagger.Module
@@ -38,17 +37,12 @@
}
@Provides
- fun provideMotionToolManager(
- viewCapture: ViewCapture,
- windowManagerGlobal: WindowManagerGlobal
- ): MotionToolManager {
- return MotionToolManager.getInstance(viewCapture, windowManagerGlobal)
+ fun provideMotionToolManager(windowManagerGlobal: WindowManagerGlobal): MotionToolManager {
+ return MotionToolManager.getInstance(windowManagerGlobal)
}
@Provides
fun provideWindowManagerGlobal(): WindowManagerGlobal = WindowManagerGlobal.getInstance()
-
- @Provides fun provideViewCapture(): ViewCapture = ViewCapture.getInstance()
}
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
index 66b7842..1b728b8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
@@ -67,10 +67,7 @@
}
// If label wasn't loaded, use a default
- val badgedLabel =
- packageManager.getUserBadgedLabel(label ?: defaultFileAppName(), userHandle)
-
- return WorkProfileFirstRunData(badgedLabel, badgedIcon)
+ return WorkProfileFirstRunData(label ?: defaultFileAppName(), badgedIcon)
}
return null
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 479510a..2ac7f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3151,17 +3151,11 @@
}
// The padding on this area is large enough that we can use a cheaper clipping strategy
mKeyguardStatusViewController.setClipBounds(clipStatusView ? mLastQsClipBounds : null);
- if (!qsVisible && mSplitShadeEnabled) {
- // On the lockscreen when qs isn't visible, we don't want the bounds of the shade to
- // be visible, otherwise you can see the bounds once swiping up to see bouncer
- mScrimController.setNotificationsBounds(0, 0, 0, 0);
- } else {
- // Increase the height of the notifications scrim when not in split shade
- // (e.g. portrait tablet) so the rounded corners are not visible at the bottom,
- // in this case they are rendered off-screen
- final int notificationsScrimBottom = mSplitShadeEnabled ? bottom : bottom + radius;
- mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom);
- }
+ // Increase the height of the notifications scrim when not in split shade
+ // (e.g. portrait tablet) so the rounded corners are not visible at the bottom,
+ // in this case they are rendered off-screen
+ final int notificationsScrimBottom = mSplitShadeEnabled ? bottom : bottom + radius;
+ mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom);
if (mSplitShadeEnabled) {
mKeyguardStatusBarViewController.setNoTopClipping();
@@ -3222,6 +3216,12 @@
private int calculateQsBottomPosition(float qsExpansionFraction) {
if (mTransitioningToFullShadeProgress > 0.0f) {
return mTransitionToFullShadeQSPosition;
+ } else if (mSplitShadeEnabled) {
+ // in split shade - outside lockscreen transition handled above - we simply jump between
+ // two qs expansion values - either shade is closed and qs expansion is 0 or shade is
+ // open and qs expansion is 1
+ int qsBottomTarget = mQs.getDesiredHeight() + mLargeScreenShadeHeaderHeight;
+ return qsExpansionFraction > 0 ? qsBottomTarget : 0;
} else {
int qsBottomYFrom = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight();
int expandedTopMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : 0;
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
index 393279b..641131e 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
@@ -44,6 +44,11 @@
const val DREAM_SMARTSPACE_PRECONDITION = "dream_smartspace_precondition"
/**
+ * The BcSmartspaceDataPlugin for the standalone date (+alarm+dnd).
+ */
+ const val DATE_SMARTSPACE_DATA_PLUGIN = "date_smartspace_data_plugin"
+
+ /**
* The BcSmartspaceDataPlugin for the standalone weather.
*/
const val WEATHER_SMARTSPACE_DATA_PLUGIN = "weather_smartspace_data_plugin"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 5b62d30..4d7496d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -52,6 +52,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.shared.regionsampling.UpdateColorCallback
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -84,6 +85,8 @@
@Main private val uiExecutor: Executor,
@Background private val bgExecutor: Executor,
@Main private val handler: Handler,
+ @Named(DATE_SMARTSPACE_DATA_PLUGIN)
+ optionalDatePlugin: Optional<BcSmartspaceDataPlugin>,
@Named(WEATHER_SMARTSPACE_DATA_PLUGIN)
optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
optionalPlugin: Optional<BcSmartspaceDataPlugin>,
@@ -94,6 +97,7 @@
}
private var session: SmartspaceSession? = null
+ private val datePlugin: BcSmartspaceDataPlugin? = optionalDatePlugin.orElse(null)
private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
private val configPlugin: BcSmartspaceConfigPlugin? = optionalConfigPlugin.orElse(null)
@@ -223,7 +227,7 @@
execution.assertIsMainThread()
return featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED) &&
- weatherPlugin != null
+ datePlugin != null && weatherPlugin != null
}
private fun updateBypassEnabled() {
@@ -232,6 +236,25 @@
}
/**
+ * Constructs the date view and connects it to the smartspace service.
+ */
+ fun buildAndConnectDateView(parent: ViewGroup): View? {
+ execution.assertIsMainThread()
+
+ if (!isEnabled()) {
+ throw RuntimeException("Cannot build view when not enabled")
+ }
+ if (!isDateWeatherDecoupled()) {
+ throw RuntimeException("Cannot build date view when not decoupled")
+ }
+
+ val view = buildView(parent, datePlugin)
+ connectSession()
+
+ return view
+ }
+
+ /**
* Constructs the weather view and connects it to the smartspace service.
*/
fun buildAndConnectWeatherView(parent: ViewGroup): View? {
@@ -309,7 +332,7 @@
}
private fun connectSession() {
- if (weatherPlugin == null && plugin == null) return
+ if (datePlugin == null && weatherPlugin == null && plugin == null) return
if (session != null || smartspaceViews.isEmpty()) {
return
}
@@ -347,6 +370,7 @@
statusBarStateController.addCallback(statusBarStateListener)
bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener)
+ datePlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
@@ -384,6 +408,8 @@
bypassController.unregisterOnBypassStateChangedListener(bypassStateChangedListener)
session = null
+ datePlugin?.registerSmartspaceEventNotifier(null)
+
weatherPlugin?.registerSmartspaceEventNotifier(null)
weatherPlugin?.onTargetsAvailable(emptyList())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 344d233..c1c6c88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -23,6 +23,7 @@
import android.view.View;
import android.view.ViewStub;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.LockIconView;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
@@ -67,6 +68,7 @@
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.settings.SecureSettings;
@@ -299,7 +301,9 @@
OperatorNameViewController.Factory operatorNameViewControllerFactory,
SecureSettings secureSettings,
@Main Executor mainExecutor,
- DumpManager dumpManager
+ DumpManager dumpManager,
+ StatusBarWindowStateController statusBarWindowStateController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor
) {
return new CollapsedStatusBarFragment(statusBarFragmentComponentFactory,
ongoingCallController,
@@ -320,7 +324,9 @@
operatorNameViewControllerFactory,
secureSettings,
mainExecutor,
- dumpManager);
+ dumpManager,
+ statusBarWindowStateController,
+ keyguardUpdateMonitor);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index cbd27cf..65becf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -44,6 +44,7 @@
import androidx.annotation.VisibleForTesting;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -72,6 +73,8 @@
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListener;
import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
@@ -129,6 +132,8 @@
private final SecureSettings mSecureSettings;
private final Executor mMainExecutor;
private final DumpManager mDumpManager;
+ private final StatusBarWindowStateController mStatusBarWindowStateController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private List<String> mBlockedIcons = new ArrayList<>();
private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>();
@@ -164,6 +169,22 @@
}
};
+ /**
+ * Whether we've launched the secure camera over the lockscreen, but haven't yet received a
+ * status bar window state change afterward.
+ *
+ * We wait for this state change (which will tell us whether to show/hide the status bar icons)
+ * so that there is no flickering/jump cutting during the camera launch.
+ */
+ private boolean mWaitingForWindowStateChangeAfterCameraLaunch = false;
+
+ /**
+ * Listener that updates {@link #mWaitingForWindowStateChangeAfterCameraLaunch} when it receives
+ * a new status bar window state.
+ */
+ private final StatusBarWindowStateListener mStatusBarWindowStateListener = state ->
+ mWaitingForWindowStateChangeAfterCameraLaunch = false;
+
@SuppressLint("ValidFragment")
public CollapsedStatusBarFragment(
StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
@@ -185,7 +206,9 @@
OperatorNameViewController.Factory operatorNameViewControllerFactory,
SecureSettings secureSettings,
@Main Executor mainExecutor,
- DumpManager dumpManager
+ DumpManager dumpManager,
+ StatusBarWindowStateController statusBarWindowStateController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor
) {
mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
mOngoingCallController = ongoingCallController;
@@ -207,6 +230,20 @@
mSecureSettings = secureSettings;
mMainExecutor = mainExecutor;
mDumpManager = dumpManager;
+ mStatusBarWindowStateController = statusBarWindowStateController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
}
@Override
@@ -254,6 +291,11 @@
mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
}
+ @Override
+ public void onCameraLaunchGestureDetected(int source) {
+ mWaitingForWindowStateChangeAfterCameraLaunch = true;
+ }
+
@VisibleForTesting
void updateBlockedIcons() {
mBlockedIcons.clear();
@@ -466,6 +508,27 @@
&& mNotificationPanelViewController.hideStatusBarIconsWhenExpanded()) {
return true;
}
+
+ // When launching the camera over the lockscreen, the icons become visible momentarily
+ // before animating out, since we're not yet aware that the launching camera activity is
+ // fullscreen. Even once the activity finishes launching, it takes a short time before WM
+ // decides that the top app wants to hide the icons and tells us to hide them. To ensure
+ // that this high-visibility animation is smooth, keep the icons hidden during a camera
+ // launch until we receive a window state change which indicates that the activity is done
+ // launching and WM has decided to show/hide the icons. For extra safety (to ensure the
+ // icons don't remain hidden somehow) we double check that the camera is still showing, the
+ // status bar window isn't hidden, and we're still occluded as well, though these checks
+ // are typically unnecessary.
+ final boolean hideIconsForSecureCamera =
+ (mWaitingForWindowStateChangeAfterCameraLaunch ||
+ !mStatusBarWindowStateController.windowIsShowing()) &&
+ mKeyguardUpdateMonitor.isSecureCameraLaunchedOverKeyguard() &&
+ mKeyguardStateController.isOccluded();
+
+ if (hideIconsForSecureCamera) {
+ return true;
+ }
+
return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
index 60f6df6..8f424b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
@@ -61,6 +61,10 @@
listeners.add(listener)
}
+ fun removeListener(listener: StatusBarWindowStateListener) {
+ listeners.remove(listener)
+ }
+
/** Returns true if the window is currently showing. */
fun windowIsShowing() = windowState == WINDOW_STATE_SHOWING
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 9f6e602..307787b 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -356,13 +356,22 @@
};
@Inject
- public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
- @Background Handler bgHandler, @Main Executor mainExecutor,
- @Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier,
- SecureSettings secureSettings, WallpaperManager wallpaperManager,
- UserManager userManager, DeviceProvisionedController deviceProvisionedController,
- UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags,
- @Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle) {
+ public ThemeOverlayController(
+ Context context,
+ BroadcastDispatcher broadcastDispatcher,
+ @Background Handler bgHandler,
+ @Main Executor mainExecutor,
+ @Background Executor bgExecutor,
+ ThemeOverlayApplier themeOverlayApplier,
+ SecureSettings secureSettings,
+ WallpaperManager wallpaperManager,
+ UserManager userManager,
+ DeviceProvisionedController deviceProvisionedController,
+ UserTracker userTracker,
+ DumpManager dumpManager,
+ FeatureFlags featureFlags,
+ @Main Resources resources,
+ WakefulnessLifecycle wakefulnessLifecycle) {
mContext = context;
mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
mDeviceProvisionedController = deviceProvisionedController;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index a4180fd..512c351 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -33,6 +33,7 @@
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
@@ -118,6 +119,11 @@
@Mock
private LogBuffer mLogBuffer;
+ private final View mFakeDateView = (View) (new ViewGroup(mContext) {
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {}
+ });
+ private final View mFakeWeatherView = new View(mContext);
private final View mFakeSmartspaceView = new View(mContext);
private KeyguardClockSwitchController mController;
@@ -145,6 +151,8 @@
when(mLargeClockView.getContext()).thenReturn(getContext());
when(mView.isAttachedToWindow()).thenReturn(true);
+ when(mSmartspaceController.buildAndConnectDateView(any())).thenReturn(mFakeDateView);
+ when(mSmartspaceController.buildAndConnectWeatherView(any())).thenReturn(mFakeWeatherView);
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mExecutor = new FakeExecutor(new FakeSystemClock());
mController = new KeyguardClockSwitchController(
@@ -252,6 +260,19 @@
}
@Test
+ public void onLocaleListChanged_rebuildsSmartspaceViews_whenDecouplingEnabled() {
+ when(mSmartspaceController.isEnabled()).thenReturn(true);
+ when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true);
+ mController.init();
+
+ mController.onLocaleListChanged();
+ // Should be called once on initial setup, then once again for locale change
+ verify(mSmartspaceController, times(2)).buildAndConnectDateView(mView);
+ verify(mSmartspaceController, times(2)).buildAndConnectWeatherView(mView);
+ verify(mSmartspaceController, times(2)).buildAndConnectView(mView);
+ }
+
+ @Test
public void testSmartspaceDisabledShowsKeyguardStatusArea() {
when(mSmartspaceController.isEnabled()).thenReturn(false);
mController.init();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
index af46d9b..4b41537 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
@@ -16,30 +16,23 @@
package com.android.systemui.biometrics.udfps
-import android.graphics.Point
import android.graphics.Rect
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.EllipseOverlapDetectorParams
import com.google.common.truth.Truth.assertThat
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.`when` as whenEver
@SmallTest
@RunWith(Parameterized::class)
class EllipseOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() {
- val underTest = spy(EllipseOverlapDetector(neededPoints = 1))
-
- @Before
- fun setUp() {
- // Use one single center point for testing, required or total number of points may change
- whenEver(underTest.calculateSensorPoints(SENSOR))
- .thenReturn(listOf(Point(SENSOR.centerX(), SENSOR.centerY())))
- }
+ val underTest =
+ EllipseOverlapDetector(
+ EllipseOverlapDetectorParams(minOverlap = .4f, targetSize = .2f, stepSize = 1)
+ )
@Test
fun isGoodOverlap() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 3a871b4..702f3763 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -437,6 +438,43 @@
}
@Test
+ fun `DOZING to GONE`() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to DOZING
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN biometrics succeeds with wake and unlock mode
+ keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DOZING should occur
+ assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.DOZING)
+ assertThat(info.to).isEqualTo(KeyguardState.GONE)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun `GONE to DOZING`() =
testScope.runTest {
// GIVEN a device with AOD not available
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
new file mode 100644
index 0000000..a5b78b74
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardTransitionAnimationFlowTest : SysuiTestCase() {
+ private lateinit var underTest: KeyguardTransitionAnimationFlow
+ private lateinit var repository: FakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardTransitionRepository()
+ underTest =
+ KeyguardTransitionAnimationFlow(
+ 1000.milliseconds,
+ repository.transitions,
+ )
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun zeroDurationThrowsException() = runTest {
+ val flow = underTest.createFlow(duration = 0.milliseconds, onStep = { it })
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun startTimePlusDurationGreaterThanTransitionDurationThrowsException() = runTest {
+ val flow =
+ underTest.createFlow(
+ startTime = 300.milliseconds,
+ duration = 800.milliseconds,
+ onStep = { it }
+ )
+ }
+
+ @Test
+ fun onFinishRunsWhenSpecified() = runTest {
+ val flow =
+ underTest.createFlow(
+ duration = 100.milliseconds,
+ onStep = { it },
+ onFinish = { 10f },
+ )
+ var animationValues = collectLastValue(flow)
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(animationValues()).isEqualTo(10f)
+ }
+
+ @Test
+ fun onCancelRunsWhenSpecified() = runTest {
+ val flow =
+ underTest.createFlow(
+ duration = 100.milliseconds,
+ onStep = { it },
+ onCancel = { 100f },
+ )
+ var animationValues = collectLastValue(flow)
+ repository.sendTransitionStep(step(0.5f, TransitionState.CANCELED))
+ assertThat(animationValues()).isEqualTo(100f)
+ }
+
+ @Test
+ fun usesStartTime() = runTest {
+ val flow =
+ underTest.createFlow(
+ startTime = 500.milliseconds,
+ duration = 500.milliseconds,
+ onStep = { it },
+ )
+ var animationValues = collectLastValue(flow)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(animationValues()).isEqualTo(0f)
+
+ // Should not emit a value
+ repository.sendTransitionStep(step(0.1f, TransitionState.RUNNING))
+
+ repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 0f)
+ repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 0.2f)
+ repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 0.6f)
+ repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 1f)
+ }
+
+ @Test
+ fun usesInterpolator() = runTest {
+ val flow =
+ underTest.createFlow(
+ duration = 1000.milliseconds,
+ interpolator = EMPHASIZED_ACCELERATE,
+ onStep = { it },
+ )
+ var animationValues = collectLastValue(flow)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0f))
+ repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING))
+ assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.5f))
+ repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+ assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.6f))
+ repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+ assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.8f))
+ repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+ assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(1f))
+ }
+
+ @Test
+ fun usesOnStepToDoubleValue() = runTest {
+ val flow =
+ underTest.createFlow(
+ duration = 1000.milliseconds,
+ onStep = { it * 2 },
+ )
+ var animationValues = collectLastValue(flow)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertFloat(animationValues(), 0f)
+ repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 0.6f)
+ repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 1.2f)
+ repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 1.6f)
+ repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 2f)
+ }
+
+ private fun assertFloat(actual: Float?, expected: Float) {
+ assertThat(actual!!).isWithin(0.01f).of(expected)
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.DREAMING,
+ value = value,
+ transitionState = state,
+ ownerName = "GoneToDreamingTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index 5571663..06e397d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -18,19 +18,13 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_TRANSLATION_Y
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -46,6 +40,7 @@
class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: DreamingToLockscreenTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var transitionAnimation: KeyguardTransitionAnimationFlow
@Before
fun setUp() {
@@ -63,32 +58,18 @@
val job =
underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+ // Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(0.6f))
+ // ...up to here
+ repository.sendTransitionStep(step(0.8f))
repository.sendTransitionStep(step(1f))
- // Only 3 values should be present, since the dream overlay runs for a small fraction
- // of the overall animation time
- assertThat(values.size).isEqualTo(3)
- assertThat(values[0])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0f, DREAM_OVERLAY_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.3f, DREAM_OVERLAY_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.5f, DREAM_OVERLAY_TRANSLATION_Y)
- ) * pixels
- )
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
job.cancel()
}
@@ -100,16 +81,18 @@
val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this)
+ // Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.5f))
+ // ...up to here
repository.sendTransitionStep(step(1f))
// Only two values should be present, since the dream overlay runs for a small fraction
// of the overall animation time
- assertThat(values.size).isEqualTo(2)
- assertThat(values[0]).isEqualTo(1f - animValue(0f, DREAM_OVERLAY_ALPHA))
- assertThat(values[1]).isEqualTo(1f - animValue(0.1f, DREAM_OVERLAY_ALPHA))
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -121,19 +104,15 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
- // Should start running here...
repository.sendTransitionStep(step(0.2f))
repository.sendTransitionStep(step(0.3f))
- // ...up to here
repository.sendTransitionStep(step(1f))
- // Only two values should be present, since the dream overlay runs for a small fraction
- // of the overall animation time
- assertThat(values.size).isEqualTo(2)
- assertThat(values[0]).isEqualTo(animValue(0.2f, LOCKSCREEN_ALPHA))
- assertThat(values[1]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA))
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -147,58 +126,27 @@
val job =
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
repository.sendTransitionStep(step(1f))
- assertThat(values.size).isEqualTo(4)
- assertThat(values[0])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[3])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(1f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
job.cancel()
}
- private fun animValue(stepValue: Float, params: AnimationParams): Float {
- val totalDuration = TO_LOCKSCREEN_DURATION
- val startValue = (params.startTime / totalDuration).toFloat()
-
- val multiplier = (totalDuration / params.duration).toFloat()
- return (stepValue - startValue) * multiplier
- }
-
- private fun step(value: Float): TransitionStep {
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
return TransitionStep(
from = KeyguardState.DREAMING,
to = KeyguardState.LOCKSCREEN,
value = value,
- transitionState = TransitionState.RUNNING,
+ transitionState = state,
ownerName = "DreamingToLockscreenTransitionViewModelTest"
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index 7fa204b..14c3b50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -59,20 +55,18 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
// Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.2f))
- // ...up to here
repository.sendTransitionStep(step(0.3f))
+ // ...up to here
repository.sendTransitionStep(step(1f))
// Only three values should be present, since the dream overlay runs for a small
- // fraction
- // of the overall animation time
- assertThat(values.size).isEqualTo(3)
- assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
- assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
- assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA))
+ // fraction of the overall animation time
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -87,45 +81,19 @@
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
// Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
- // ...up to here
- repository.sendTransitionStep(step(1f))
// And a final reset event on CANCEL
repository.sendTransitionStep(step(0.8f, TransitionState.CANCELED))
- assertThat(values.size).isEqualTo(4)
- assertThat(values[0])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[3]).isEqualTo(0f)
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+
job.cancel()
}
- private fun animValue(stepValue: Float, params: AnimationParams): Float {
- val totalDuration = TO_DREAMING_DURATION
- val startValue = (params.startTime / totalDuration).toFloat()
-
- val multiplier = (totalDuration / params.duration).toFloat()
- return (stepValue - startValue) * multiplier
- }
-
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index 539fc2c..ed31dc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -59,19 +55,18 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
// Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.2f))
- // ...up to here
repository.sendTransitionStep(step(0.3f))
+ // ...up to here
repository.sendTransitionStep(step(1f))
// Only three values should be present, since the dream overlay runs for a small
// fraction of the overall animation time
- assertThat(values.size).isEqualTo(3)
- assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
- assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
- assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA))
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -85,47 +80,22 @@
val job =
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
- // Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
- // ...up to here
repository.sendTransitionStep(step(1f))
// And a final reset event on FINISHED
repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
- assertThat(values.size).isEqualTo(4)
- assertThat(values[0])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[3]).isEqualTo(0f)
+ assertThat(values.size).isEqualTo(6)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+ // Validate finished value
+ assertThat(values[5]).isEqualTo(0f)
job.cancel()
}
- private fun animValue(stepValue: Float, params: AnimationParams): Float {
- val totalDuration = TO_DREAMING_DURATION
- val startValue = (params.startTime / totalDuration).toFloat()
-
- val multiplier = (totalDuration / params.duration).toFloat()
- return (stepValue - startValue) * multiplier
- }
-
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index 759345f..458b315 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -59,19 +55,18 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
// Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.4f))
- // ...up to here
repository.sendTransitionStep(step(0.7f))
+ // ...up to here
repository.sendTransitionStep(step(1f))
// Only 3 values should be present, since the dream overlay runs for a small fraction
// of the overall animation time
- assertThat(values.size).isEqualTo(3)
- assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
- assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
- assertThat(values[2]).isEqualTo(1f - animValue(0.4f, LOCKSCREEN_ALPHA))
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -86,54 +81,51 @@
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
// Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
repository.sendTransitionStep(step(1f))
// ...up to here
- assertThat(values.size).isEqualTo(4)
- assertThat(values[0])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[3])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(1f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+
job.cancel()
}
- private fun animValue(stepValue: Float, params: AnimationParams): Float {
- val totalDuration = TO_OCCLUDED_DURATION
- val startValue = (params.startTime / totalDuration).toFloat()
+ @Test
+ fun lockscreenTranslationYIsCanceled() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
- val multiplier = (totalDuration / params.duration).toFloat()
- return (stepValue - startValue) * multiplier
- }
+ val pixels = 100
+ val job =
+ underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
- private fun step(value: Float): TransitionStep {
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.3f, TransitionState.CANCELED))
+
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+
+ // Cancel will reset the translation
+ assertThat(values[3]).isEqualTo(0)
+
+ job.cancel()
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING,
+ ): TransitionStep {
return TransitionStep(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.OCCLUDED,
value = value,
- transitionState = TransitionState.RUNNING,
+ transitionState = state,
ownerName = "LockscreenToOccludedTransitionViewModelTest"
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index 98d292d..a36214e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -58,21 +54,19 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
- repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.1f))
// Should start running here...
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.4f))
repository.sendTransitionStep(step(0.5f))
- // ...up to here
repository.sendTransitionStep(step(0.6f))
+ // ...up to here
+ repository.sendTransitionStep(step(0.8f))
repository.sendTransitionStep(step(1f))
- // Only two values should be present, since the dream overlay runs for a small fraction
- // of the overall animation time
- assertThat(values.size).isEqualTo(3)
- assertThat(values[0]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA))
- assertThat(values[1]).isEqualTo(animValue(0.4f, LOCKSCREEN_ALPHA))
- assertThat(values[2]).isEqualTo(animValue(0.5f, LOCKSCREEN_ALPHA))
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -86,58 +80,27 @@
val job =
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
repository.sendTransitionStep(step(1f))
- assertThat(values.size).isEqualTo(4)
- assertThat(values[0])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[3])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(1f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
job.cancel()
}
- private fun animValue(stepValue: Float, params: AnimationParams): Float {
- val totalDuration = TO_LOCKSCREEN_DURATION
- val startValue = (params.startTime / totalDuration).toFloat()
-
- val multiplier = (totalDuration / params.duration).toFloat()
- return (stepValue - startValue) * multiplier
- }
-
- private fun step(value: Float): TransitionStep {
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
return TransitionStep(
from = KeyguardState.OCCLUDED,
to = KeyguardState.LOCKSCREEN,
value = value,
- transitionState = TransitionState.RUNNING,
+ transitionState = state,
ownerName = "OccludedToLockscreenTransitionViewModelTest"
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
index c0639f3..0a5b124 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
@@ -79,7 +79,7 @@
USER_ID, true, APP, null, ARTIST, TITLE, null,
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
- InstanceId.fakeInstanceId(-1), -1, false);
+ InstanceId.fakeInstanceId(-1), -1, false, null);
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 1ac6695..53cc78f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -644,27 +644,8 @@
build()
}
val currentTime = clock.elapsedRealtime()
- mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
- )
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- // THEN the media data indicates that it is for resumption
- verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
- )
+ addResumeControlAndLoad(desc)
+
val data = mediaDataCaptor.value
assertThat(data.resumption).isTrue()
assertThat(data.song).isEqualTo(SESSION_TITLE)
@@ -690,27 +671,8 @@
build()
}
val currentTime = clock.elapsedRealtime()
- mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
- )
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- // THEN the media data indicates that it is for resumption
- verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
- )
+ addResumeControlAndLoad(desc)
+
val data = mediaDataCaptor.value
assertThat(data.resumption).isTrue()
assertThat(data.song).isEqualTo(SESSION_TITLE)
@@ -723,6 +685,84 @@
}
@Test
+ fun testAddResumptionControls_hasPartialProgress() {
+ whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+ // WHEN resumption controls are added with partial progress
+ val progress = 0.5
+ val extras =
+ Bundle().apply {
+ putInt(
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+ )
+ putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, progress)
+ }
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ setExtras(extras)
+ build()
+ }
+ addResumeControlAndLoad(desc)
+
+ val data = mediaDataCaptor.value
+ assertThat(data.resumption).isTrue()
+ assertThat(data.resumeProgress).isEqualTo(progress)
+ }
+
+ @Test
+ fun testAddResumptionControls_hasNotPlayedProgress() {
+ whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+ // WHEN resumption controls are added that have not been played
+ val extras =
+ Bundle().apply {
+ putInt(
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
+ )
+ }
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ setExtras(extras)
+ build()
+ }
+ addResumeControlAndLoad(desc)
+
+ val data = mediaDataCaptor.value
+ assertThat(data.resumption).isTrue()
+ assertThat(data.resumeProgress).isEqualTo(0)
+ }
+
+ @Test
+ fun testAddResumptionControls_hasFullProgress() {
+ whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+ // WHEN resumption controls are added with progress info
+ val extras =
+ Bundle().apply {
+ putInt(
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
+ )
+ }
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ setExtras(extras)
+ build()
+ }
+ addResumeControlAndLoad(desc)
+
+ // THEN the media data includes the progress
+ val data = mediaDataCaptor.value
+ assertThat(data.resumption).isTrue()
+ assertThat(data.resumeProgress).isEqualTo(1)
+ }
+
+ @Test
fun testResumptionDisabled_dismissesResumeControls() {
// WHEN there are resume controls and resumption is switched off
val desc =
@@ -730,26 +770,8 @@
setTitle(SESSION_TITLE)
build()
}
- mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
- )
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
- )
+ addResumeControlAndLoad(desc)
+
val data = mediaDataCaptor.value
mediaDataManager.setMediaResumptionEnabled(false)
@@ -1690,4 +1712,29 @@
stateBuilder.setState(PlaybackState.STATE_PAUSED, 0, 1.0f)
whenever(controller.playbackState).thenReturn(stateBuilder.build())
}
+
+ /** Helper function to add a resumption control and capture the resulting MediaData */
+ private fun addResumeControlAndLoad(desc: MediaDescription) {
+ mediaDataManager.addResumptionControls(
+ USER_ID,
+ desc,
+ Runnable {},
+ session.sessionToken,
+ APP_NAME,
+ pendingIntent,
+ PACKAGE_NAME
+ )
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index ce22b19..26b9204 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -906,6 +906,17 @@
}
@Test
+ fun bind_resumeState_withProgress() {
+ val progress = 0.5
+ val state = mediaData.copy(resumption = true, resumeProgress = progress)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(state, PACKAGE)
+
+ verify(seekBarViewModel).updateStaticProgress(progress)
+ }
+
+ @Test
fun bindNotificationActions() {
val icon = context.getDrawable(android.R.drawable.ic_media_play)
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 5564774..f40e3a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -16,7 +16,7 @@
package com.android.systemui.media.dialog;
-import static android.media.RouteListingPreference.Item.DISABLE_REASON_SUBSCRIPTION_REQUIRED;
+import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
import static com.google.common.truth.Truth.assertThat;
@@ -436,7 +436,7 @@
R.string.media_output_status_require_premium);
when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
when(mMediaDevice2.hasDisabledReason()).thenReturn(true);
- when(mMediaDevice2.getDisableReason()).thenReturn(DISABLE_REASON_SUBSCRIPTION_REQUIRED);
+ when(mMediaDevice2.getDisableReason()).thenReturn(SUBTEXT_SUBSCRIPTION_REQUIRED);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
index 576652f..3440f91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
@@ -59,9 +59,7 @@
@RunWith(AndroidTestingRunner.class)
public class WorkProfileMessageControllerTest extends SysuiTestCase {
private static final String DEFAULT_LABEL = "default label";
- private static final String BADGED_DEFAULT_LABEL = "badged default label";
private static final String APP_LABEL = "app label";
- private static final String BADGED_APP_LABEL = "badged app label";
private static final UserHandle NON_WORK_USER = UserHandle.of(0);
private static final UserHandle WORK_USER = UserHandle.of(10);
@@ -91,10 +89,6 @@
eq(WorkProfileMessageController.SHARED_PREFERENCES_NAME),
eq(Context.MODE_PRIVATE))).thenReturn(mSharedPreferences);
when(mMockContext.getString(ArgumentMatchers.anyInt())).thenReturn(DEFAULT_LABEL);
- when(mPackageManager.getUserBadgedLabel(eq(DEFAULT_LABEL), any()))
- .thenReturn(BADGED_DEFAULT_LABEL);
- when(mPackageManager.getUserBadgedLabel(eq(APP_LABEL), any()))
- .thenReturn(BADGED_APP_LABEL);
when(mPackageManager.getActivityIcon(any(ComponentName.class)))
.thenReturn(mActivityIcon);
when(mPackageManager.getUserBadgedIcon(
@@ -133,7 +127,7 @@
WorkProfileMessageController.WorkProfileFirstRunData data =
mMessageController.onScreenshotTaken(WORK_USER);
- assertEquals(BADGED_DEFAULT_LABEL, data.getAppName());
+ assertEquals(DEFAULT_LABEL, data.getAppName());
assertNull(data.getIcon());
}
@@ -142,7 +136,7 @@
WorkProfileMessageController.WorkProfileFirstRunData data =
mMessageController.onScreenshotTaken(WORK_USER);
- assertEquals(BADGED_APP_LABEL, data.getAppName());
+ assertEquals(APP_LABEL, data.getAppName());
assertEquals(mBadgedActivityIcon, data.getIcon());
}
@@ -151,7 +145,7 @@
ViewGroup layout = (ViewGroup) LayoutInflater.from(mContext).inflate(
R.layout.screenshot_work_profile_first_run, null);
WorkProfileMessageController.WorkProfileFirstRunData data =
- new WorkProfileMessageController.WorkProfileFirstRunData(BADGED_APP_LABEL,
+ new WorkProfileMessageController.WorkProfileFirstRunData(APP_LABEL,
mBadgedActivityIcon);
final CountDownLatch countdown = new CountDownLatch(1);
mMessageController.populateView(layout, data, () -> {
@@ -163,7 +157,7 @@
assertEquals(mBadgedActivityIcon, image.getDrawable());
TextView text = layout.findViewById(R.id.screenshot_message_content);
// The app name is used in a template, but at least validate that it was inserted.
- assertTrue(text.getText().toString().contains(BADGED_APP_LABEL));
+ assertTrue(text.getText().toString().contains(APP_LABEL));
// Validate that clicking the dismiss button calls back properly.
assertEquals(1, countdown.getCount());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 2423f13..0a576de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -113,6 +113,9 @@
private lateinit var handler: Handler
@Mock
+ private lateinit var datePlugin: BcSmartspaceDataPlugin
+
+ @Mock
private lateinit var weatherPlugin: BcSmartspaceDataPlugin
@Mock
@@ -155,6 +158,7 @@
KeyguardBypassController.OnBypassStateChangedListener
private lateinit var deviceProvisionedListener: DeviceProvisionedListener
+ private lateinit var dateSmartspaceView: SmartspaceView
private lateinit var weatherSmartspaceView: SmartspaceView
private lateinit var smartspaceView: SmartspaceView
@@ -190,6 +194,8 @@
`when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING))
.thenReturn(fakeNotifOnLockscreenSettingUri)
`when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession)
+ `when`(datePlugin.getView(any())).thenReturn(
+ createDateSmartspaceView(), createDateSmartspaceView())
`when`(weatherPlugin.getView(any())).thenReturn(
createWeatherSmartspaceView(), createWeatherSmartspaceView())
`when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView())
@@ -221,6 +227,7 @@
executor,
bgExecutor,
handler,
+ Optional.of(datePlugin),
Optional.of(weatherPlugin),
Optional.of(plugin),
Optional.of(configPlugin),
@@ -275,7 +282,8 @@
// THEN the listener is registered to the underlying plugin
verify(plugin).registerListener(controllerListener)
- // The listener is registered only for the plugin, not the weather plugin.
+ // The listener is registered only for the plugin, not the date, or weather plugin.
+ verify(datePlugin, never()).registerListener(any())
verify(weatherPlugin, never()).registerListener(any())
}
@@ -289,7 +297,8 @@
// THEN the listener is subsequently registered
verify(plugin).registerListener(controllerListener)
- // The listener is registered only for the plugin, not the weather plugin.
+ // The listener is registered only for the plugin, not the date, or the weather plugin.
+ verify(datePlugin, never()).registerListener(any())
verify(weatherPlugin, never()).registerListener(any())
}
@@ -308,6 +317,7 @@
verify(plugin).registerSmartspaceEventNotifier(null)
verify(weatherPlugin).onTargetsAvailable(emptyList())
verify(weatherPlugin).registerSmartspaceEventNotifier(null)
+ verify(datePlugin).registerSmartspaceEventNotifier(null)
}
@Test
@@ -357,6 +367,7 @@
configChangeListener.onThemeChanged()
// We update the new text color to match the wallpaper color
+ verify(dateSmartspaceView).setPrimaryTextColor(anyInt())
verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
verify(smartspaceView).setPrimaryTextColor(anyInt())
}
@@ -384,6 +395,7 @@
statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f)
// We pass that along to the view
+ verify(dateSmartspaceView).setDozeAmount(0.7f)
verify(weatherSmartspaceView).setDozeAmount(0.7f)
verify(smartspaceView).setDozeAmount(0.7f)
}
@@ -502,6 +514,8 @@
verify(plugin).onTargetsAvailable(eq(listOf(targets[0], targets[1], targets[2])))
// No filtering is applied for the weather plugin
verify(weatherPlugin).onTargetsAvailable(eq(targets))
+ // No targets needed for the date plugin
+ verify(datePlugin, never()).onTargetsAvailable(any())
}
@Test
@@ -633,6 +647,18 @@
private fun connectSession() {
if (controller.isDateWeatherDecoupled()) {
+ val dateView = controller.buildAndConnectDateView(fakeParent)
+ dateSmartspaceView = dateView as SmartspaceView
+ fakeParent.addView(dateView)
+ controller.stateChangeListener.onViewAttachedToWindow(dateView)
+
+ verify(dateSmartspaceView).setUiSurface(
+ BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
+ verify(dateSmartspaceView).registerDataProvider(datePlugin)
+
+ verify(dateSmartspaceView).setPrimaryTextColor(anyInt())
+ verify(dateSmartspaceView).setDozeAmount(0.5f)
+
val weatherView = controller.buildAndConnectWeatherView(fakeParent)
weatherSmartspaceView = weatherView as SmartspaceView
fakeParent.addView(weatherView)
@@ -686,6 +712,7 @@
verify(smartspaceView).setDozeAmount(0.5f)
if (controller.isDateWeatherDecoupled()) {
+ clearInvocations(dateSmartspaceView)
clearInvocations(weatherSmartspaceView)
}
clearInvocations(smartspaceView)
@@ -734,7 +761,38 @@
).thenReturn(if (value) 1 else 0)
}
- // Separate function for the weather view, which doesn't implement all functions in interface.
+ // Separate function for the date view, which implements a specific subset of all functions.
+ private fun createDateSmartspaceView(): SmartspaceView {
+ return spy(object : View(context), SmartspaceView {
+ override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
+ }
+
+ override fun setPrimaryTextColor(color: Int) {
+ }
+
+ override fun setIsDreaming(isDreaming: Boolean) {
+ }
+
+ override fun setUiSurface(uiSurface: String) {
+ }
+
+ override fun setDozeAmount(amount: Float) {
+ }
+
+ override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {
+ }
+
+ override fun setFalsingManager(falsingManager: FalsingManager?) {
+ }
+
+ override fun setDnd(image: Drawable?, description: String?) {
+ }
+
+ override fun setNextAlarm(image: Drawable?, description: String?) {
+ }
+ })
+ }
+ // Separate function for the weather view, which implements a specific subset of all functions.
private fun createWeatherSmartspaceView(): SmartspaceView {
return spy(object : View(context), SmartspaceView {
override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 36e76f4..85e8c34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -23,10 +23,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -45,6 +47,7 @@
import androidx.test.filters.SmallTest;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.dump.DumpManager;
@@ -67,6 +70,8 @@
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
@@ -79,6 +84,9 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -118,6 +126,12 @@
private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
@Mock
private DumpManager mDumpManager;
+ @Mock
+ private StatusBarWindowStateController mStatusBarWindowStateController;
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+ private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>();
public CollapsedStatusBarFragmentTest() {
super(CollapsedStatusBarFragment.class);
@@ -127,6 +141,14 @@
public void setup() {
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
mDependency.injectMockDependency(DarkIconDispatcher.class);
+
+ // Keep the window state listeners so we can dispatch to them to test the status bar
+ // fragment's response.
+ doAnswer(invocation -> {
+ mStatusBarWindowStateListeners.add(invocation.getArgument(0));
+ return null;
+ }).when(mStatusBarWindowStateController).addListener(
+ any(StatusBarWindowStateListener.class));
}
@Test
@@ -414,6 +436,27 @@
assertFalse(contains);
}
+ @Test
+ public void testStatusBarIcons_hiddenThroughoutCameraLaunch() {
+ final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+ mockSecureCameraLaunch(fragment, true /* launched */);
+
+ // Status icons should be invisible or gone, but certainly not VISIBLE.
+ assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ assertNotEquals(View.VISIBLE, getClockView().getVisibility());
+
+ mockSecureCameraLaunchFinished();
+
+ assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ assertNotEquals(View.VISIBLE, getClockView().getVisibility());
+
+ mockSecureCameraLaunch(fragment, false /* launched */);
+
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ assertEquals(View.VISIBLE, getClockView().getVisibility());
+ }
+
@Override
protected Fragment instantiate(Context context, String className, Bundle arguments) {
MockitoAnnotations.initMocks(this);
@@ -455,7 +498,9 @@
mOperatorNameViewControllerFactory,
mSecureSettings,
mExecutor,
- mDumpManager);
+ mDumpManager,
+ mStatusBarWindowStateController,
+ mKeyguardUpdateMonitor);
}
private void setUpDaggerComponent() {
@@ -478,6 +523,35 @@
mNotificationAreaInner);
}
+ /**
+ * Configure mocks to return values consistent with the secure camera animating itself launched
+ * over the keyguard.
+ */
+ private void mockSecureCameraLaunch(CollapsedStatusBarFragment fragment, boolean launched) {
+ when(mKeyguardUpdateMonitor.isSecureCameraLaunchedOverKeyguard()).thenReturn(launched);
+ when(mKeyguardStateController.isOccluded()).thenReturn(launched);
+
+ if (launched) {
+ fragment.onCameraLaunchGestureDetected(0 /* source */);
+ } else {
+ for (StatusBarWindowStateListener listener : mStatusBarWindowStateListeners) {
+ listener.onStatusBarWindowStateChanged(StatusBarManager.WINDOW_STATE_SHOWING);
+ }
+ }
+
+ fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+ }
+
+ /**
+ * Configure mocks to return values consistent with the secure camera showing over the keyguard
+ * with its launch animation finished.
+ */
+ private void mockSecureCameraLaunchFinished() {
+ for (StatusBarWindowStateListener listener : mStatusBarWindowStateListeners) {
+ listener.onStatusBarWindowStateChanged(StatusBarManager.WINDOW_STATE_HIDDEN);
+ }
+ }
+
private CollapsedStatusBarFragment resumeAndGetFragment() {
mFragments.dispatchResume();
processAllMessages();
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index f93f504..b6a2a0e 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -96,7 +96,6 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
/**
@@ -287,7 +286,7 @@
* - dynamically installed mobile bundled apps (MBAs) (new in Android U)
*/
public void recordMeasurementsForAllPackages() {
- // check if we should record the resulting measurements
+ // check if we should measure and record
long currentTimeMs = System.currentTimeMillis();
if ((currentTimeMs - mMeasurementsLastRecordedMs) < RECORD_MEASUREMENTS_COOLDOWN_MS) {
Slog.d(TAG, "Skip measurement since the last measurement was only taken at "
@@ -1230,10 +1229,8 @@
* JobService to measure all covered binaries and record result to Westworld.
*/
public static class UpdateMeasurementsJobService extends JobService {
- private static AtomicBoolean sScheduled = new AtomicBoolean();
private static long sTimeLastRanMs = 0;
- private static final int DO_BINARY_MEASUREMENTS_JOB_ID =
- UpdateMeasurementsJobService.class.hashCode();
+ private static final int DO_BINARY_MEASUREMENTS_JOB_ID = 1740526926;
@Override
public boolean onStartJob(JobParameters params) {
@@ -1256,7 +1253,6 @@
return;
}
sTimeLastRanMs = System.currentTimeMillis();
- sScheduled.set(false);
jobFinished(params, false);
}).start();
@@ -1277,7 +1273,7 @@
return;
}
- if (sScheduled.get()) {
+ if (jobScheduler.getPendingJob(DO_BINARY_MEASUREMENTS_JOB_ID) != null) {
Slog.d(TAG, "A measurement job has already been scheduled.");
return;
}
@@ -1303,7 +1299,6 @@
Slog.e(TAG, "Failed to schedule job to measure binaries.");
return;
}
- sScheduled.set(true);
Slog.d(TAG, TextUtils.formatSimple(
"Job %d to measure binaries was scheduled successfully.",
DO_BINARY_MEASUREMENTS_JOB_ID));
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 4854c37..4b76127 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -333,6 +333,11 @@
// Update ownership for system applications and the installers eligible to update them.
private final ArrayMap<String, String> mUpdateOwnersForSystemApps = new ArrayMap<>();
+ // Set of package names that should not be marked as "stopped" during initial device boot
+ // or when adding a new user. A new package not contained in this set will be
+ // marked as stopped by the system
+ @NonNull private final Set<String> mInitialNonStoppedSystemPackages = new ArraySet<>();
+
/**
* Map of system pre-defined, uniquely named actors; keys are namespace,
* value maps actor name to package name.
@@ -527,6 +532,10 @@
? null : mOverlayConfigSignaturePackage;
}
+ public Set<String> getInitialNonStoppedSystemPackages() {
+ return mInitialNonStoppedSystemPackages;
+ }
+
/**
* Only use for testing. Do NOT use in production code.
* @param readPermissions false to create an empty SystemConfig; true to read the permissions.
@@ -1445,6 +1454,19 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "initial-package-state": {
+ String pkgName = parser.getAttributeValue(null, "package");
+ String stopped = parser.getAttributeValue(null, "stopped");
+ if (TextUtils.isEmpty(pkgName)) {
+ Slog.w(TAG, "<" + name + "> without package in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else if (TextUtils.isEmpty(stopped)) {
+ Slog.w(TAG, "<" + name + "> without stopped in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else if (!Boolean.parseBoolean(stopped)) {
+ mInitialNonStoppedSystemPackages.add(pkgName);
+ }
+ }
default: {
Slog.w(TAG, "Tag " + name + " is unknown in "
+ permFile + " at " + parser.getPositionDescription());
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index ae65dcb..cc8aec7 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -69,5 +69,11 @@
],
"file_patterns": ["ClipboardService\\.java"]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "BinaryTransparencyHostTest",
+ "file_patterns": ["BinaryTransparencyService\\.java"]
+ }
]
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 57a89e3..423a090 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18974,14 +18974,20 @@
return null;
}
- // Starts with all displays but DEFAULT_DISPLAY
- int[] displayIds = new int[allDisplays.length - 1];
+ boolean allowOnDefaultDisplay = UserManager
+ .isVisibleBackgroundUsersOnDefaultDisplayEnabled();
+ int displaysSize = allDisplays.length;
+ if (!allowOnDefaultDisplay) {
+ displaysSize--;
+ }
+ int[] displayIds = new int[displaysSize];
- // TODO(b/247592632): check for other properties like isSecure or proper display type
int numberValidDisplays = 0;
for (Display display : allDisplays) {
int displayId = display.getDisplayId();
- if (display.isValid() && displayId != Display.DEFAULT_DISPLAY) {
+ // TODO(b/247592632): check other properties like isSecure or proper display type
+ if (display.isValid()
+ && (allowOnDefaultDisplay || displayId != Display.DEFAULT_DISPLAY)) {
displayIds[numberValidDisplays++] = displayId;
}
}
@@ -18993,14 +18999,15 @@
// STOPSHIP: if not removed, it should at least be unit tested
String testingProp = "fw.display_ids_for_starting_users_for_testing_purposes";
int displayId = SystemProperties.getInt(testingProp, Display.DEFAULT_DISPLAY);
- if (displayId != Display.DEFAULT_DISPLAY && displayId > 0) {
- Slogf.w(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): no valid "
+ if (allowOnDefaultDisplay && displayId == Display.DEFAULT_DISPLAY
+ || displayId > 0) {
+ Slogf.w(TAG, "getDisplayIdsForStartingVisibleBackgroundUsers(): no valid "
+ "display found, but returning %d as set by property %s", displayId,
testingProp);
return new int[] { displayId };
}
- Slogf.e(TAG, "getDisplayIdsForStartingBackgroundUsers(): no valid display on %s",
- Arrays.toString(allDisplays));
+ Slogf.e(TAG, "getDisplayIdsForStartingVisibleBackgroundUsers(): no valid display"
+ + " on %s", Arrays.toString(allDisplays));
return null;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ed59a88..d888c81 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1462,7 +1462,7 @@
mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- mSoundDoseHelper.configureSafeMediaVolume(/*forced=*/true, TAG);
+ mSoundDoseHelper.configureSafeMedia(/*forced=*/true, TAG);
initA11yMonitoring();
@@ -10236,7 +10236,7 @@
// reading new configuration "safely" (i.e. under try catch) in case anything
// goes wrong.
Configuration config = context.getResources().getConfiguration();
- mSoundDoseHelper.configureSafeMediaVolume(/*forced*/false, TAG);
+ mSoundDoseHelper.configureSafeMedia(/*forced*/false, TAG);
boolean cameraSoundForced = readCameraSoundForced();
synchronized (mSettingsLock) {
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 42031c6..ac02eb7 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -38,6 +38,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
import android.util.MathUtils;
@@ -55,6 +56,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* Safe media volume management.
@@ -77,14 +79,16 @@
// SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it
// can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume()
// (when user opts out).
+ // Note: when CSD calculation is enabled the state is set to SAFE_MEDIA_VOLUME_DISABLED
private static final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0;
private static final int SAFE_MEDIA_VOLUME_DISABLED = 1;
private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2; // confirmed
private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3; // unconfirmed
- private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = SAFE_MEDIA_VOLUME_MSG_START + 1;
+ private static final int MSG_CONFIGURE_SAFE_MEDIA = SAFE_MEDIA_VOLUME_MSG_START + 1;
private static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 2;
private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 3;
+ private static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 4;
private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
@@ -100,14 +104,16 @@
private static final int CSD_WARNING_TIMEOUT_MS_ACCUMULATION_START = -1;
private static final int CSD_WARNING_TIMEOUT_MS_MOMENTARY_EXPOSURE = 5000;
+ private static final String PERSIST_CSD_RECORD_FIELD_SEPARATOR = ",";
+ private static final String PERSIST_CSD_RECORD_SEPARATOR_CHAR = "|";
+ private static final String PERSIST_CSD_RECORD_SEPARATOR = "\\|";
+
private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
"CSD updates");
private int mMcc = 0;
- private final boolean mEnableCsd;
-
- final Object mSafeMediaVolumeStateLock = new Object();
+ private final Object mSafeMediaVolumeStateLock = new Object();
private int mSafeMediaVolumeState;
// Used when safe volume warning message display is requested by setStreamVolume(). In this
@@ -148,10 +154,18 @@
@NonNull private final AudioHandler mAudioHandler;
@NonNull private final ISafeHearingVolumeController mVolumeController;
+ private final boolean mEnableCsd;
+
private ISoundDose mSoundDose;
+
+ private final Object mCsdStateLock = new Object();
+
+ @GuardedBy("mCsdStateLock")
private float mCurrentCsd = 0.f;
// dose at which the next dose reached warning occurs
+ @GuardedBy("mCsdStateLock")
private float mNextCsdWarning = 1.0f;
+ @GuardedBy("mCsdStateLock")
private final List<SoundDoseRecord> mDoseRecords = new ArrayList<>();
private final Context mContext;
@@ -169,11 +183,16 @@
}
public void onNewCsdValue(float currentCsd, SoundDoseRecord[] records) {
+ if (!mEnableCsd) {
+ Log.w(TAG, "onNewCsdValue: csd not supported, ignoring value");
+ return;
+ }
+
Log.i(TAG, "onNewCsdValue: " + currentCsd);
- if (mCurrentCsd < currentCsd) {
- // dose increase: going over next threshold?
- if ((mCurrentCsd < mNextCsdWarning) && (currentCsd >= mNextCsdWarning)) {
- if (mEnableCsd) {
+ synchronized (mCsdStateLock) {
+ if (mCurrentCsd < currentCsd) {
+ // dose increase: going over next threshold?
+ if ((mCurrentCsd < mNextCsdWarning) && (currentCsd >= mNextCsdWarning)) {
if (mNextCsdWarning == 5.0f) {
// 500% dose repeat
mVolumeController.postDisplayCsdWarning(
@@ -188,17 +207,18 @@
getTimeoutMsForWarning(
AudioManager.CSD_WARNING_DOSE_REACHED_1X));
}
+ mNextCsdWarning += 1.0f;
}
- mNextCsdWarning += 1.0f;
+ } else {
+ // dose decrease: dropping below previous threshold of warning?
+ if ((currentCsd < mNextCsdWarning - 1.0f) && (
+ mNextCsdWarning >= 2.0f)) {
+ mNextCsdWarning -= 1.0f;
+ }
}
- } else {
- // dose decrease: dropping below previous threshold of warning?
- if ((currentCsd < mNextCsdWarning - 1.0f) && (mNextCsdWarning >= 2.0f)) {
- mNextCsdWarning -= 1.0f;
- }
+ mCurrentCsd = currentCsd;
+ updateSoundDoseRecords_l(records, currentCsd);
}
- mCurrentCsd = currentCsd;
- updateSoundDoseRecords(records, currentCsd);
}
};
@@ -213,20 +233,22 @@
mContext = context;
- mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
- Settings.Global.AUDIO_SAFE_VOLUME_STATE, 0);
-
mEnableCsd = mContext.getResources().getBoolean(R.bool.config_audio_csd_enabled_default);
+ if (mEnableCsd) {
+ mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
+ } else {
+ mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
+ Settings.Global.AUDIO_SAFE_VOLUME_STATE, 0);
+ }
// The default safe volume index read here will be replaced by the actual value when
- // the mcc is read by onConfigureSafeVolume()
+ // the mcc is read by onConfigureSafeMedia()
+ // For now we use the same index for RS2 initial warning with CSD
mSafeMediaVolumeIndex = mContext.getResources().getInteger(
R.integer.config_safe_media_volume_index) * 10;
mAlarmManager = (AlarmManager) mContext.getSystemService(
Context.ALARM_SERVICE);
-
- initCsd();
}
float getRs2Value() {
@@ -456,8 +478,8 @@
}
}
- /*package*/ void configureSafeMediaVolume(boolean forced, String caller) {
- int msg = MSG_CONFIGURE_SAFE_MEDIA_VOLUME;
+ /*package*/ void configureSafeMedia(boolean forced, String caller) {
+ int msg = MSG_CONFIGURE_SAFE_MEDIA;
mAudioHandler.removeMessages(msg);
long time = 0;
@@ -507,8 +529,8 @@
/*package*/ void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:
- onConfigureSafeVolume((msg.arg1 == 1), (String) msg.obj);
+ case MSG_CONFIGURE_SAFE_MEDIA:
+ onConfigureSafeMedia((msg.arg1 == 1), (String) msg.obj);
break;
case MSG_PERSIST_SAFE_VOLUME_STATE:
onPersistSafeVolumeState(msg.arg1);
@@ -519,8 +541,13 @@
Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, musicActiveMs,
UserHandle.USER_CURRENT);
break;
+ case MSG_PERSIST_CSD_VALUES:
+ onPersistSoundDoseRecords();
+ break;
+ default:
+ Log.e(TAG, "Unexpected msg to handle: " + msg.what);
+ break;
}
-
}
/*package*/ void dump(PrintWriter pw) {
@@ -540,33 +567,44 @@
/*package*/void reset() {
Log.d(TAG, "Reset the sound dose helper");
- initCsd();
- }
-
- private void initCsd() {
- synchronized (mSafeMediaVolumeStateLock) {
- if (mEnableCsd) {
- Log.v(TAG, "Initializing sound dose");
-
- mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
- mSoundDose = AudioSystem.getSoundDoseInterface(mSoundDoseCallback);
- try {
- if (mSoundDose != null && mSoundDose.asBinder().isBinderAlive()) {
- if (mCurrentCsd != 0.f) {
- Log.d(TAG, "Resetting the saved sound dose value " + mCurrentCsd);
- SoundDoseRecord[] records = mDoseRecords.toArray(
- new SoundDoseRecord[0]);
- mSoundDose.resetCsd(mCurrentCsd, records);
- }
+ mSoundDose = AudioSystem.getSoundDoseInterface(mSoundDoseCallback);
+ synchronized (mCsdStateLock) {
+ try {
+ if (mSoundDose != null && mSoundDose.asBinder().isBinderAlive()) {
+ if (mCurrentCsd != 0.f) {
+ Log.d(TAG,
+ "Resetting the saved sound dose value " + mCurrentCsd);
+ SoundDoseRecord[] records = mDoseRecords.toArray(
+ new SoundDoseRecord[0]);
+ mSoundDose.resetCsd(mCurrentCsd, records);
}
- } catch (RemoteException e) {
- // noop
}
+ } catch (RemoteException e) {
+ // noop
}
}
}
- private void onConfigureSafeVolume(boolean force, String caller) {
+ private void initCsd() {
+ if (mEnableCsd) {
+ Log.v(TAG, "Initializing sound dose");
+
+ synchronized (mCsdStateLock) {
+ // Restore persisted values
+ mCurrentCsd = parseGlobalSettingFloat(
+ Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE, /* defaultValue= */0.f);
+ mNextCsdWarning = parseGlobalSettingFloat(
+ Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING, /* defaultValue= */1.f);
+ mDoseRecords.addAll(persistedStringToRecordList(
+ mSettings.getGlobalString(mAudioService.getContentResolver(),
+ Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS)));
+ }
+
+ reset();
+ }
+ }
+
+ private void onConfigureSafeMedia(boolean force, String caller) {
synchronized (mSafeMediaVolumeStateLock) {
int mcc = mContext.getResources().getConfiguration().mcc;
if ((mMcc != mcc) || ((mMcc == 0) && force)) {
@@ -612,6 +650,10 @@
/*obj=*/null), /*delay=*/0);
}
}
+
+ if (mEnableCsd) {
+ initCsd();
+ }
}
private int getTimeoutMsForWarning(@AudioManager.CsdWarning int csdWarning) {
@@ -702,7 +744,8 @@
return null;
}
- private void updateSoundDoseRecords(SoundDoseRecord[] newRecords, float currentCsd) {
+ @GuardedBy("mCsdStateLock")
+ private void updateSoundDoseRecords_l(SoundDoseRecord[] newRecords, float currentCsd) {
long totalDuration = 0;
for (SoundDoseRecord record : newRecords) {
Log.i(TAG, " new record: " + record);
@@ -722,9 +765,87 @@
}
}
+ mAudioHandler.sendMessageAtTime(mAudioHandler.obtainMessage(MSG_PERSIST_CSD_VALUES,
+ /* arg1= */0, /* arg2= */0, /* obj= */null), /* delay= */0);
+
mLogger.enqueue(SoundDoseEvent.getDoseUpdateEvent(currentCsd, totalDuration));
}
+ private void onPersistSoundDoseRecords() {
+ synchronized (mCsdStateLock) {
+ mSettings.putGlobalString(mAudioService.getContentResolver(),
+ Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE,
+ Float.toString(mCurrentCsd));
+ mSettings.putGlobalString(mAudioService.getContentResolver(),
+ Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING,
+ Float.toString(mNextCsdWarning));
+ mSettings.putGlobalString(mAudioService.getContentResolver(),
+ Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS,
+ mDoseRecords.stream().map(
+ SoundDoseHelper::recordToPersistedString).collect(
+ Collectors.joining(PERSIST_CSD_RECORD_SEPARATOR_CHAR)));
+ }
+ }
+
+ private static String recordToPersistedString(SoundDoseRecord record) {
+ return record.timestamp
+ + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.duration
+ + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.value
+ + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.averageMel;
+ }
+
+ private static List<SoundDoseRecord> persistedStringToRecordList(String records) {
+ if (records == null || records.isEmpty()) {
+ return null;
+ }
+ return Arrays.stream(TextUtils.split(records, PERSIST_CSD_RECORD_SEPARATOR)).map(
+ SoundDoseHelper::persistedStringToRecord).filter(Objects::nonNull).collect(
+ Collectors.toList());
+ }
+
+ private static SoundDoseRecord persistedStringToRecord(String record) {
+ if (record == null || record.isEmpty()) {
+ return null;
+ }
+ final String[] fields = TextUtils.split(record, PERSIST_CSD_RECORD_FIELD_SEPARATOR);
+ if (fields.length != 4) {
+ Log.w(TAG, "Expecting 4 fields for a SoundDoseRecord, parsed " + fields.length);
+ return null;
+ }
+
+ final SoundDoseRecord sdRecord = new SoundDoseRecord();
+ try {
+ sdRecord.timestamp = Long.parseLong(fields[0]);
+ sdRecord.duration = Integer.parseInt(fields[1]);
+ sdRecord.value = Float.parseFloat(fields[2]);
+ sdRecord.averageMel = Float.parseFloat(fields[3]);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Unable to parse persisted SoundDoseRecord: " + record, e);
+ return null;
+ }
+
+ return sdRecord;
+ }
+
+ private float parseGlobalSettingFloat(String audioSafeCsdCurrentValue, float defaultValue) {
+ String stringValue = mSettings.getGlobalString(mAudioService.getContentResolver(),
+ audioSafeCsdCurrentValue);
+ if (stringValue == null || stringValue.isEmpty()) {
+ Log.v(TAG, "No value stored in settings " + audioSafeCsdCurrentValue);
+ return defaultValue;
+ }
+
+ float value;
+ try {
+ value = Float.parseFloat(stringValue);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Error parsing float from settings " + audioSafeCsdCurrentValue, e);
+ value = defaultValue;
+ }
+
+ return value;
+ }
+
// StreamVolumeCommand contains the information needed to defer the process of
// setStreamVolume() in case the user has to acknowledge the safe volume warning message.
private static class StreamVolumeCommand {
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 064cd2d..ba96861 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -25,6 +25,7 @@
import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
+import static com.android.server.devicestate.OverrideRequestController.FLAG_THERMAL_CRITICAL;
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
import static com.android.server.devicestate.OverrideRequestController.STATUS_UNKNOWN;
@@ -183,6 +184,9 @@
ActivityTaskManagerInternal.ScreenObserver mOverrideRequestScreenObserver =
new OverrideRequestScreenObserver();
+ @NonNull
+ private final DeviceStateNotificationController mDeviceStateNotificationController;
+
public DeviceStateManagerService(@NonNull Context context) {
this(context, DeviceStatePolicy.Provider
.fromResources(context.getResources())
@@ -211,6 +215,13 @@
mDeviceStatePolicy.getDeviceStateProvider().setListener(mDeviceStateProviderListener);
mBinderService = new BinderService();
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ mDeviceStateNotificationController = new DeviceStateNotificationController(
+ context, mHandler,
+ () -> {
+ synchronized (mLock) {
+ mActiveOverride.ifPresent(mOverrideRequestController::cancelRequest);
+ }
+ });
}
@Override
@@ -346,7 +357,8 @@
return mBinderService;
}
- private void updateSupportedStates(DeviceState[] supportedDeviceStates) {
+ private void updateSupportedStates(DeviceState[] supportedDeviceStates,
+ @DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
synchronized (mLock) {
final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked();
@@ -370,7 +382,7 @@
return;
}
- mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers);
+ mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers, reason);
updatePendingStateLocked();
setRearDisplayStateLocked();
@@ -596,7 +608,7 @@
@GuardedBy("mLock")
private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request,
- @OverrideRequestController.RequestStatus int status) {
+ @OverrideRequestController.RequestStatus int status, int flags) {
if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_BASE_STATE) {
switch (status) {
case STATUS_ACTIVE:
@@ -616,10 +628,19 @@
switch (status) {
case STATUS_ACTIVE:
mActiveOverride = Optional.of(request);
+ mDeviceStateNotificationController.showStateActiveNotificationIfNeeded(
+ request.getRequestedState(), request.getUid());
break;
case STATUS_CANCELED:
if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
mActiveOverride = Optional.empty();
+ mDeviceStateNotificationController.cancelNotification(
+ request.getRequestedState());
+ if ((flags & FLAG_THERMAL_CRITICAL) == FLAG_THERMAL_CRITICAL) {
+ mDeviceStateNotificationController
+ .showThermalCriticalNotificationIfNeeded(
+ request.getRequestedState());
+ }
}
break;
case STATUS_UNKNOWN: // same as default
@@ -700,7 +721,7 @@
}
}
- private void requestStateInternal(int state, int flags, int callingPid,
+ private void requestStateInternal(int state, int flags, int callingPid, int callingUid,
@NonNull IBinder token, boolean hasControlDeviceStatePermission) {
synchronized (mLock) {
final ProcessRecord processRecord = mProcessRecords.get(callingPid);
@@ -721,8 +742,8 @@
+ " is not supported.");
}
- OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
- OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+ OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
+ state, flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
// If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
if (!hasControlDeviceStatePermission && mRearDisplayState != null
@@ -762,7 +783,7 @@
}
private void requestBaseStateOverrideInternal(int state, int flags, int callingPid,
- @NonNull IBinder token) {
+ int callingUid, @NonNull IBinder token) {
synchronized (mLock) {
final Optional<DeviceState> deviceState = getStateLocked(state);
if (!deviceState.isPresent()) {
@@ -782,8 +803,8 @@
+ " token: " + token);
}
- OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
- OVERRIDE_REQUEST_TYPE_BASE_STATE);
+ OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
+ state, flags, OVERRIDE_REQUEST_TYPE_BASE_STATE);
mOverrideRequestController.addBaseStateRequest(request);
}
}
@@ -953,11 +974,12 @@
@IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int mCurrentBaseState;
@Override
- public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
+ public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates,
+ @DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
if (newDeviceStates.length == 0) {
throw new IllegalArgumentException("Supported device states must not be empty");
}
- updateSupportedStates(newDeviceStates);
+ updateSupportedStates(newDeviceStates, reason);
}
@Override
@@ -1085,6 +1107,7 @@
@Override // Binder call
public void requestState(IBinder token, int state, int flags) {
final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
// Allow top processes to request a device state change
// If the calling process ID is not the top app, then we check if this process
// holds a permission to CONTROL_DEVICE_STATE
@@ -1099,7 +1122,8 @@
final long callingIdentity = Binder.clearCallingIdentity();
try {
- requestStateInternal(state, flags, callingPid, token, hasControlStatePermission);
+ requestStateInternal(state, flags, callingPid, callingUid, token,
+ hasControlStatePermission);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
@@ -1124,6 +1148,7 @@
@Override // Binder call
public void requestBaseStateOverride(IBinder token, int state, int flags) {
final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to control base state of device.");
@@ -1133,7 +1158,7 @@
final long callingIdentity = Binder.clearCallingIdentity();
try {
- requestBaseStateOverrideInternal(state, flags, callingPid, token);
+ requestBaseStateOverrideInternal(state, flags, callingPid, callingUid, token);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
new file mode 100644
index 0000000..2f14998
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicestate;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.Handler;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Manages the user-visible device state notifications.
+ */
+class DeviceStateNotificationController extends BroadcastReceiver {
+ private static final String TAG = "DeviceStateNotificationController";
+
+ @VisibleForTesting static final String INTENT_ACTION_CANCEL_STATE =
+ "com.android.server.devicestate.INTENT_ACTION_CANCEL_STATE";
+ @VisibleForTesting static final int NOTIFICATION_ID = 1;
+ @VisibleForTesting static final String CHANNEL_ID = "DeviceStateManager";
+ @VisibleForTesting static final String NOTIFICATION_TAG = "DeviceStateManager";
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final NotificationManager mNotificationManager;
+ private final PackageManager mPackageManager;
+
+ // Stores the notification title and content indexed with the device state identifier.
+ private final SparseArray<NotificationInfo> mNotificationInfos;
+
+ // The callback when a device state is requested to be canceled.
+ private final Runnable mCancelStateRunnable;
+
+ DeviceStateNotificationController(@NonNull Context context, @NonNull Handler handler,
+ @NonNull Runnable cancelStateRunnable) {
+ this(context, handler, cancelStateRunnable, getNotificationInfos(context),
+ context.getPackageManager(), context.getSystemService(NotificationManager.class));
+ }
+
+ @VisibleForTesting
+ DeviceStateNotificationController(
+ @NonNull Context context, @NonNull Handler handler,
+ @NonNull Runnable cancelStateRunnable,
+ @NonNull SparseArray<NotificationInfo> notificationInfos,
+ @NonNull PackageManager packageManager,
+ @NonNull NotificationManager notificationManager) {
+ mContext = context;
+ mHandler = handler;
+ mCancelStateRunnable = cancelStateRunnable;
+ mNotificationInfos = notificationInfos;
+ mPackageManager = packageManager;
+ mNotificationManager = notificationManager;
+ mContext.registerReceiver(
+ this,
+ new IntentFilter(INTENT_ACTION_CANCEL_STATE),
+ android.Manifest.permission.CONTROL_DEVICE_STATE,
+ mHandler,
+ Context.RECEIVER_NOT_EXPORTED);
+ }
+
+ /**
+ * Displays the ongoing notification indicating that the device state is active. Does nothing if
+ * the state does not have an active notification.
+ *
+ * @param state the active device state identifier.
+ * @param requestingAppUid the uid of the requesting app used to retrieve the app name.
+ */
+ void showStateActiveNotificationIfNeeded(int state, int requestingAppUid) {
+ NotificationInfo info = mNotificationInfos.get(state);
+ if (info == null || !info.hasActiveNotification()) {
+ return;
+ }
+ String requesterApplicationLabel = getApplicationLabel(requestingAppUid);
+ if (requesterApplicationLabel != null) {
+ showNotification(
+ info.name, info.activeNotificationTitle,
+ String.format(info.activeNotificationContent, requesterApplicationLabel),
+ true /* ongoing */
+ );
+ } else {
+ Slog.e(TAG, "Cannot determine the requesting app name when showing state active "
+ + "notification. uid=" + requestingAppUid + ", state=" + state);
+ }
+ }
+
+ /**
+ * Displays the notification indicating that the device state is canceled due to thermal
+ * critical condition. Does nothing if the state does not have a thermal critical notification.
+ *
+ * @param state the identifier of the device state being canceled.
+ */
+ void showThermalCriticalNotificationIfNeeded(int state) {
+ NotificationInfo info = mNotificationInfos.get(state);
+ if (info == null || !info.hasThermalCriticalNotification()) {
+ return;
+ }
+ showNotification(
+ info.name, info.thermalCriticalNotificationTitle,
+ info.thermalCriticalNotificationContent, false /* ongoing */
+ );
+ }
+
+ /**
+ * Cancels the notification of the corresponding device state.
+ *
+ * @param state the device state identifier.
+ */
+ void cancelNotification(int state) {
+ if (!mNotificationInfos.contains(state)) {
+ return;
+ }
+ mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
+ }
+
+ @Override
+ public void onReceive(@NonNull Context context, @Nullable Intent intent) {
+ if (intent != null) {
+ if (INTENT_ACTION_CANCEL_STATE.equals(intent.getAction())) {
+ mCancelStateRunnable.run();
+ }
+ }
+ }
+
+ /**
+ * Displays a notification with the specified name, title, and content.
+ *
+ * @param name the name of the notification.
+ * @param title the title of the notification.
+ * @param content the content of the notification.
+ * @param ongoing if true, display an ongoing (sticky) notification with a turn off button.
+ */
+ private void showNotification(
+ @NonNull String name, @NonNull String title, @NonNull String content, boolean ongoing) {
+ final NotificationChannel channel = new NotificationChannel(
+ CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH);
+ final Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_lock) // TODO(b/266833171) update icons when available.
+ .setContentTitle(title)
+ .setContentText(content)
+ .setSubText(name)
+ .setLocalOnly(true)
+ .setOngoing(ongoing)
+ .setCategory(Notification.CATEGORY_SYSTEM);
+
+ if (ongoing) {
+ final Intent intent = new Intent(INTENT_ACTION_CANCEL_STATE)
+ .setPackage(mContext.getPackageName());
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE);
+ final Notification.Action action = new Notification.Action.Builder(
+ null /* icon */,
+ mContext.getString(R.string.device_state_notification_turn_off_button),
+ pendingIntent)
+ .build();
+ builder.addAction(action);
+ }
+
+ mNotificationManager.createNotificationChannel(channel);
+ mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
+ }
+
+ /**
+ * Loads the resources for the notifications. The device state identifiers and strings are
+ * stored in arrays. All the string arrays must have the same length and same order as the
+ * identifier array.
+ */
+ private static SparseArray<NotificationInfo> getNotificationInfos(Context context) {
+ final SparseArray<NotificationInfo> notificationInfos = new SparseArray<>();
+
+ final int[] stateIdentifiers =
+ context.getResources().getIntArray(
+ R.array.device_state_notification_state_identifiers);
+ final String[] names =
+ context.getResources().getStringArray(R.array.device_state_notification_names);
+ final String[] activeNotificationTitles =
+ context.getResources().getStringArray(
+ R.array.device_state_notification_active_titles);
+ final String[] activeNotificationContents =
+ context.getResources().getStringArray(
+ R.array.device_state_notification_active_contents);
+ final String[] thermalCriticalNotificationTitles =
+ context.getResources().getStringArray(
+ R.array.device_state_notification_thermal_titles);
+ final String[] thermalCriticalNotificationContents =
+ context.getResources().getStringArray(
+ R.array.device_state_notification_thermal_contents);
+
+ if (stateIdentifiers.length != names.length
+ || stateIdentifiers.length != activeNotificationTitles.length
+ || stateIdentifiers.length != activeNotificationContents.length
+ || stateIdentifiers.length != thermalCriticalNotificationTitles.length
+ || stateIdentifiers.length != thermalCriticalNotificationContents.length
+ ) {
+ throw new IllegalStateException(
+ "The length of state identifiers and notification texts must match!");
+ }
+
+ for (int i = 0; i < stateIdentifiers.length; i++) {
+ int identifier = stateIdentifiers[i];
+ if (identifier == DeviceStateManager.INVALID_DEVICE_STATE) {
+ continue;
+ }
+
+ notificationInfos.put(
+ identifier,
+ new NotificationInfo(
+ names[i], activeNotificationTitles[i], activeNotificationContents[i],
+ thermalCriticalNotificationTitles[i],
+ thermalCriticalNotificationContents[i])
+ );
+ }
+
+ return notificationInfos;
+ }
+
+ /**
+ * A helper function to get app name (label) using the app uid.
+ *
+ * @param uid the uid of the app.
+ * @return app name (label) if found, or null otherwise.
+ */
+ @Nullable
+ private String getApplicationLabel(int uid) {
+ String packageName = mPackageManager.getNameForUid(uid);
+ try {
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
+ packageName, PackageManager.ApplicationInfoFlags.of(0));
+ return appInfo.loadLabel(mPackageManager).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * A data class storing string resources of the notification of a device state.
+ */
+ @VisibleForTesting
+ static class NotificationInfo {
+ public final String name;
+ public final String activeNotificationTitle;
+ public final String activeNotificationContent;
+ public final String thermalCriticalNotificationTitle;
+ public final String thermalCriticalNotificationContent;
+
+ NotificationInfo(String name, String activeNotificationTitle,
+ String activeNotificationContent, String thermalCriticalNotificationTitle,
+ String thermalCriticalNotificationContent) {
+
+ this.name = name;
+ this.activeNotificationTitle = activeNotificationTitle;
+ this.activeNotificationContent = activeNotificationContent;
+ this.thermalCriticalNotificationTitle = thermalCriticalNotificationTitle;
+ this.thermalCriticalNotificationContent = thermalCriticalNotificationContent;
+ }
+
+ boolean hasActiveNotification() {
+ return activeNotificationTitle != null && activeNotificationTitle.length() > 0;
+ }
+
+ boolean hasThermalCriticalNotification() {
+ return thermalCriticalNotificationTitle != null
+ && thermalCriticalNotificationTitle.length() > 0;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index 109bf63..fecc13f 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -19,8 +19,12 @@
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+import android.annotation.IntDef;
import android.annotation.IntRange;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Responsible for providing the set of supported {@link DeviceState device states} as well as the
* current device state.
@@ -28,12 +32,42 @@
* @see DeviceStatePolicy
*/
public interface DeviceStateProvider {
+ int SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT = 0;
+
+ /**
+ * Indicating that the supported device states changed callback is trigger for initial listener
+ * registration.
+ */
+ int SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED = 1;
+
+ /**
+ * Indicating that the supported device states have changed because the thermal condition
+ * returned to normal status from critical status.
+ */
+ int SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL = 2;
+
+ /**
+ * Indicating that the supported device states have changed because of thermal critical
+ * condition.
+ */
+ int SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL = 3;
+
+ @IntDef(prefix = { "SUPPORTED_DEVICE_STATES_CHANGED_" }, value = {
+ SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT,
+ SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED,
+ SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL,
+ SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface SupportedStatesUpdatedReason {}
+
/**
* Registers a listener for changes in provider state.
* <p>
- * It is <b>required</b> that {@link Listener#onSupportedDeviceStatesChanged(DeviceState[])} be
- * called followed by {@link Listener#onStateChanged(int)} with the initial values on successful
- * registration of the listener.
+ * It is <b>required</b> that
+ * {@link Listener#onSupportedDeviceStatesChanged(DeviceState[], int)} be called followed by
+ * {@link Listener#onStateChanged(int)} with the initial values on successful registration of
+ * the listener.
*/
void setListener(Listener listener);
@@ -53,18 +87,20 @@
* to zero and there must always be at least one supported device state.
*
* @param newDeviceStates array of supported device states.
+ * @param reason the reason for the supported device states change.
*
* @throws IllegalArgumentException if the list of device states is empty or if one of the
* provided states contains an invalid identifier.
*/
- void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates);
+ void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates,
+ @SupportedStatesUpdatedReason int reason);
/**
* Called to notify the listener of a change in current device state. Required to be called
* once on successful registration of the listener and then once on every subsequent change
* in device state. Value must have been included in the set of supported device states
* provided in the most recent call to
- * {@link #onSupportedDeviceStatesChanged(DeviceState[])}.
+ * {@link #onSupportedDeviceStatesChanged(DeviceState[], int)}.
*
* @param identifier the identifier of the new device state.
*
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java
index 325e384..74cf184 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequest.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java
@@ -31,6 +31,7 @@
final class OverrideRequest {
private final IBinder mToken;
private final int mPid;
+ private final int mUid;
private final int mRequestedState;
@DeviceStateRequest.RequestFlags
private final int mFlags;
@@ -68,10 +69,11 @@
@Retention(RetentionPolicy.SOURCE)
public @interface OverrideRequestType {}
- OverrideRequest(IBinder token, int pid, int requestedState,
+ OverrideRequest(IBinder token, int pid, int uid, int requestedState,
@DeviceStateRequest.RequestFlags int flags, @OverrideRequestType int requestType) {
mToken = token;
mPid = pid;
+ mUid = uid;
mRequestedState = requestedState;
mFlags = flags;
mRequestType = requestType;
@@ -85,6 +87,10 @@
return mPid;
}
+ int getUid() {
+ return mUid;
+ }
+
int getRequestedState() {
return mRequestedState;
}
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index e39c732..2ed4765 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -59,6 +59,11 @@
@Retention(RetentionPolicy.SOURCE)
@interface RequestStatus {}
+ /**
+ * A flag indicating that the status change was triggered by thermal critical status.
+ */
+ static final int FLAG_THERMAL_CRITICAL = 1 << 0;
+
static String statusToString(@RequestStatus int status) {
switch (status) {
case STATUS_ACTIVE:
@@ -106,7 +111,7 @@
void addRequest(@NonNull OverrideRequest request) {
OverrideRequest previousRequest = mRequest;
mRequest = request;
- mListener.onStatusChanged(request, STATUS_ACTIVE);
+ mListener.onStatusChanged(request, STATUS_ACTIVE, 0 /* flags */);
if (previousRequest != null) {
cancelRequestLocked(previousRequest);
@@ -116,7 +121,7 @@
void addBaseStateRequest(@NonNull OverrideRequest request) {
OverrideRequest previousRequest = mBaseStateRequest;
mBaseStateRequest = request;
- mListener.onStatusChanged(request, STATUS_ACTIVE);
+ mListener.onStatusChanged(request, STATUS_ACTIVE, 0 /* flags */);
if (previousRequest != null) {
cancelRequestLocked(previousRequest);
@@ -219,14 +224,17 @@
* Notifies the controller that the set of supported states has changed. The controller will
* notify the listener of all changes to request status as a result of this change.
*/
- void handleNewSupportedStates(int[] newSupportedStates) {
+ void handleNewSupportedStates(int[] newSupportedStates,
+ @DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
+ boolean isThermalCritical =
+ reason == DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
if (mBaseStateRequest != null && !contains(newSupportedStates,
mBaseStateRequest.getRequestedState())) {
- cancelCurrentBaseStateRequestLocked();
+ cancelCurrentBaseStateRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0);
}
if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) {
- cancelCurrentRequestLocked();
+ cancelCurrentRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0);
}
}
@@ -244,7 +252,11 @@
}
private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel) {
- mListener.onStatusChanged(requestToCancel, STATUS_CANCELED);
+ cancelRequestLocked(requestToCancel, 0 /* flags */);
+ }
+
+ private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel, int flags) {
+ mListener.onStatusChanged(requestToCancel, STATUS_CANCELED, flags);
}
/**
@@ -252,12 +264,16 @@
* Notifies the listener of the canceled status as well.
*/
private void cancelCurrentRequestLocked() {
+ cancelCurrentRequestLocked(0 /* flags */);
+ }
+
+ private void cancelCurrentRequestLocked(int flags) {
if (mRequest == null) {
Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
return;
}
mStickyRequest = false;
- cancelRequestLocked(mRequest);
+ cancelRequestLocked(mRequest, flags);
mRequest = null;
}
@@ -266,11 +282,15 @@
* Notifies the listener of the canceled status as well.
*/
private void cancelCurrentBaseStateRequestLocked() {
+ cancelCurrentBaseStateRequestLocked(0 /* flags */);
+ }
+
+ private void cancelCurrentBaseStateRequestLocked(int flags) {
if (mBaseStateRequest == null) {
Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
return;
}
- cancelRequestLocked(mBaseStateRequest);
+ cancelRequestLocked(mBaseStateRequest, flags);
mBaseStateRequest = null;
}
@@ -284,12 +304,14 @@
}
public interface StatusChangeListener {
+
/**
* Notifies the listener of a change in request status. If a change within the controller
* causes one request to become active and one to become either suspended or cancelled, this
* method is guaranteed to be called with the active request first before the suspended or
* cancelled request.
*/
- void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus);
+ void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus,
+ int flags);
}
}
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index fa9a100..f229d0f 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -28,7 +28,8 @@
* Calls into SurfaceFlinger for Display creation and deletion.
*/
public class DisplayControl {
- private static native IBinder nativeCreateDisplay(String name, boolean secure);
+ private static native IBinder nativeCreateDisplay(String name, boolean secure,
+ float requestedRefreshRate);
private static native void nativeDestroyDisplay(IBinder displayToken);
private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
private static native long[] nativeGetPhysicalDisplayIds();
@@ -46,7 +47,25 @@
*/
public static IBinder createDisplay(String name, boolean secure) {
Objects.requireNonNull(name, "name must not be null");
- return nativeCreateDisplay(name, secure);
+ return nativeCreateDisplay(name, secure, 0.0f);
+ }
+
+ /**
+ * Create a display in SurfaceFlinger.
+ *
+ * @param name The name of the display
+ * @param secure Whether this display is secure.
+ * @param requestedRefreshRate The requested refresh rate in frames per second.
+ * For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
+ * 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded
+ * up or down to a divisor of the physical display. If 0 is specified, the virtual
+ * display is refreshed at the physical display refresh rate.
+ * @return The token reference for the display in SurfaceFlinger.
+ */
+ public static IBinder createDisplay(String name, boolean secure,
+ float requestedRefreshRate) {
+ Objects.requireNonNull(name, "name must not be null");
+ return nativeCreateDisplay(name, secure, requestedRefreshRate);
}
/**
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index ddeaa1b..364d53b 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -106,7 +106,8 @@
String name = virtualDisplayConfig.getName();
boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
IBinder appToken = callback.asBinder();
- IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure);
+ IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure,
+ virtualDisplayConfig.getRequestedRefreshRate());
final String baseUniqueId =
UNIQUE_ID_PREFIX + ownerPackageName + "," + ownerUid + "," + name + ",";
final int uniqueIndex = getNextUniqueIndex(baseUniqueId);
@@ -247,6 +248,7 @@
private int mWidth;
private int mHeight;
private int mDensityDpi;
+ private float mRequestedRefreshRate;
private Surface mSurface;
private DisplayDeviceInfo mInfo;
private int mDisplayState;
@@ -270,8 +272,9 @@
mName = virtualDisplayConfig.getName();
mWidth = virtualDisplayConfig.getWidth();
mHeight = virtualDisplayConfig.getHeight();
- mMode = createMode(mWidth, mHeight, REFRESH_RATE);
mDensityDpi = virtualDisplayConfig.getDensityDpi();
+ mRequestedRefreshRate = virtualDisplayConfig.getRequestedRefreshRate();
+ mMode = createMode(mWidth, mHeight, getRefreshRate());
mSurface = surface;
mFlags = flags;
mCallback = callback;
@@ -410,7 +413,7 @@
sendTraversalRequestLocked();
mWidth = width;
mHeight = height;
- mMode = createMode(width, height, REFRESH_RATE);
+ mMode = createMode(width, height, getRefreshRate());
mDensityDpi = densityDpi;
mInfo = null;
mPendingChanges |= PENDING_RESIZE;
@@ -438,9 +441,9 @@
pw.println("mStopped=" + mStopped);
pw.println("mDisplayIdToMirror=" + mDisplayIdToMirror);
pw.println("mWindowManagerMirroring=" + mIsWindowManagerMirroring);
+ pw.println("mRequestedRefreshRate=" + mRequestedRefreshRate);
}
-
@Override
public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
if (mInfo == null) {
@@ -456,7 +459,7 @@
mInfo.densityDpi = mDensityDpi;
mInfo.xDpi = mDensityDpi;
mInfo.yDpi = mDensityDpi;
- mInfo.presentationDeadlineNanos = 1000000000L / (int) REFRESH_RATE; // 1 frame
+ mInfo.presentationDeadlineNanos = 1000000000L / (int) getRefreshRate(); // 1 frame
mInfo.flags = 0;
if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE
@@ -554,6 +557,10 @@
}
return mInfo;
}
+
+ private float getRefreshRate() {
+ return (mRequestedRefreshRate != 0.0f) ? mRequestedRefreshRate : REFRESH_RATE;
+ }
}
private static class Callback extends Handler {
@@ -632,6 +639,19 @@
@VisibleForTesting
public interface SurfaceControlDisplayFactory {
- public IBinder createDisplay(String name, boolean secure);
+ /**
+ * Create a virtual display in SurfaceFlinger.
+ *
+ * @param name The name of the display
+ * @param secure Whether this display is secure.
+ * @param requestedRefreshRate
+ * The refresh rate, frames per second, to request on the virtual display.
+ * It should be a divisor of refresh rate of the leader physical display
+ * that drives VSYNC, e.g. 30/60fps on 120fps display. If an arbitrary refresh
+ * rate is specified, SurfaceFlinger rounds up or down to match a divisor of
+ * the refresh rate of the leader physical display.
+ * @return The token reference for the display in SurfaceFlinger.
+ */
+ IBinder createDisplay(String name, boolean secure, float requestedRefreshRate);
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index ffc309e..3c97aaf8 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -280,13 +280,13 @@
public void setRouteListingPreference(
@NonNull IMediaRouter2 router,
@Nullable RouteListingPreference routeListingPreference) {
- ComponentName inAppOnlyItemRoutingReceiver =
+ ComponentName linkedItemLandingComponent =
routeListingPreference != null
- ? routeListingPreference.getInAppOnlyItemRoutingReceiver()
+ ? routeListingPreference.getLinkedItemComponentName()
: null;
- if (inAppOnlyItemRoutingReceiver != null) {
+ if (linkedItemLandingComponent != null) {
MediaServerUtils.enforcePackageName(
- inAppOnlyItemRoutingReceiver.getPackageName(), Binder.getCallingUid());
+ linkedItemLandingComponent.getPackageName(), Binder.getCallingUid());
}
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 69436da..8d40adf 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -27,12 +27,15 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
+import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
@@ -51,6 +54,7 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
@@ -122,7 +126,18 @@
@Override
public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
@PackageManager.PackageInfoFlagsBits long flags, int userId) {
- return mService.getBackgroundInstalledPackages(flags, userId);
+ if (!Build.IS_DEBUGGABLE) {
+ return mService.getBackgroundInstalledPackages(flags, userId);
+ }
+ // The debug.transparency.bg-install-apps (only works for debuggable builds)
+ // is used to set mock list of background installed apps for testing.
+ // The list of apps' names is delimited by ",".
+ String propertyString = SystemProperties.get("debug.transparency.bg-install-apps");
+ if (TextUtils.isEmpty(propertyString)) {
+ return mService.getBackgroundInstalledPackages(flags, userId);
+ } else {
+ return mService.getMockBackgroundInstalledPackages(propertyString);
+ }
}
}
@@ -145,6 +160,27 @@
return new ParceledListSlice<>(packages);
}
+ /**
+ * Mock a list of background installed packages based on the property string.
+ */
+ @NonNull
+ ParceledListSlice<PackageInfo> getMockBackgroundInstalledPackages(
+ @NonNull String propertyString) {
+ String[] mockPackageNames = propertyString.split(",");
+ List<PackageInfo> mockPackages = new ArrayList<>();
+ for (String name : mockPackageNames) {
+ try {
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(name,
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL));
+ mockPackages.add(packageInfo);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Package's PackageInfo not found " + name);
+ continue;
+ }
+ }
+ return new ParceledListSlice<>(mockPackages);
+ }
+
private static class EventHandler extends Handler {
private final BackgroundInstallControlService mService;
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index bd6bd75..0d5392b 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -70,6 +70,7 @@
import static com.android.server.pm.PackageManagerService.SCAN_AS_OEM;
import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED;
import static com.android.server.pm.PackageManagerService.SCAN_AS_PRODUCT;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_STOPPED_SYSTEM_APP;
import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM;
import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM_EXT;
import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR;
@@ -4237,6 +4238,18 @@
}
}
+ // A new application appeared on /system, and we are seeing it for the first time.
+ // Its also not updated as we don't have a copy of it on /data. So, scan it in a
+ // STOPPED state. Ignore if it's an APEX package since stopped state does not affect them.
+ final boolean isApexPkg = (scanFlags & SCAN_AS_APEX) != 0;
+ if (mPm.mShouldStopSystemPackagesByDefault && scanSystemPartition
+ && !pkgAlreadyExists && !isApexPkg) {
+ String packageName = parsedPackage.getPackageName();
+ if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName)) {
+ scanFlags |= SCAN_AS_STOPPED_SYSTEM_APP;
+ }
+ }
+
final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
scanFlags | SCAN_UPDATE_SIGNATURE, 0 /* currentTime */, user, null);
return new Pair<>(scanResult, shouldHideSystemApp);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 617561a..d23ea16 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -706,7 +706,7 @@
}
}
- if (Build.IS_DEBUGGABLE || isCalledBySystemOrShell(callingUid)) {
+ if (Build.IS_DEBUGGABLE || isCalledBySystem(callingUid)) {
params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
} else {
params.installFlags &= ~PackageManager.INSTALL_ALLOW_DOWNGRADE;
@@ -916,6 +916,10 @@
return sessionId;
}
+ private static boolean isCalledBySystem(int callingUid) {
+ return callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID;
+ }
+
private boolean isCalledBySystemOrShell(int callingUid) {
return callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID
|| callingUid == Process.SHELL_UID;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a02a419..c6a1579 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -387,6 +387,7 @@
static final int SCAN_DROP_CACHE = 1 << 24;
static final int SCAN_AS_FACTORY = 1 << 25;
static final int SCAN_AS_APEX = 1 << 26;
+ static final int SCAN_AS_STOPPED_SYSTEM_APP = 1 << 27;
@IntDef(flag = true, prefix = { "SCAN_" }, value = {
SCAN_NO_DEX,
@@ -403,6 +404,7 @@
SCAN_AS_INSTANT_APP,
SCAN_AS_FULL_APP,
SCAN_AS_VIRTUAL_PRELOAD,
+ SCAN_AS_STOPPED_SYSTEM_APP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ScanFlags {}
@@ -964,6 +966,8 @@
final @Nullable String mRecentsPackage;
final @Nullable String mAmbientContextDetectionPackage;
final @Nullable String mWearableSensingPackage;
+ final @NonNull Set<String> mInitialNonStoppedSystemPackages;
+ final boolean mShouldStopSystemPackagesByDefault;
private final @NonNull String mRequiredSdkSandboxPackage;
@GuardedBy("mLock")
@@ -1771,6 +1775,8 @@
mOverlayConfigSignaturePackage = testParams.overlayConfigSignaturePackage;
mResolveComponentName = testParams.resolveComponentName;
mRequiredSdkSandboxPackage = testParams.requiredSdkSandboxPackage;
+ mInitialNonStoppedSystemPackages = testParams.initialNonStoppedSystemPackages;
+ mShouldStopSystemPackagesByDefault = testParams.shouldStopSystemPackagesByDefault;
mLiveComputer = createLiveComputer();
mSnapshotStatistics = null;
@@ -2097,6 +2103,11 @@
mCacheDir = PackageManagerServiceUtils.preparePackageParserCache(
mIsEngBuild, mIsUserDebugBuild, mIncrementalVersion);
+ mInitialNonStoppedSystemPackages = mInjector.getSystemConfig()
+ .getInitialNonStoppedSystemPackages();
+ mShouldStopSystemPackagesByDefault = mContext.getResources()
+ .getBoolean(R.bool.config_stopSystemPackagesByDefault);
+
final int[] userIds = mUserManager.getUserIds();
PackageParser2 packageParser = mInjector.getScanningCachingPackageParser();
mOverlayConfig = mInitAppsHelper.initSystemApps(packageParser, packageSettings, userIds,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index e5cfa67..0ed90e4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -26,6 +26,7 @@
import android.os.Handler;
import android.os.incremental.IncrementalManager;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.DisplayMetrics;
import com.android.internal.annotations.VisibleForTesting;
@@ -40,6 +41,7 @@
import java.io.File;
import java.util.List;
+import java.util.Set;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
public final class PackageManagerServiceTestParams {
@@ -119,4 +121,6 @@
public SuspendPackageHelper suspendPackageHelper;
public DistractingPackageHelper distractingPackageHelper;
public StorageEventHelper storageEventHelper;
+ public Set<String> initialNonStoppedSystemPackages = new ArraySet<>();
+ public boolean shouldStopSystemPackagesByDefault;
}
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 830b096c..2538871 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -31,6 +31,7 @@
import static com.android.server.pm.PackageManagerService.SCAN_AS_OEM;
import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED;
import static com.android.server.pm.PackageManagerService.SCAN_AS_PRODUCT;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_STOPPED_SYSTEM_APP;
import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM;
import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM_EXT;
import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR;
@@ -198,6 +199,7 @@
if (createNewPackage) {
final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
final boolean virtualPreload = (scanFlags & SCAN_AS_VIRTUAL_PRELOAD) != 0;
+ final boolean isStoppedSystemApp = (scanFlags & SCAN_AS_STOPPED_SYSTEM_APP) != 0;
// Flags contain system values stored in the server variant of AndroidPackage,
// and so the server-side PackageInfoUtils is still called, even without a
@@ -212,7 +214,7 @@
AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage),
AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage),
parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user,
- true /*allowInstall*/, instantApp, virtualPreload,
+ true /*allowInstall*/, instantApp, virtualPreload, isStoppedSystemApp,
UserManagerService.getInstance(), usesSdkLibraries,
parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 165e476..4f0a115 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1041,7 +1041,7 @@
File codePath, String legacyNativeLibraryPath, String primaryCpuAbi,
String secondaryCpuAbi, long versionCode, int pkgFlags, int pkgPrivateFlags,
UserHandle installUser, boolean allowInstall, boolean instantApp,
- boolean virtualPreload, UserManagerService userManager,
+ boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager,
String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,
String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
Set<String> mimeGroupNames, @NonNull UUID domainSetId) {
@@ -1068,6 +1068,9 @@
pkgSetting.setFlags(pkgFlags)
.setPrivateFlags(pkgPrivateFlags);
} else {
+ int installUserId = installUser != null ? installUser.getIdentifier()
+ : UserHandle.USER_SYSTEM;
+
pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
@@ -1086,8 +1089,6 @@
Slog.i(PackageManagerService.TAG, "Stopping package " + pkgName, e);
}
List<UserInfo> users = getAllUsers(userManager);
- int installUserId = installUser != null ? installUser.getIdentifier()
- : UserHandle.USER_SYSTEM;
if (users != null && allowInstall) {
for (UserInfo user : users) {
// By default we consider this app to be installed
@@ -1126,6 +1127,13 @@
);
}
}
+ } else if (isStoppedSystemApp) {
+ if (DEBUG_STOPPED) {
+ RuntimeException e = new RuntimeException("here");
+ e.fillInStackTrace();
+ Slog.i(PackageManagerService.TAG, "Stopping system package " + pkgName, e);
+ }
+ pkgSetting.setStopped(true, installUserId);
}
if (sharedUser != null) {
pkgSetting.setAppId(sharedUser.mAppId);
@@ -4422,6 +4430,15 @@
ps.getPackageName()));
// Only system apps are initially installed.
ps.setInstalled(shouldReallyInstall, userHandle);
+
+ // Non-Apex system apps, that are not included in the allowlist in
+ // initialNonStoppedSystemPackages, should be marked as stopped by default.
+ final boolean shouldBeStopped = service.mShouldStopSystemPackagesByDefault
+ && ps.isSystem()
+ && !ps.isApex()
+ && !service.mInitialNonStoppedSystemPackages.contains(ps.getPackageName());
+ ps.setStopped(shouldBeStopped, userHandle);
+
// If userTypeInstallablePackages is the *only* reason why we're not installing,
// then uninstallReason is USER_TYPE. If there's a different reason, or if we
// actually are installing, put UNKNOWN.
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index eb3be54..26a990c 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -52,12 +52,14 @@
// TODO(b/248408342): Move keep annotation to the method referencing these fields reflectively.
@Keep public static final int USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE = 1;
@Keep public static final int USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE = 2;
+ @Keep public static final int USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE = 3;
@Keep public static final int USER_ASSIGNMENT_RESULT_FAILURE = -1;
private static final String PREFIX_USER_ASSIGNMENT_RESULT = "USER_ASSIGNMENT_RESULT_";
@IntDef(flag = false, prefix = {PREFIX_USER_ASSIGNMENT_RESULT}, value = {
USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE,
USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE,
+ USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE,
USER_ASSIGNMENT_RESULT_FAILURE
})
public @interface UserAssignmentResult {}
diff --git a/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java b/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
index 5bdcbb9..50a1d90 100644
--- a/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
+++ b/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.os.Binder;
import android.os.Build;
import android.os.Process;
@@ -36,6 +37,7 @@
import android.util.IndentingPrintWriter;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
@@ -103,6 +105,9 @@
pw.println();
pw.println(" is-headless-system-user-mode [-v | --verbose]");
pw.println(" Checks whether the device uses headless system user mode.");
+ pw.println(" is-visible-background-users-on-default-display-supported [-v | --verbose]");
+ pw.println(" Checks whether the device allows users to be start visible on background "
+ + "in the default display.");
pw.println(" It returns the effective mode, even when using emulation");
pw.println(" (to get the real mode as well, use -v or --verbose)");
pw.println();
@@ -129,6 +134,8 @@
return runSetSystemUserModeEmulation();
case "is-headless-system-user-mode":
return runIsHeadlessSystemUserMode();
+ case "is-visible-background-users-on-default-display-supported":
+ return runIsVisibleBackgroundUserOnDefaultDisplaySupported();
case "is-user-visible":
return runIsUserVisible();
default:
@@ -431,19 +438,47 @@
return -1;
}
}
-
- boolean isHsum = mService.isHeadlessSystemUserMode();
+ boolean effective = mService.isHeadlessSystemUserMode();
if (!verbose) {
// NOTE: do not change output below, as it's used by ITestDevice
// (it's ok to change the verbose option though)
- pw.println(isHsum);
+ pw.println(effective);
} else {
- pw.printf("effective=%b real=%b\n", isHsum,
+ pw.printf("effective=%b real=%b\n", effective,
RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER);
}
return 0;
}
+ private int runIsVisibleBackgroundUserOnDefaultDisplaySupported() {
+ PrintWriter pw = getOutPrintWriter();
+
+ boolean verbose = false;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-v":
+ case "--verbose":
+ verbose = true;
+ break;
+ default:
+ pw.println("Invalid option: " + opt);
+ return -1;
+ }
+ }
+
+ boolean effective = UserManager.isVisibleBackgroundUsersOnDefaultDisplayEnabled();
+ if (!verbose) {
+ // NOTE: do not change output below, as it's used by ITestDevice
+ // (it's ok to change the verbose option though)
+ pw.println(effective);
+ } else {
+ pw.printf("effective=%b real=%b\n", effective, Resources.getSystem()
+ .getBoolean(R.bool.config_multiuserVisibleBackgroundUsersOnDefaultDisplay));
+ }
+ return 0;
+ }
+
/**
* Gets the {@link UserManager} associated with the context of the given user.
*/
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 77c32ae..4cf8c09 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -121,6 +121,16 @@
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(1)
.setLabel(0)
+ .setIconBadge(com.android.internal.R.drawable.ic_clone_icon_badge)
+ .setBadgePlain(com.android.internal.R.drawable.ic_clone_badge)
+ // Clone doesn't use BadgeNoBackground, so just set to BadgePlain as a placeholder.
+ .setBadgeNoBackground(com.android.internal.R.drawable.ic_clone_badge)
+ .setBadgeLabels(
+ com.android.internal.R.string.clone_profile_label_badge)
+ .setBadgeColors(
+ com.android.internal.R.color.system_neutral2_800)
+ .setDarkThemeBadgeColors(
+ com.android.internal.R.color.system_neutral2_900)
.setDefaultRestrictions(null)
.setDefaultCrossProfileIntentFilters(getDefaultCloneCrossProfileIntentFilter())
.setDefaultUserProperties(new UserProperties.Builder()
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 0a181a4..fe8a500 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -22,6 +22,7 @@
import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
@@ -163,8 +164,7 @@
UserVisibilityMediator(Handler handler) {
this(UserManager.isVisibleBackgroundUsersEnabled(),
- // TODO(b/261538232): get visibleBackgroundUserOnDefaultDisplayAllowed from UM
- /* visibleBackgroundUserOnDefaultDisplayAllowed= */ false, handler);
+ UserManager.isVisibleBackgroundUsersOnDefaultDisplayEnabled(), handler);
}
@VisibleForTesting
@@ -230,7 +230,8 @@
Slogf.d(TAG, "result of getUserVisibilityOnStartLocked(%s)",
userAssignmentResultToString(result));
}
- if (result == USER_ASSIGNMENT_RESULT_FAILURE) {
+ if (result == USER_ASSIGNMENT_RESULT_FAILURE
+ || result == USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE) {
return result;
}
@@ -316,11 +317,19 @@
}
boolean visibleBackground = userStartMode == USER_START_MODE_BACKGROUND_VISIBLE;
- if (displayId == DEFAULT_DISPLAY && visibleBackground
- && !mVisibleBackgroundUserOnDefaultDisplayAllowed
- && !isProfile(userId, profileGroupId)) {
- Slogf.wtf(TAG, "cannot start full user (%d) visible on default display", userId);
- return USER_ASSIGNMENT_RESULT_FAILURE;
+ if (displayId == DEFAULT_DISPLAY && visibleBackground) {
+ if (mVisibleBackgroundUserOnDefaultDisplayAllowed && isCurrentUserLocked(userId)) {
+ // Shouldn't happen - UserController returns before calling this method
+ Slogf.wtf(TAG, "trying to start current user (%d) visible in background on default"
+ + " display", userId);
+ return USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE;
+
+ }
+ if (!mVisibleBackgroundUserOnDefaultDisplayAllowed
+ && !isProfile(userId, profileGroupId)) {
+ Slogf.wtf(TAG, "cannot start full user (%d) visible on default display", userId);
+ return USER_ASSIGNMENT_RESULT_FAILURE;
+ }
}
boolean foreground = userStartMode == USER_START_MODE_FOREGROUND;
@@ -406,7 +415,7 @@
// parent is the current user, as the current user is always assigned to the
// DEFAULT_DISPLAY).
if (DBG) {
- Slogf.d(TAG, "ignoring mapping for default display for user %d starting as %s",
+ Slogf.d(TAG, "Ignoring mapping for default display for user %d starting as %s",
userId, userStartModeToString(userStartMode));
}
return SECONDARY_DISPLAY_MAPPING_NOT_NEEDED;
@@ -1007,6 +1016,15 @@
}
}
+ @GuardedBy("mLock")
+ private boolean isCurrentUserLocked(@UserIdInt int userId) {
+ // Special case as NO_PROFILE_GROUP_ID == USER_NULL
+ if (userId == USER_NULL || mCurrentUserId == USER_NULL) {
+ return false;
+ }
+ return mCurrentUserId == userId;
+ }
+
private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
synchronized (mLock) {
// Special case as NO_PROFILE_GROUP_ID == USER_NULL
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 287cc29..42a8cb1 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -51,7 +51,7 @@
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.permission.SplitPermissionInfoParcelable;
-import android.healthconnect.HealthConnectManager;
+import android.health.connect.HealthConnectManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 7f733ef..8d7f782 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -366,12 +366,12 @@
}
mListener = listener;
}
- notifySupportedStatesChanged();
+ notifySupportedStatesChanged(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
notifyDeviceStateChangedIfNeeded();
}
/** Notifies the listener that the set of supported device states has changed. */
- private void notifySupportedStatesChanged() {
+ private void notifySupportedStatesChanged(@SupportedStatesUpdatedReason int reason) {
List<DeviceState> supportedStates = new ArrayList<>();
Listener listener;
synchronized (mLock) {
@@ -390,7 +390,7 @@
}
listener.onSupportedDeviceStatesChanged(
- supportedStates.toArray(new DeviceState[supportedStates.size()]));
+ supportedStates.toArray(new DeviceState[supportedStates.size()]), reason);
}
/** Computes the current device state and notifies the listener of a change, if needed. */
@@ -688,7 +688,10 @@
if (isThermalStatusCriticalOrAbove != isPreviousThermalStatusCriticalOrAbove) {
Slog.i(TAG, "Updating supported device states due to thermal status change."
+ " isThermalStatusCriticalOrAbove: " + isThermalStatusCriticalOrAbove);
- notifySupportedStatesChanged();
+ notifySupportedStatesChanged(
+ isThermalStatusCriticalOrAbove
+ ? SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL
+ : SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL);
}
}
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
index ec42324..db7fced 100644
--- a/services/core/jni/com_android_server_display_DisplayControl.cpp
+++ b/services/core/jni/com_android_server_display_DisplayControl.cpp
@@ -23,9 +23,11 @@
namespace android {
-static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure) {
+static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure,
+ jfloat requestedRefreshRate) {
ScopedUtfChars name(env, nameObj);
- sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure)));
+ sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure),
+ requestedRefreshRate));
return javaObjectForIBinder(env, token);
}
@@ -134,7 +136,7 @@
static const JNINativeMethod sDisplayMethods[] = {
// clang-format off
- {"nativeCreateDisplay", "(Ljava/lang/String;Z)Landroid/os/IBinder;",
+ {"nativeCreateDisplay", "(Ljava/lang/String;ZF)Landroid/os/IBinder;",
(void*)nativeCreateDisplay },
{"nativeDestroyDisplay", "(Landroid/os/IBinder;)V",
(void*)nativeDestroyDisplay },
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 75484d1..a39e021 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1129,6 +1129,7 @@
false /*allowInstall*/,
false /*instantApp*/,
false /*virtualPreload*/,
+ false /* stopped */,
UserManagerService.getInstance(),
null /*usesSdkLibraries*/,
null /*usesSdkLibrariesVersions*/,
@@ -1173,6 +1174,7 @@
true /*allowInstall*/,
false /*instantApp*/,
false /*virtualPreload*/,
+ false /* stopped */,
UserManagerService.getInstance(),
null /*usesSdkLibraries*/,
null /*usesSdkLibrariesVersions*/,
@@ -1217,6 +1219,7 @@
false /*allowInstall*/,
false /*instantApp*/,
false /*virtualPreload*/,
+ false /* stopped */,
UserManagerService.getInstance(),
null /*usesSdkLibraries*/,
null /*usesSdkLibrariesVersions*/,
@@ -1262,6 +1265,7 @@
false /*allowInstall*/,
false /*instantApp*/,
false /*virtualPreload*/,
+ false /* stopped */,
UserManagerService.getInstance(),
null /*usesSdkLibraries*/,
null /*usesSdkLibrariesVersions*/,
@@ -1284,6 +1288,48 @@
verifyUserState(userState, false /*notLaunched*/, false /*stopped*/, true /*installed*/);
}
+ /** Create a new stopped system PackageSetting */
+ @Test
+ public void testCreateNewSetting05() {
+ final PackageSetting testPkgSetting01 = Settings.createNewSetting(
+ PACKAGE_NAME,
+ null /*originalPkg*/,
+ null /*disabledPkg*/,
+ null /*realPkgName*/,
+ null /*sharedUser*/,
+ UPDATED_CODE_PATH /*codePath*/,
+ null /*legacyNativeLibraryPath*/,
+ "arm64-v8a" /*primaryCpuAbi*/,
+ "armeabi" /*secondaryCpuAbi*/,
+ UPDATED_VERSION_CODE /*versionCode*/,
+ ApplicationInfo.FLAG_SYSTEM /*pkgFlags*/,
+ 0 /*pkgPrivateFlags*/,
+ UserHandle.SYSTEM /*installUser*/,
+ false /*allowInstall*/,
+ false /*instantApp*/,
+ false /*virtualPreload*/,
+ true /* stopped */,
+ UserManagerService.getInstance(),
+ null /*usesSdkLibraries*/,
+ null /*usesSdkLibrariesVersions*/,
+ null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/,
+ null /*mimeGroups*/,
+ UUID.randomUUID());
+ assertThat(testPkgSetting01.getAppId(), is(0));
+ assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
+ assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
+ assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM));
+ assertThat(testPkgSetting01.getPrivateFlags(), is(0));
+ assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
+ assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
+ assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
+ assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
+ assertThat(testPkgSetting01.getVersionCode(), is(UPDATED_VERSION_CODE));
+ final PackageUserState userState = testPkgSetting01.readUserState(0);
+ verifyUserState(userState, false /*notLaunched*/, true /*stopped*/, true /*installed*/);
+ }
+
@Test
public void testSetPkgStateLibraryFiles_addNewFiles() {
final PackageSetting packageSetting = createPackageSetting("com.foo");
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index ef470fe..3b20735 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -112,7 +112,6 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.ActivityManager;
@@ -1265,7 +1264,30 @@
mService.interactiveStateChangedLocked(false);
mService.interactiveStateChangedLocked(true);
runnableCaptor.getValue().run();
- verify(mMockContext).sendBroadcastAsUser(mService.mTimeTickIntent, UserHandle.ALL);
+ final ArgumentCaptor<Bundle> optionsCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mMockContext).sendBroadcastAsUser(eq(mService.mTimeTickIntent), eq(UserHandle.ALL),
+ isNull(), optionsCaptor.capture());
+ verifyTimeTickBroadcastOptions(optionsCaptor.getValue());
+ }
+
+ @Test
+ public void sendsTimeTickOnAlarmTrigger() throws Exception {
+ final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ // Stubbing so the handler doesn't actually run the runnable.
+ doReturn(true).when(mService.mHandler).post(runnableCaptor.capture());
+ mService.mTimeTickTrigger.doAlarm(mock(IAlarmCompleteListener.class));
+ runnableCaptor.getValue().run();
+ final ArgumentCaptor<Bundle> optionsCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mMockContext).sendBroadcastAsUser(eq(mService.mTimeTickIntent), eq(UserHandle.ALL),
+ isNull(), optionsCaptor.capture());
+ verifyTimeTickBroadcastOptions(optionsCaptor.getValue());
+ }
+
+ private void verifyTimeTickBroadcastOptions(Bundle actualOptionsBundle) {
+ final BroadcastOptions actualOptions = new BroadcastOptions(actualOptionsBundle);
+ assertEquals(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT,
+ actualOptions.getDeliveryGroupPolicy());
+ assertTrue(actualOptions.isDeferUntilActive());
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index c40017a..2fb6c23 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -110,6 +110,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -225,6 +226,31 @@
}
}
+ /**
+ * Replace the process LRU with the given processes.
+ * @param apps
+ */
+ private void setProcessesToLru(ProcessRecord... apps) {
+ ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
+ lru.clear();
+ Collections.addAll(lru, apps);
+ }
+
+ /**
+ * Run updateOomAdjLocked().
+ * - If there's only one process, then it calls updateOomAdjLocked(ProcessRecord, int).
+ * - Otherwise, sets the processes to the LRU and run updateOomAdjLocked(int).
+ */
+ private void updateOomAdj(ProcessRecord... apps) {
+ if (apps.length == 1) {
+ sService.mOomAdjuster.updateOomAdjLocked(apps[0], OomAdjuster.OOM_ADJ_REASON_NONE);
+ } else {
+ setProcessesToLru(apps);
+ sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mProcessList.getLruProcessesLOSP().clear();
+ }
+ }
+
@SuppressWarnings("GuardedBy")
@Test
public void testUpdateOomAdj_DoOne_Persistent_TopUi_Sleeping() {
@@ -233,7 +259,7 @@
app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
app.mState.setHasTopUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERSISTENT_PROC_ADJ,
@@ -248,7 +274,7 @@
app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
app.mState.setHasTopUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_PERSISTENT_UI, PERSISTENT_PROC_ADJ,
SCHED_GROUP_TOP_APP);
@@ -262,7 +288,7 @@
app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
doReturn(app).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doReturn(null).when(sService).getTopApp();
assertProcStates(app, PROCESS_STATE_PERSISTENT_UI, PERSISTENT_PROC_ADJ,
@@ -277,7 +303,7 @@
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
doReturn(app).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doReturn(null).when(sService).getTopApp();
assertProcStates(app, PROCESS_STATE_TOP, FOREGROUND_APP_ADJ, SCHED_GROUP_TOP_APP);
@@ -291,7 +317,7 @@
doReturn(PROCESS_STATE_TOP_SLEEPING).when(sService.mAtmInternal).getTopProcessState();
app.mState.setRunningRemoteAnimation(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
assertProcStates(app, PROCESS_STATE_TOP_SLEEPING, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
@@ -304,7 +330,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(mock(ActiveInstrumentation.class)).when(app).getActiveInstrumentation();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doCallRealMethod().when(app).getActiveInstrumentation();
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, FOREGROUND_APP_ADJ,
@@ -319,7 +345,7 @@
doReturn(true).when(sService).isReceivingBroadcastLocked(any(ProcessRecord.class),
any(int[].class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doReturn(false).when(sService).isReceivingBroadcastLocked(any(ProcessRecord.class),
any(int[].class));
@@ -333,7 +359,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mServices.startExecutingService(mock(ServiceRecord.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_SERVICE, FOREGROUND_APP_ADJ, SCHED_GROUP_BACKGROUND);
}
@@ -346,7 +372,7 @@
doReturn(PROCESS_STATE_TOP_SLEEPING).when(sService.mAtmInternal).getTopProcessState();
doReturn(app).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doReturn(null).when(sService).getTopApp();
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
@@ -363,7 +389,7 @@
app.mState.setCurRawAdj(CACHED_APP_MIN_ADJ);
doReturn(null).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, CACHED_APP_MIN_ADJ,
SCHED_GROUP_BACKGROUND);
@@ -389,7 +415,7 @@
})).when(wpc).computeOomAdjFromActivities(
any(WindowProcessController.ComputeOomAdjCallback.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
}
@@ -403,7 +429,7 @@
doReturn(true).when(wpc).hasRecentTasks();
app.mState.setLastTopTime(SystemClock.uptimeMillis());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doCallRealMethod().when(wpc).hasRecentTasks();
assertEquals(PROCESS_STATE_CACHED_RECENT, app.mState.getSetProcState());
@@ -417,7 +443,7 @@
app.mServices.setHasForegroundServices(true, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION,
/* hasNoneType=*/false);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -430,7 +456,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -458,7 +484,7 @@
app.mState.setLastTopTime(SystemClock.uptimeMillis());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -477,7 +503,7 @@
- sService.mConstants.TOP_TO_FGS_GRACE_DURATION);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
PERCEPTIBLE_MEDIUM_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -503,7 +529,7 @@
- sService.mConstants.TOP_TO_FGS_GRACE_DURATION);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
// Procstate should be lower than FGS. (It should be SERVICE)
assertEquals(app.mState.getSetProcState(), PROCESS_STATE_SERVICE);
@@ -520,7 +546,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mState.setHasOverlayUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -534,7 +560,7 @@
app.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
app.mState.setLastTopTime(SystemClock.uptimeMillis());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE,
PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ, SCHED_GROUP_DEFAULT);
@@ -558,7 +584,7 @@
s.getConnections().clear();
app.mServices.updateHasTopStartedAlmostPerceptibleServices();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
}
@@ -578,7 +604,7 @@
nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
app.mServices.updateHasTopStartedAlmostPerceptibleServices();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
}
@@ -599,7 +625,7 @@
s.getConnections().clear();
app.mServices.updateHasTopStartedAlmostPerceptibleServices();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertNotEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
}
@@ -611,7 +637,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mState.setForcingToImportant(new Object());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -625,7 +651,7 @@
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).isHeavyWeightProcess();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doReturn(false).when(wpc).isHeavyWeightProcess();
assertProcStates(app, PROCESS_STATE_HEAVY_WEIGHT, HEAVY_WEIGHT_APP_ADJ,
@@ -640,7 +666,7 @@
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).isHomeProcess();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_HOME, HOME_APP_ADJ, SCHED_GROUP_BACKGROUND);
}
@@ -654,7 +680,7 @@
doReturn(true).when(wpc).isPreviousProcess();
doReturn(true).when(wpc).hasActivities();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
SCHED_GROUP_BACKGROUND);
@@ -669,7 +695,7 @@
backupTarget.app = app;
doReturn(backupTarget).when(sService.mBackupTargets).get(anyInt());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doReturn(null).when(sService.mBackupTargets).get(anyInt());
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, BACKUP_APP_ADJ,
@@ -683,7 +709,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mServices.setHasClientActivities(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertEquals(PROCESS_STATE_CACHED_ACTIVITY_CLIENT, app.mState.getSetProcState());
}
@@ -695,7 +721,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mServices.setTreatLikeActivity(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.mState.getSetProcState());
}
@@ -712,7 +738,7 @@
s.lastActivity = SystemClock.uptimeMillis();
app.mServices.startService(s);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_B_ADJ, SCHED_GROUP_BACKGROUND);
}
@@ -724,7 +750,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
app.mState.setMaxAdj(PERCEPTIBLE_LOW_APP_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, PERCEPTIBLE_LOW_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -739,7 +765,7 @@
app.mState.setCurRawAdj(SERVICE_ADJ);
doReturn(null).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertTrue(ProcessList.CACHED_APP_MIN_ADJ <= app.mState.getSetAdj());
assertTrue(ProcessList.CACHED_APP_MAX_ADJ >= app.mState.getSetAdj());
@@ -756,7 +782,7 @@
s.lastActivity = SystemClock.uptimeMillis();
app.mServices.startService(s);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
}
@@ -774,7 +800,7 @@
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
doReturn(client).when(sService).getTopApp();
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
doReturn(null).when(sService).getTopApp();
assertProcStates(app, PROCESS_STATE_SERVICE, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
@@ -790,7 +816,7 @@
bindService(app, client, null, Context.BIND_WAIVE_PRIORITY
| Context.BIND_TREAT_LIKE_ACTIVITY, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.mState.getSetProcState());
}
@@ -810,7 +836,7 @@
mock(ActivityServiceConnectionsHolder.class));
doReturn(true).when(cr.activity).isActivityVisible();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
assertEquals(SCHED_GROUP_TOP_APP_BOUND, app.mState.getSetSchedGroup());
@@ -822,11 +848,8 @@
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
bindService(app, app, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
}
@@ -841,7 +864,7 @@
client.mServices.setTreatLikeActivity(true);
bindService(app, client, null, 0, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PROCESS_STATE_CACHED_EMPTY, app.mState.getSetProcState());
}
@@ -861,7 +884,7 @@
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
doReturn(client).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
doReturn(null).when(sService).getTopApp();
assertEquals(PREVIOUS_APP_ADJ, app.mState.getSetAdj());
@@ -878,7 +901,7 @@
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
client.mState.setHasTopUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -894,7 +917,7 @@
bindService(app, client, null, Context.BIND_IMPORTANT, mock(IBinder.class));
client.mServices.startExecutingService(mock(ServiceRecord.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
}
@@ -910,7 +933,7 @@
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
doReturn(client).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
doReturn(null).when(sService).getTopApp();
assertProcStates(app, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_DEFAULT);
@@ -926,9 +949,10 @@
bindService(app, client, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PROCESS_STATE_BOUND_FOREGROUND_SERVICE, app.mState.getSetProcState());
+ assertEquals(PROCESS_STATE_PERSISTENT, client.mState.getSetProcState());
}
@SuppressWarnings("GuardedBy")
@@ -941,7 +965,7 @@
bindService(app, client, null, Context.BIND_NOT_FOREGROUND, mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.mState.getSetProcState());
}
@@ -956,7 +980,7 @@
bindService(app, client, null, 0, mock(IBinder.class));
client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PROCESS_STATE_FOREGROUND_SERVICE, app.mState.getSetProcState());
}
@@ -973,13 +997,14 @@
backupTarget.app = client;
doReturn(backupTarget).when(sService.mBackupTargets).get(anyInt());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
+
doReturn(null).when(sService.mBackupTargets).get(anyInt());
assertEquals(BACKUP_APP_ADJ, app.mState.getSetAdj());
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertEquals(PERSISTENT_SERVICE_ADJ, app.mState.getSetAdj());
}
@@ -994,7 +1019,7 @@
bindService(app, client, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
client.mState.setRunningRemoteAnimation(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertEquals(PERCEPTIBLE_LOW_APP_ADJ, app.mState.getSetAdj());
}
@@ -1009,7 +1034,7 @@
bindService(app, client, null, Context.BIND_NOT_VISIBLE, mock(IBinder.class));
client.mState.setRunningRemoteAnimation(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_APP_ADJ, app.mState.getSetAdj());
}
@@ -1024,7 +1049,7 @@
bindService(app, client, null, 0, mock(IBinder.class));
client.mState.setHasOverlayUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_APP_ADJ, app.mState.getSetAdj());
}
@@ -1040,7 +1065,7 @@
bindService(app, client, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
}
@@ -1055,7 +1080,7 @@
bindService(app, client, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
doReturn(false).when(wpc).isHeavyWeightProcess();
assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
@@ -1072,7 +1097,7 @@
bindService(app, client, null, 0, mock(IBinder.class));
client.mState.setRunningRemoteAnimation(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(VISIBLE_APP_ADJ, app.mState.getSetAdj());
}
@@ -1087,7 +1112,7 @@
bindService(app, client, null, Context.BIND_IMPORTANT_BACKGROUND, mock(IBinder.class));
client.mState.setHasOverlayUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PROCESS_STATE_IMPORTANT_BACKGROUND, app.mState.getSetProcState());
}
@@ -1098,7 +1123,7 @@
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
bindProvider(app, app, null, null, false);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
}
@@ -1112,12 +1137,8 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindProvider(app, client, null, null, false);
client.mServices.setTreatLikeActivity(true);
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client);
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
}
@@ -1133,7 +1154,7 @@
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
doReturn(client).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
doReturn(null).when(sService).getTopApp();
assertProcStates(app, PROCESS_STATE_BOUND_TOP, FOREGROUND_APP_ADJ, SCHED_GROUP_DEFAULT);
@@ -1149,7 +1170,7 @@
client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindProvider(app, client, null, null, false);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1164,7 +1185,7 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindProvider(app, client, null, null, true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, FOREGROUND_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1177,7 +1198,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
app.mProviders.setLastProviderTime(SystemClock.uptimeMillis());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
SCHED_GROUP_BACKGROUND);
@@ -1197,7 +1218,7 @@
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
doReturn(client2).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, client2, app);
doReturn(null).when(sService).getTopApp();
assertProcStates(app, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
@@ -1217,7 +1238,7 @@
bindService(app, client2, null, 0, mock(IBinder.class));
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1236,7 +1257,7 @@
bindService(client, client2, null, 0, mock(IBinder.class));
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1255,13 +1276,12 @@
bindService(client, client2, null, 0, mock(IBinder.class));
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(client2, app, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
+
+ // Note: We add processes to LRU but still call updateOomAdjLocked() with a specific
+ // processes.
+ setProcessesToLru(app, client, client2);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1292,13 +1312,8 @@
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client2, client, null, 0, mock(IBinder.class));
client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1321,13 +1336,8 @@
bindService(client, client2, null, 0, mock(IBinder.class));
bindService(client2, client, null, 0, mock(IBinder.class));
client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1357,15 +1367,8 @@
bindService(client3, client4, null, 0, mock(IBinder.class));
bindService(client4, client3, null, 0, mock(IBinder.class));
client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
- lru.add(client3);
- lru.add(client4);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2, client3, client4);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1396,14 +1399,8 @@
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
client3.mState.setForcingToImportant(new Object());
bindService(app, client3, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
- lru.add(client3);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2, client3);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1427,14 +1424,8 @@
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
client3.mState.setForcingToImportant(new Object());
bindService(app, client3, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
- lru.add(client3);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2, client3);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1460,15 +1451,8 @@
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
client4.mState.setForcingToImportant(new Object());
bindService(app, client4, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
- lru.add(client3);
- lru.add(client4);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2, client3, client4);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1496,15 +1480,8 @@
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
client4.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(app, client4, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
- lru.add(client3);
- lru.add(client4);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2, client3, client4);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1529,7 +1506,7 @@
client3.mState.setForcingToImportant(new Object());
bindService(app, client3, null, 0, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, client2, client3, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1548,7 +1525,7 @@
bindProvider(client, client2, null, null, false);
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1567,13 +1544,8 @@
bindProvider(client, client2, null, null, false);
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(client2, app, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1592,7 +1564,7 @@
bindProvider(client, client2, null, null, false);
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1611,14 +1583,8 @@
bindProvider(client, client2, null, null, false);
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindProvider(client2, app, null, null, false);
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
-
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1643,8 +1609,7 @@
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
- sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client1, client2, app1, app2);
assertProcStates(app1, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1653,8 +1618,7 @@
bindService(app1, client1, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class));
bindService(app2, client2, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class));
- sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
- sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app1, app2);
assertProcStates(app1, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
SCHED_GROUP_TOP_APP);
@@ -1662,8 +1626,7 @@
SCHED_GROUP_DEFAULT);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
- sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
- sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client1, client2, app1, app2);
assertProcStates(app1, PROCESS_STATE_IMPORTANT_FOREGROUND, VISIBLE_APP_ADJ,
SCHED_GROUP_TOP_APP);
assertProcStates(app2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1689,8 +1652,7 @@
final ServiceRecord s2 = bindService(app2, client2, null,
Context.BIND_IMPORTANT, mock(IBinder.class));
- sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
- sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client1, client2, app1, app2);
assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1699,7 +1661,7 @@
bindService(app2, client1, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
mock(IBinder.class));
- sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app2);
assertProcStates(app2, PROCESS_STATE_PERSISTENT, PERSISTENT_SERVICE_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1715,10 +1677,10 @@
bindService(app2, client2, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
mock(IBinder.class));
- sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
- sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app1, app2);
- assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ // VISIBLE_APP_ADJ is the max oom-adj for BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE.
+ assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
assertProcStates(app2, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1747,7 +1709,7 @@
bindService(app1, client1, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
- sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client1, app1);
assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1767,7 +1729,7 @@
bindService(app1, client1, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
- sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client1, app1);
assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1798,13 +1760,7 @@
client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
client2.mState.setForcingToImportant(new Object());
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app1);
- lru.add(app2);
- lru.add(app3);
- lru.add(client1);
- lru.add(client2);
+ setProcessesToLru(app1, app2, app3, client1, client2);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
final ComponentName cn1 = ComponentName.unflattenFromString(
@@ -1891,13 +1847,9 @@
ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
app2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(app2);
+
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app, app2);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1915,13 +1867,8 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
app2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(app, app2, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(app2);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app, app2);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1942,14 +1889,8 @@
bindService(app2, app3, null, 0, mock(IBinder.class));
app3.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(app3, app, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(app2);
- lru.add(app3);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app, app2, app3);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1990,16 +1931,8 @@
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(app, app5, s, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(app2);
- lru.add(app3);
- lru.add(app4);
- lru.add(app5);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app, app2, app3, app4, app5);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -2035,16 +1968,8 @@
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(app, app5, s, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app5);
- lru.add(app4);
- lru.add(app3);
- lru.add(app2);
- lru.add(app);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app5, app4, app3, app2, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -2080,16 +2005,8 @@
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(app, app5, s, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app3);
- lru.add(app4);
- lru.add(app2);
- lru.add(app);
- lru.add(app5);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app3, app4, app2, app, app5);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -2119,18 +2036,13 @@
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
client3.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
bindService(app, client3, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
- lru.add(client3);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2, client3);
- assertEquals(PROCESS_CAPABILITY_ALL, client.mState.getSetCapability());
- assertEquals(PROCESS_CAPABILITY_ALL, client2.mState.getSetCapability());
- assertEquals(PROCESS_CAPABILITY_ALL, app.mState.getSetCapability());
+ final int expected = PROCESS_CAPABILITY_ALL;
+ assertEquals(expected, client.mState.getSetCapability());
+ assertEquals(expected, client2.mState.getSetCapability());
+ assertEquals(expected, app.mState.getSetCapability());
}
@SuppressWarnings("GuardedBy")
@@ -2155,16 +2067,8 @@
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindProvider(app, app5, cr, null, false);
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(app2);
- lru.add(app3);
- lru.add(app4);
- lru.add(app5);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app, app2, app3, app4, app5);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -2202,15 +2106,9 @@
doCallRealMethod().when(s).getConnections();
s.startRequested = true;
s.lastActivity = now;
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app3);
- lru.add(app2);
- lru.add(app);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.mNumServiceProcs = 3;
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app3, app2, app);
assertEquals(SERVICE_B_ADJ, app3.mState.getSetAdj());
assertEquals(SERVICE_ADJ, app2.mState.getSetAdj());
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
index f69054b..8979585 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
@@ -20,6 +20,7 @@
import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
@@ -129,7 +130,28 @@
}
@Test
- public void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsStartedVisibleOnBg()
+ public void
+ testStartVisibleBgProfile_onDefaultDisplay_whenParentIsStartedVisibleOnBgOnSecondaryDisplay()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
+ startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+ BG_VISIBLE, DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+ expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
+
+ assertInvisibleUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public void
+ testStartVisibleBgProfile_onDefaultDisplay_whenParentIsStartedVisibleOnBgOnDefaultDisplay()
throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(
onVisible(PARENT_USER_ID),
@@ -183,4 +205,25 @@
assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
listener.verify();
}
+
+ @Test
+ public void testStartVisibleBgUser_onDefaultDisplay_currentUserId() throws Exception {
+ int currentUserId = INITIAL_CURRENT_USER_ID;
+
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId,
+ BG_VISIBLE, DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE);
+
+ // Assert current user visibility
+ expectUserIsVisible(currentUserId);
+ expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
+ expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
+
+ assertUserCanBeAssignedExtraDisplay(currentUserId, OTHER_SECONDARY_DISPLAY_ID);
+
+ listener.verify();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 82f6493..c952609 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -945,13 +945,15 @@
}
mListener = listener;
- mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates);
+ mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates,
+ SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
mListener.onStateChanged(mSupportedDeviceStates[0].getIdentifier());
}
public void notifySupportedDeviceStates(DeviceState[] supportedDeviceStates) {
mSupportedDeviceStates = supportedDeviceStates;
- mListener.onSupportedDeviceStatesChanged(supportedDeviceStates);
+ mListener.onSupportedDeviceStatesChanged(supportedDeviceStates,
+ SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
}
public void setState(int identifier) {
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
new file mode 100644
index 0000000..8196d6a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicestate;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+
+/**
+ * Unit tests for {@link DeviceStateNotificationController}.
+ * <p/>
+ * Run with <code>atest com.android.server.devicestate.DeviceStateNotificationControllerTest</code>.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class DeviceStateNotificationControllerTest {
+
+ private static final int STATE_WITHOUT_NOTIFICATION = 1;
+ private static final int STATE_WITH_ACTIVE_NOTIFICATION = 2;
+ private static final int STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION = 3;
+
+ private static final int VALID_APP_UID = 1000;
+ private static final int INVALID_APP_UID = 2000;
+ private static final String VALID_APP_NAME = "Valid app name";
+ private static final String INVALID_APP_NAME = "Invalid app name";
+ private static final String VALID_APP_LABEL = "Valid app label";
+
+ private static final String NAME_1 = "name1";
+ private static final String TITLE_1 = "title1";
+ private static final String CONTENT_1 = "content1:%1$s";
+ private static final String NAME_2 = "name2";
+ private static final String TITLE_2 = "title2";
+ private static final String CONTENT_2 = "content2:%1$s";
+ private static final String THERMAL_TITLE_2 = "thermal_title2";
+ private static final String THERMAL_CONTENT_2 = "thermal_content2";
+
+ private DeviceStateNotificationController mController;
+
+ private final ArgumentCaptor<Notification> mNotificationCaptor = ArgumentCaptor.forClass(
+ Notification.class);
+ private final NotificationManager mNotificationManager = mock(NotificationManager.class);
+
+ @Before
+ public void setup() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ Handler handler = mock(Handler.class);
+ PackageManager packageManager = mock(PackageManager.class);
+ Runnable cancelStateRunnable = mock(Runnable.class);
+ ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
+
+ final SparseArray<DeviceStateNotificationController.NotificationInfo> notificationInfos =
+ new SparseArray<>();
+ notificationInfos.put(STATE_WITH_ACTIVE_NOTIFICATION,
+ new DeviceStateNotificationController.NotificationInfo(
+ NAME_1, TITLE_1, CONTENT_1,
+ "", ""));
+ notificationInfos.put(STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION,
+ new DeviceStateNotificationController.NotificationInfo(
+ NAME_2, TITLE_2, CONTENT_2,
+ THERMAL_TITLE_2, THERMAL_CONTENT_2));
+
+ when(packageManager.getNameForUid(VALID_APP_UID)).thenReturn(VALID_APP_NAME);
+ when(packageManager.getNameForUid(INVALID_APP_UID)).thenReturn(INVALID_APP_NAME);
+ when(packageManager.getApplicationInfo(eq(VALID_APP_NAME), ArgumentMatchers.any()))
+ .thenReturn(applicationInfo);
+ when(packageManager.getApplicationInfo(eq(INVALID_APP_NAME), ArgumentMatchers.any()))
+ .thenThrow(new PackageManager.NameNotFoundException());
+ when(applicationInfo.loadLabel(eq(packageManager))).thenReturn(VALID_APP_LABEL);
+
+ mController = new DeviceStateNotificationController(
+ context, handler, cancelStateRunnable, notificationInfos,
+ packageManager, mNotificationManager);
+ }
+
+ @Test
+ public void test_activeNotification() {
+ mController.showStateActiveNotificationIfNeeded(
+ STATE_WITH_ACTIVE_NOTIFICATION, VALID_APP_UID);
+
+ // Verify that the notification manager is called with correct notification information.
+ verify(mNotificationManager).notify(
+ eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+ eq(DeviceStateNotificationController.NOTIFICATION_ID),
+ mNotificationCaptor.capture());
+ Notification notification = mNotificationCaptor.getValue();
+ assertEquals(TITLE_1, notification.extras.getString(Notification.EXTRA_TITLE));
+ assertEquals(String.format(CONTENT_1, VALID_APP_LABEL),
+ notification.extras.getString(Notification.EXTRA_TEXT));
+ assertEquals(Notification.FLAG_ONGOING_EVENT,
+ notification.flags & Notification.FLAG_ONGOING_EVENT);
+
+ // Verify that the notification action is as expected.
+ Notification.Action[] actions = notification.actions;
+ assertEquals(1, actions.length);
+ Notification.Action action = actions[0];
+ assertEquals(DeviceStateNotificationController.INTENT_ACTION_CANCEL_STATE,
+ action.actionIntent.getIntent().getAction());
+
+ // Verify that the notification is properly canceled.
+ mController.cancelNotification(STATE_WITH_ACTIVE_NOTIFICATION);
+ verify(mNotificationManager).cancel(
+ DeviceStateNotificationController.NOTIFICATION_TAG,
+ DeviceStateNotificationController.NOTIFICATION_ID);
+ }
+
+ @Test
+ public void test_thermalNotification() {
+ // Verify that the active notification is created.
+ mController.showStateActiveNotificationIfNeeded(
+ STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION, VALID_APP_UID);
+ verify(mNotificationManager).notify(
+ eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+ eq(DeviceStateNotificationController.NOTIFICATION_ID),
+ mNotificationCaptor.capture());
+ Notification notification = mNotificationCaptor.getValue();
+ assertEquals(TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
+ assertEquals(String.format(CONTENT_2, VALID_APP_LABEL),
+ notification.extras.getString(Notification.EXTRA_TEXT));
+ assertEquals(Notification.FLAG_ONGOING_EVENT,
+ notification.flags & Notification.FLAG_ONGOING_EVENT);
+ Mockito.clearInvocations(mNotificationManager);
+
+ // Verify that the thermal critical notification is created.
+ mController.showThermalCriticalNotificationIfNeeded(
+ STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION);
+ verify(mNotificationManager).notify(
+ eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+ eq(DeviceStateNotificationController.NOTIFICATION_ID),
+ mNotificationCaptor.capture());
+ notification = mNotificationCaptor.getValue();
+ assertEquals(THERMAL_TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
+ assertEquals(THERMAL_CONTENT_2, notification.extras.getString(Notification.EXTRA_TEXT));
+ assertEquals(0, notification.flags & Notification.FLAG_ONGOING_EVENT);
+
+ // Verify that the notification is canceled.
+ mController.cancelNotification(STATE_WITH_ACTIVE_NOTIFICATION);
+ verify(mNotificationManager).cancel(
+ DeviceStateNotificationController.NOTIFICATION_TAG,
+ DeviceStateNotificationController.NOTIFICATION_ID);
+ }
+
+ @Test
+ public void test_deviceStateWithoutNotification() {
+ // Verify that no notification is created.
+ mController.showStateActiveNotificationIfNeeded(
+ STATE_WITHOUT_NOTIFICATION, VALID_APP_UID);
+ verify(mNotificationManager, Mockito.never()).notify(
+ eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+ eq(DeviceStateNotificationController.NOTIFICATION_ID),
+ mNotificationCaptor.capture());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index 430504c..fe37f42 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -58,7 +58,7 @@
@Test
public void addRequest() {
- OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
assertNull(mStatusListener.getLastStatus(request));
@@ -68,14 +68,14 @@
@Test
public void addRequest_cancelExistingRequestThroughNewRequest() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
assertNull(mStatusListener.getLastStatus(firstRequest));
mController.addRequest(firstRequest);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
assertNull(mStatusListener.getLastStatus(secondRequest));
@@ -86,7 +86,7 @@
@Test
public void addRequest_cancelActiveRequest() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
mController.addRequest(firstRequest);
@@ -100,7 +100,7 @@
@Test
public void addBaseStateRequest() {
- OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
assertNull(mStatusListener.getLastStatus(request));
@@ -110,14 +110,14 @@
@Test
public void addBaseStateRequest_cancelExistingBaseStateRequestThroughNewRequest() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
assertNull(mStatusListener.getLastStatus(firstRequest));
mController.addBaseStateRequest(firstRequest);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
assertNull(mStatusListener.getLastStatus(secondRequest));
@@ -128,7 +128,7 @@
@Test
public void addBaseStateRequest_cancelActiveBaseStateRequest() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
mController.addBaseStateRequest(firstRequest);
@@ -142,12 +142,13 @@
@Test
public void handleBaseStateChanged() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */,
DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */,
OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* uid */,
0 /* requestedState */,
0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
@@ -167,10 +168,11 @@
@Test
public void handleProcessDied() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* uid */,
1 /* requestedState */,
0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
@@ -189,10 +191,11 @@
public void handleProcessDied_stickyRequests() {
mController.setStickyRequestsAllowed(true);
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* uid */,
1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
mController.addRequest(firstRequest);
@@ -211,10 +214,11 @@
@Test
public void handleNewSupportedStates() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* uid */,
1 /* requestedState */,
0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
@@ -224,18 +228,20 @@
mController.addBaseStateRequest(baseStateRequest);
assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
- mController.handleNewSupportedStates(new int[]{0, 1});
+ mController.handleNewSupportedStates(new int[]{0, 1},
+ DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
- mController.handleNewSupportedStates(new int[]{0});
+ mController.handleNewSupportedStates(new int[]{0},
+ DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
}
@Test
public void cancelOverrideRequestsTest() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
mController.addRequest(firstRequest);
@@ -250,7 +256,7 @@
private Map<OverrideRequest, Integer> mLastStatusMap = new HashMap<>();
@Override
- public void onStatusChanged(@NonNull OverrideRequest request, int newStatus) {
+ public void onStatusChanged(@NonNull OverrideRequest request, int newStatus, int flags) {
mLastStatusMap.put(request, newStatus);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 2d252cb..9a43762 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -157,7 +157,7 @@
VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
Handler handler, DisplayAdapter.Listener displayAdapterListener) {
return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
- (String name, boolean secure) -> mMockDisplayToken);
+ (String name, boolean secure, float refreshRate) -> mMockDisplayToken);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 7fac9b6..7125796 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -19,6 +19,9 @@
import static android.content.Context.SENSOR_SERVICE;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL;
import static com.android.server.policy.DeviceStateProviderImpl.DEFAULT_DEVICE_STATE;
import static org.junit.Assert.assertArrayEquals;
@@ -124,7 +127,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(new DeviceState[]{DEFAULT_DEVICE_STATE},
mDeviceStateArrayCaptor.getValue());
@@ -151,7 +155,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(1, "", 0 /* flags */),
new DeviceState(2, "", 0 /* flags */) };
@@ -183,7 +188,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(1, "", DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS),
new DeviceState(2, "", 0 /* flags */) };
@@ -212,7 +218,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(1, "", 0 /* flags */),
new DeviceState(2, "", 0 /* flags */) };
@@ -247,7 +254,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(1, "", 0 /* flags */),
new DeviceState(2, "CLOSED", 0 /* flags */) };
@@ -265,7 +273,8 @@
Mockito.clearInvocations(listener);
provider.notifyLidSwitchChanged(1, true /* lidOpen */);
- verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(1, mIntegerCaptor.getValue().intValue());
}
@@ -336,7 +345,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -358,7 +368,8 @@
provider.onSensorChanged(event0);
- verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(3, mIntegerCaptor.getValue().intValue());
@@ -370,7 +381,8 @@
provider.onSensorChanged(event1);
- verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(2, mIntegerCaptor.getValue().intValue());
@@ -382,7 +394,8 @@
provider.onSensorChanged(event2);
- verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(1, mIntegerCaptor.getValue().intValue());
}
@@ -397,7 +410,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -410,12 +424,14 @@
Mockito.clearInvocations(listener);
provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_MODERATE);
- verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
Mockito.clearInvocations(listener);
// The THERMAL_TEST state should be disabled.
provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_CRITICAL);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL));
assertArrayEquals(
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -426,7 +442,8 @@
// The THERMAL_TEST state should be re-enabled.
provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_LIGHT);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL));
assertArrayEquals(
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -468,7 +485,8 @@
provider.onSensorChanged(event2);
- verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
}
@@ -513,7 +531,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
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 49edde5..12f124e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -525,6 +525,13 @@
@Test
public void testZenUpgradeNotification() {
+ /**
+ * Commit a485ec65b5ba947d69158ad90905abf3310655cf disabled DND status change
+ * notification on watches. So, assume that the device is not watch.
+ */
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(false);
+
// shows zen upgrade notification if stored settings says to shows,
// zen has not been updated, boot is completed
// and we're setting zen mode on
diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp
index af0eca9..7332d2d 100644
--- a/services/voiceinteraction/Android.bp
+++ b/services/voiceinteraction/Android.bp
@@ -20,6 +20,7 @@
srcs: [":services.voiceinteraction-sources"],
libs: [
"services.core",
+ "app-compat-annotations",
"android.hardware.power-V1-java",
"android.hardware.power-V1.0-java",
],
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 03796de..cc22847 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -51,7 +51,6 @@
import android.os.ServiceManager;
import android.os.SharedMemory;
import android.provider.DeviceConfig;
-import android.service.voice.HotwordDetectedResult;
import android.service.voice.HotwordDetectionService;
import android.service.voice.HotwordDetector;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
@@ -89,19 +88,18 @@
static final boolean DEBUG = false;
/**
- * For apps targeting Android API {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above,
- * implementors of the {@link HotwordDetectionService} must not augment the phrase IDs which are
- * supplied via {@link HotwordDetectionService
- * #onDetect(AlwaysOnHotwordDetector.EventPayload, long, HotwordDetectionService.Callback)}.
+ * For apps targeting Android API Build.VERSION_CODES#UPSIDE_DOWN_CAKE and above,
+ * implementors of the HotwordDetectionService must not augment the phrase IDs which are
+ * supplied via HotwordDetectionService
+ * #onDetect(AlwaysOnHotwordDetector.EventPayload, long, HotwordDetectionService.Callback).
*
- * <p>The {@link HotwordDetectedResult#getHotwordPhraseId()} must match one of the phrase IDs
- * from the {@link android.service.voice.AlwaysOnHotwordDetector
- * .EventPayload#getKeyphraseRecognitionExtras()} list.
+ * <p>The HotwordDetectedResult#getHotwordPhraseId() must match one of the phrase IDs
+ * from the AlwaysOnHotwordDetector.EventPayload#getKeyphraseRecognitionExtras() list.
* </p>
*
- * <p>This behavior change is made to ensure the {@link HotwordDetectionService} honors what
- * it receives from the {@link android.hardware.soundtrigger.SoundTriggerModule}, and it
- * cannot signal to the client application a phrase which was not origially detected.
+ * <p>This behavior change is made to ensure the HotwordDetectionService honors what
+ * it receives from the android.hardware.soundtrigger.SoundTriggerModule, and it
+ * cannot signal to the client application a phrase which was not originally detected.
* </p>
*/
@ChangeId
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index f0f230f..acfc194 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3866,13 +3866,12 @@
* NR_SA - NR SA is unmetered for sub-6 frequencies
* NR_SA_MMWAVE - NR SA is unmetered for mmwave frequencies
*
- * Note that this config only applies if an unmetered SubscriptionPlan is set via
- * {@link SubscriptionManager#setSubscriptionPlans(int, List)} or an unmetered override is set
+ * Note that this config only applies if an unmetered SubscriptionPlan is set via {@link
+ * SubscriptionManager#setSubscriptionPlans(int, List, long)} or an unmetered override is set
* via {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, int[], long)}
* or {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, long)}.
* If neither SubscriptionPlans nor an override are set, then no network types can be unmetered
* regardless of the value of this config.
- * TODO: remove other unmetered keys and replace with this
* @hide
*/
public static final String KEY_UNMETERED_NETWORK_TYPES_STRING_ARRAY =
@@ -3887,73 +3886,18 @@
* NR_SA - NR SA is unmetered when roaming for sub-6 frequencies
* NR_SA_MMWAVE - NR SA is unmetered when roaming for mmwave frequencies
*
- * Note that this config only applies if an unmetered SubscriptionPlan is set via
- * {@link SubscriptionManager#setSubscriptionPlans(int, List)} or an unmetered override is set
+ * Note that this config only applies if an unmetered SubscriptionPlan is set via {@link
+ * SubscriptionManager#setSubscriptionPlans(int, List, long)} or an unmetered override is set
* via {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, int[], long)}
* or {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, long)}.
* If neither SubscriptionPlans nor an override are set, then no network types can be unmetered
* when roaming regardless of the value of this config.
- * TODO: remove KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL and replace with this
* @hide
*/
public static final String KEY_ROAMING_UNMETERED_NETWORK_TYPES_STRING_ARRAY =
"roaming_unmetered_network_types_string_array";
/**
- * Whether NR (non-standalone) should be unmetered for all frequencies.
- * If either {@link #KEY_UNMETERED_NR_NSA_MMWAVE_BOOL} or
- * {@link #KEY_UNMETERED_NR_NSA_SUB6_BOOL} are true, then this value will be ignored.
- * @hide
- */
- public static final String KEY_UNMETERED_NR_NSA_BOOL = "unmetered_nr_nsa_bool";
-
- /**
- * Whether NR (non-standalone) frequencies above 6GHz (millimeter wave) should be unmetered.
- * If this is true, then the value for {@link #KEY_UNMETERED_NR_NSA_BOOL} will be ignored.
- * @hide
- */
- public static final String KEY_UNMETERED_NR_NSA_MMWAVE_BOOL = "unmetered_nr_nsa_mmwave_bool";
-
- /**
- * Whether NR (non-standalone) frequencies below 6GHz (sub6) should be unmetered.
- * If this is true, then the value for {@link #KEY_UNMETERED_NR_NSA_BOOL} will be ignored.
- * @hide
- */
- public static final String KEY_UNMETERED_NR_NSA_SUB6_BOOL = "unmetered_nr_nsa_sub6_bool";
-
- /**
- * Whether NR (non-standalone) should be unmetered when the device is roaming.
- * If false, then the values for {@link #KEY_UNMETERED_NR_NSA_BOOL},
- * {@link #KEY_UNMETERED_NR_NSA_MMWAVE_BOOL}, {@link #KEY_UNMETERED_NR_NSA_SUB6_BOOL},
- * and unmetered {@link SubscriptionPlan} will be ignored.
- * @hide
- */
- public static final String KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL =
- "unmetered_nr_nsa_when_roaming_bool";
-
- /**
- * Whether NR (standalone) should be unmetered for all frequencies.
- * If either {@link #KEY_UNMETERED_NR_SA_MMWAVE_BOOL} or
- * {@link #KEY_UNMETERED_NR_SA_SUB6_BOOL} are true, then this value will be ignored.
- * @hide
- */
- public static final String KEY_UNMETERED_NR_SA_BOOL = "unmetered_nr_sa_bool";
-
- /**
- * Whether NR (standalone) frequencies above 6GHz (millimeter wave) should be unmetered.
- * If this is true, then the value for {@link #KEY_UNMETERED_NR_SA_BOOL} will be ignored.
- * @hide
- */
- public static final String KEY_UNMETERED_NR_SA_MMWAVE_BOOL = "unmetered_nr_sa_mmwave_bool";
-
- /**
- * Whether NR (standalone) frequencies below 6GHz (sub6) should be unmetered.
- * If this is true, then the value for {@link #KEY_UNMETERED_NR_SA_BOOL} will be ignored.
- * @hide
- */
- public static final String KEY_UNMETERED_NR_SA_SUB6_BOOL = "unmetered_nr_sa_sub6_bool";
-
- /**
* Support ASCII 7-BIT encoding for long SMS. This carrier config is used to enable
* this feature.
* @hide
@@ -10043,13 +9987,6 @@
sDefaults.putStringArray(KEY_UNMETERED_NETWORK_TYPES_STRING_ARRAY, new String[] {
"NR_NSA", "NR_NSA_MMWAVE", "NR_SA", "NR_SA_MMWAVE"});
sDefaults.putStringArray(KEY_ROAMING_UNMETERED_NETWORK_TYPES_STRING_ARRAY, new String[0]);
- sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_BOOL, false);
- sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_MMWAVE_BOOL, false);
- sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_SUB6_BOOL, false);
- sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL, false);
- sDefaults.putBoolean(KEY_UNMETERED_NR_SA_BOOL, false);
- sDefaults.putBoolean(KEY_UNMETERED_NR_SA_MMWAVE_BOOL, false);
- sDefaults.putBoolean(KEY_UNMETERED_NR_SA_SUB6_BOOL, false);
sDefaults.putBoolean(KEY_ASCII_7_BIT_SUPPORT_FOR_LONG_MESSAGE_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_WIFI_CALLING_ICON_IN_STATUS_BAR_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL, false);
diff --git a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
index 8308821..c2f5b8f 100644
--- a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
+++ b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
@@ -23,6 +23,7 @@
import android.telephony.euicc.EuiccCardManager;
import android.telephony.euicc.EuiccManager;
import android.telephony.ims.ImsManager;
+import android.telephony.satellite.SatelliteManager;
import com.android.internal.util.Preconditions;
@@ -98,6 +99,11 @@
context -> SmsManager.getSmsManagerForContextAndSubscriptionId(context,
SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
);
+ SystemServiceRegistry.registerContextAwareService(
+ Context.SATELLITE_SERVICE,
+ SatelliteManager.class,
+ context -> new SatelliteManager(context)
+ );
}
/** @hide */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0921834..c65faea 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -9432,7 +9432,6 @@
ALLOWED_NETWORK_TYPES_REASON_POWER,
ALLOWED_NETWORK_TYPES_REASON_CARRIER,
ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G,
- ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AllowedNetworkTypesReason {
@@ -9471,15 +9470,6 @@
public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3;
/**
- * To indicate allowed network type change is requested by an update to the
- * {@link android.os.UserManager.DISALLOW_CELLULAR_2G} user restriction.
- *
- * @hide
- */
- @SystemApi
- public static final int ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS = 4;
-
- /**
* Set the allowed network types of the device and provide the reason triggering the allowed
* network change.
* <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or
@@ -9503,7 +9493,6 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE} permissions.
* <ol>
* <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}</li>
- * <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS}</li>
* </ol>
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -9578,7 +9567,6 @@
case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER:
case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER:
case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
- case ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS:
return true;
}
return false;
diff --git a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
new file mode 100644
index 0000000..5c3fa32
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.telephony.satellite.PointingInfo;
+
+/**
+ * Callback for position updates from the satellite service.
+ * @hide
+ */
+oneway interface ISatellitePositionUpdateCallback {
+ void onSatellitePositionUpdate(in PointingInfo pointingInfo);
+ void onMessageTransferStateUpdate(in int state);
+}
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.aidl b/telephony/java/android/telephony/satellite/PointingInfo.aidl
new file mode 100644
index 0000000..7ff95cd
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/PointingInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+parcelable PointingInfo;
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
new file mode 100644
index 0000000..d015427
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
+import android.util.ArrayMap;
+
+import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.ITelephony;
+import com.android.telephony.Rlog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+/**
+ * Manages satellite operations such as provisioning, pointing, messaging, location sharing, etc.
+ * To get the object, call {@link Context#getSystemService(Context.SATELLITE_SERVICE)}.
+ * To create an instance of {@link SatelliteManager} associated with a specific subscription ID,
+ * call {@link #createForSubscriptionId(int)}.
+ *
+ * @hide
+ */
+@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SATELLITE)
+public class SatelliteManager {
+ private static final String TAG = "SatelliteManager";
+
+ /**
+ * Map of all SatellitePositionUpdateCallback and their associated callback ids.
+ */
+ private final Map<SatellitePositionUpdateCallback, Integer> mSatellitePositionUpdateCallbacks =
+ new ArrayMap<>();
+
+ /**
+ * AtomicInteger for the id of the next SatellitePositionUpdateCallback.
+ */
+ private final AtomicInteger mSatellitePositionUpdateCallbackId = new AtomicInteger(0);
+
+ /**
+ * The subscription ID for this SatelliteManager.
+ */
+ private final int mSubId;
+
+ /**
+ * Context this SatelliteManager is for.
+ */
+ @Nullable private final Context mContext;
+
+ /**
+ * Create an instance of the SatelliteManager.
+ *
+ * @param context The context the SatelliteManager belongs to.
+ */
+ public SatelliteManager(@Nullable Context context) {
+ // TODO: replace DEFAULT_SUBSCRIPTION_ID with DEFAULT_SATELLITE_SUBSCRIPTION_ID
+ this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+ }
+
+ /**
+ * Create a new SatelliteManager associated with the given subscription ID.
+ *
+ * @param subId The subscription ID to create the SatelliteManager with.
+ * @return A SatelliteManager that uses the given subscription ID for all calls.
+ */
+ @NonNull public SatelliteManager createForSubscriptionId(int subId) {
+ return new SatelliteManager(mContext, subId);
+ }
+
+ /**
+ * Create an instance of the SatelliteManager associated with a particular subscription.
+ *
+ * @param context The context the SatelliteManager belongs to.
+ * @param subId The subscription ID associated with the SatelliteManager.
+ */
+ private SatelliteManager(@Nullable Context context, int subId) {
+ mContext = context;
+ mSubId = subId;
+ }
+
+ /**
+ * Successful response.
+ */
+ public static final int SATELLITE_SERVICE_SUCCESS = 0;
+ /**
+ * Satellite server is not reachable.
+ */
+ public static final int SATELLITE_SERVICE_SERVER_NOT_REACHABLE = 1;
+ /**
+ * Error received from the satellite server.
+ */
+ public static final int SATELLITE_SERVICE_SERVER_ERROR = 2;
+ /**
+ * Unexpected telephony internal error.
+ */
+ public static final int SATELLITE_SERVICE_TELEPHONY_INTERNAL_ERROR = 3;
+ /**
+ * Modem error received from the satellite service.
+ */
+ public static final int SATELLITE_SERVICE_MODEM_ERROR = 4;
+ /**
+ * System error received from the satellite service.
+ */
+ public static final int SATELLITE_SERVICE_SYSTEM_ERROR = 5;
+ /**
+ * Invalid arguments passed.
+ */
+ public static final int SATELLITE_SERVICE_INVALID_ARGUMENTS = 6;
+ /**
+ * Invalid modem state.
+ */
+ public static final int SATELLITE_SERVICE_INVALID_MODEM_STATE = 7;
+ /**
+ * Invalid SIM state.
+ */
+ public static final int SATELLITE_SERVICE_INVALID_SIM_STATE = 8;
+ /**
+ * Invalid state.
+ */
+ public static final int SATELLITE_SERVICE_INVALID_STATE = 9;
+ /**
+ * Satellite service is unavailable.
+ */
+ public static final int SATELLITE_SERVICE_NOT_AVAILABLE = 10;
+ /**
+ * Satellite service is not supported by the device or OS.
+ */
+ public static final int SATELLITE_SERVICE_NOT_SUPPORTED = 11;
+ /**
+ * Satellite service is rate limited.
+ */
+ public static final int SATELLITE_SERVICE_RATE_LIMITED = 12;
+ /**
+ * Satellite service has no memory available.
+ */
+ public static final int SATELLITE_SERVICE_NO_MEMORY = 13;
+ /**
+ * Satellite service has no resources available.
+ */
+ public static final int SATELLITE_SERVICE_NO_RESOURCES = 14;
+ /**
+ * Failed to send a request to the satellite service.
+ */
+ public static final int SATELLITE_SERVICE_REQUEST_FAILED = 15;
+ /**
+ * Failed to send a request to the satellite service for the given subscription ID.
+ */
+ public static final int SATELLITE_SERVICE_INVALID_SUBSCRIPTION_ID = 16;
+ /**
+ * Error received from satellite service.
+ */
+ public static final int SATELLITE_SERVICE_ERROR = 17;
+
+ /**
+ * Satellite service is disabled on the requested subscription.
+ */
+ public static final int SATELLITE_SERVICE_DISABLED = 18;
+
+ /** @hide */
+ @IntDef(prefix = {"SATELLITE_SERVICE_"}, value = {
+ SATELLITE_SERVICE_SUCCESS,
+ SATELLITE_SERVICE_SERVER_NOT_REACHABLE,
+ SATELLITE_SERVICE_SERVER_ERROR,
+ SATELLITE_SERVICE_TELEPHONY_INTERNAL_ERROR,
+ SATELLITE_SERVICE_MODEM_ERROR,
+ SATELLITE_SERVICE_SYSTEM_ERROR,
+ SATELLITE_SERVICE_INVALID_ARGUMENTS,
+ SATELLITE_SERVICE_INVALID_MODEM_STATE,
+ SATELLITE_SERVICE_INVALID_SIM_STATE,
+ SATELLITE_SERVICE_INVALID_STATE,
+ SATELLITE_SERVICE_NOT_AVAILABLE,
+ SATELLITE_SERVICE_NOT_SUPPORTED,
+ SATELLITE_SERVICE_RATE_LIMITED,
+ SATELLITE_SERVICE_NO_MEMORY,
+ SATELLITE_SERVICE_NO_RESOURCES,
+ SATELLITE_SERVICE_REQUEST_FAILED,
+ SATELLITE_SERVICE_INVALID_SUBSCRIPTION_ID,
+ SATELLITE_SERVICE_ERROR,
+ SATELLITE_SERVICE_DISABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SatelliteServiceResult {}
+
+ /**
+ * Message transfer is waiting to acquire.
+ */
+ public static final int SATELLITE_MESSAGE_TRANSFER_STATE_WAITING_TO_ACQUIRE = 0;
+ /**
+ * Message is being sent.
+ */
+ public static final int SATELLITE_MESSAGE_TRANSFER_STATE_SENDING = 1;
+ /**
+ * Message is being received.
+ */
+ public static final int SATELLITE_MESSAGE_TRANSFER_STATE_RECEIVING = 2;
+ /**
+ * Message transfer is being retried.
+ */
+ public static final int SATELLITE_MESSAGE_TRANSFER_STATE_RETRYING = 3;
+ /**
+ * Message transfer is complete.
+ */
+ public static final int SATELLITE_MESSAGE_TRANSFER_STATE_COMPLETE = 4;
+
+ /** @hide */
+ @IntDef(prefix = {"SATELLITE_MESSAGE_TRANSFER_STATE_"}, value = {
+ SATELLITE_MESSAGE_TRANSFER_STATE_WAITING_TO_ACQUIRE,
+ SATELLITE_MESSAGE_TRANSFER_STATE_SENDING,
+ SATELLITE_MESSAGE_TRANSFER_STATE_RECEIVING,
+ SATELLITE_MESSAGE_TRANSFER_STATE_RETRYING,
+ SATELLITE_MESSAGE_TRANSFER_STATE_COMPLETE
+ })
+ public @interface SatelliteMessageTransferState {}
+
+ /**
+ * Callback for position updates from the satellite service.
+ */
+ public interface SatellitePositionUpdateCallback {
+ /**
+ * Called when the satellite position changes.
+ *
+ * @param pointingInfo The pointing info containing the satellite location.
+ */
+ void onSatellitePositionUpdate(@NonNull PointingInfo pointingInfo);
+
+ /**
+ * Called when satellite message transfer state changes.
+ *
+ * @param state The new message transfer state.
+ */
+ void onMessageTransferStateUpdate(@SatelliteMessageTransferState int state);
+ }
+
+ /**
+ * Start receiving satellite position updates.
+ * This can be called by the pointing UI when the user starts pointing to the satellite.
+ * Modem should continue to report the pointing input as the device or satellite moves.
+ * Satellite position updates are started only on {@link #SATELLITE_SERVICE_SUCCESS}.
+ * All other results indicate that this operation failed.
+ *
+ * @param executor The executor to run callbacks on.
+ * @param callback The callback to notify of changes in satellite position.
+ * @return The result of the operation.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteServiceResult public int startSatellitePositionUpdates(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull SatellitePositionUpdateCallback callback) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ int id;
+ if (mSatellitePositionUpdateCallbacks.containsKey(callback)) {
+ id = mSatellitePositionUpdateCallbacks.get(callback);
+ } else {
+ id = mSatellitePositionUpdateCallbackId.getAndIncrement();
+ }
+ int result = telephony.startSatellitePositionUpdates(mSubId, id,
+ new ISatellitePositionUpdateCallback.Stub() {
+ @Override
+ public void onSatellitePositionUpdate(
+ @NonNull PointingInfo pointingInfo) {
+ logd("onSatellitePositionUpdate: pointingInfo=" + pointingInfo);
+ executor.execute(() ->
+ callback.onSatellitePositionUpdate(pointingInfo));
+ }
+
+ @Override
+ public void onMessageTransferStateUpdate(
+ @SatelliteMessageTransferState int state) {
+ logd("onMessageTransferStateUpdate: state=" + state);
+ executor.execute(() ->
+ callback.onMessageTransferStateUpdate(state));
+ }
+ });
+ if (result == SATELLITE_SERVICE_SUCCESS) {
+ mSatellitePositionUpdateCallbacks.put(callback, id);
+ }
+ return result;
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("startSatellitePositionUpdates RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return SATELLITE_SERVICE_REQUEST_FAILED;
+ }
+
+ /**
+ * Stop receiving satellite position updates.
+ * This can be called by the pointing UI when the user stops pointing to the satellite.
+ * Satellite position updates are stopped only on {@link #SATELLITE_SERVICE_SUCCESS}.
+ * All other results indicate that this operation failed.
+ *
+ * @param callback The callback that was passed in {@link
+ * #startSatellitePositionUpdates(Executor, SatellitePositionUpdateCallback)}.
+ * @return The result of the operation.
+ * @throws IllegalArgumentException if the callback is invalid.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteServiceResult public int stopSatellitePositionUpdates(
+ @NonNull SatellitePositionUpdateCallback callback) {
+ if (!mSatellitePositionUpdateCallbacks.containsKey(callback)) {
+ throw new IllegalArgumentException(
+ "startSatellitePositionUpdates was never called with the callback provided.");
+ }
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ int result = telephony.stopSatellitePositionUpdates(mSubId,
+ mSatellitePositionUpdateCallbacks.get(callback));
+ if (result == SATELLITE_SERVICE_SUCCESS) {
+ mSatellitePositionUpdateCallbacks.remove(callback);
+ // TODO: Notify SmsHandler that pointing UI stopped
+ }
+ return result;
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("stopSatellitePositionUpdates RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return SATELLITE_SERVICE_REQUEST_FAILED;
+ }
+
+ /**
+ * Get maximum number of characters per text message on satellite.
+ * @param executor - The executor on which the result listener will be called.
+ * @param resultListener - Listener that will be called when the operation is successful.
+ * If this method returns {@link #SATELLITE_SERVICE_SUCCESS}, listener
+ * will be called with maximum characters limit.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ *
+ * @return The result of the operation
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteServiceResult
+ public int getMaxCharactersPerSatelliteTextMessage(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Integer> resultListener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(resultListener);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(result)));
+ }
+ };
+
+ return telephony.getMaxCharactersPerSatelliteTextMessage(mSubId, internalCallback);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("getMaxCharactersPerSatelliteTextMessage() RemoteException:" + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return SATELLITE_SERVICE_REQUEST_FAILED;
+ }
+
+ private static ITelephony getITelephony() {
+ ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyServiceRegisterer()
+ .get());
+ if (binder == null) {
+ throw new RuntimeException("Could not find Telephony Service.");
+ }
+ return binder;
+ }
+
+ private static void logd(@NonNull String log) {
+ Rlog.d(TAG, log);
+ }
+
+ private static void loge(@NonNull String log) {
+ Rlog.e(TAG, log);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 48b1657..5486365 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -66,6 +66,7 @@
import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.aidl.IRcsConfigCallback;
+import android.telephony.satellite.ISatellitePositionUpdateCallback;
import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.IBooleanConsumer;
@@ -2636,14 +2637,14 @@
*/
boolean isRemovableEsimDefaultEuicc(String callingPackage);
- /**
- * Get the component name of the default app to direct respond-via-message intent for the
- * user associated with this subscription, update the cache if there is no respond-via-message
- * application currently configured for this user.
- * @return component name of the app and class to direct Respond Via Message intent to, or
- * {@code null} if the functionality is not supported.
- * @hide
- */
+ /**
+ * Get the component name of the default app to direct respond-via-message intent for the
+ * user associated with this subscription, update the cache if there is no respond-via-message
+ * application currently configured for this user.
+ * @return component name of the app and class to direct Respond Via Message intent to, or
+ * {@code null} if the functionality is not supported.
+ * @hide
+ */
ComponentName getDefaultRespondViaMessageApplication(int subId, boolean updateIfNeeded);
/**
@@ -2667,12 +2668,12 @@
void setNullCipherAndIntegrityEnabled(boolean enabled);
/**
- * Get whether the radio is able to connect with null ciphering or integrity
- * algorithms. Note that this retrieves the phone-global preference and not
- * the state of the radio.
- *
- * @hide
- */
+ * Get whether the radio is able to connect with null ciphering or integrity
+ * algorithms. Note that this retrieves the phone-global preference and not
+ * the state of the radio.
+ *
+ * @hide
+ */
boolean isNullCipherAndIntegrityPreferenceEnabled();
/**
@@ -2696,5 +2697,21 @@
/**
* Get the carrier restriction status of the device.
*/
- void getCarrierRestrictionStatus(IIntegerConsumer internalCallback, String packageName);
-}
+ void getCarrierRestrictionStatus(IIntegerConsumer internalCallback, String packageName);
+
+ /**
+ * Start receiving satellite pointing updates.
+ */
+ int startSatellitePositionUpdates(int subId, int callbackId,
+ in ISatellitePositionUpdateCallback callback);
+
+ /**
+ * Stop receiving satellite pointing updates.
+ */
+ int stopSatellitePositionUpdates(int subId, int callbackId);
+
+ /**
+ * Get maximum number of characters per text message on satellite.
+ */
+ int getMaxCharactersPerSatelliteTextMessage(int subId, IIntegerConsumer internalCallback);
+}
\ No newline at end of file
diff --git a/tests/BinaryTransparencyHostTest/Android.bp b/tests/BinaryTransparencyHostTest/Android.bp
index 142e3dd..dc6bdff 100644
--- a/tests/BinaryTransparencyHostTest/Android.bp
+++ b/tests/BinaryTransparencyHostTest/Android.bp
@@ -35,6 +35,7 @@
data: [
":BinaryTransparencyTestApp",
":EasterEgg",
+ ":com.android.apex.cts.shim.v2_rebootless_prebuilt",
],
test_suites: [
"general-tests",
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
index 84bed92..8db3d00 100644
--- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -17,6 +17,7 @@
package android.transparency.test;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -29,14 +30,22 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.TimeUnit;
+
// TODO: Add @Presubmit
@RunWith(DeviceJUnit4ClassRunner.class)
public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test {
private static final String PACKAGE_NAME = "android.transparency.test.app";
+ private static final String JOB_ID = "1740526926";
+
+ /** Waiting time for the job to be scheduled */
+ private static final int JOB_CREATION_MAX_SECONDS = 5;
+
@After
public void tearDown() throws Exception {
uninstallPackage("com.android.egg");
+ uninstallRebootlessApex();
}
@Test
@@ -64,6 +73,28 @@
}
@Test
+ public void testRebootlessApexUpdateTriggersJobScheduling() throws Exception {
+ cancelPendingJob();
+ installRebootlessApex();
+
+ // Verify
+ expectJobToBeScheduled();
+ // Just cancel since we can't verifying very meaningfully.
+ cancelPendingJob();
+ }
+
+ @Test
+ public void testPreloadUpdateTriggersJobScheduling() throws Exception {
+ cancelPendingJob();
+ installPackage("EasterEgg.apk");
+
+ // Verify
+ expectJobToBeScheduled();
+ // Just cancel since we can't verifying very meaningfully.
+ cancelPendingJob();
+ }
+
+ @Test
public void testMeasureMbas() throws Exception {
// TODO(265244016): figure out a way to install an MBA
}
@@ -74,4 +105,47 @@
options.setTestMethodName(method);
runDeviceTests(options);
}
+
+ private void cancelPendingJob() throws DeviceNotAvailableException {
+ CommandResult result = getDevice().executeShellV2Command(
+ "cmd jobscheduler cancel android " + JOB_ID);
+ assertTrue(result.getStatus() == CommandStatus.SUCCESS);
+ }
+
+ private void expectJobToBeScheduled() throws Exception {
+ for (int i = 0; i < JOB_CREATION_MAX_SECONDS; i++) {
+ CommandResult result = getDevice().executeShellV2Command(
+ "cmd jobscheduler get-job-state android " + JOB_ID);
+ String state = result.getStdout().toString();
+ if (state.startsWith("unknown")) {
+ // The job hasn't been scheduled yet. So try again.
+ TimeUnit.SECONDS.sleep(1);
+ } else if (result.getExitCode() != 0) {
+ fail("Failing due to unexpected job state: " + result);
+ } else {
+ // The job exists, which is all we care about here
+ return;
+ }
+ }
+ fail("Timed out waiting for the job to be scheduled");
+ }
+
+ private void installRebootlessApex() throws Exception {
+ installPackage("com.android.apex.cts.shim.v2_rebootless.apex", "--force-non-staged");
+ }
+
+ private void uninstallRebootlessApex() throws DeviceNotAvailableException {
+ // Reboot only if the APEX is not the pre-install one.
+ CommandResult result = getDevice().executeShellV2Command(
+ "pm list packages -f --apex-only |grep com.android.apex.cts.shim");
+ assertTrue(result.getStatus() == CommandStatus.SUCCESS);
+ if (result.getStdout().contains("/data/apex/active/")) {
+ uninstallPackage("com.android.apex.cts.shim");
+ getDevice().reboot();
+
+ // Reboot enforces SELinux. Make it permissive again.
+ CommandResult runResult = getDevice().executeShellV2Command("setenforce 0");
+ assertTrue(runResult.getStatus() == CommandStatus.SUCCESS);
+ }
+ }
}