summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobInfo.java174
-rw-r--r--apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java80
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobStore.java16
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java5
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java8
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java36
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java40
-rw-r--r--core/api/current.txt4
-rw-r--r--core/api/test-current.txt4
-rw-r--r--core/java/android/app/ActivityManagerInternal.java10
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java5
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java87
-rw-r--r--core/java/android/hardware/radio/IRadioService.aidl2
-rw-r--r--core/java/android/hardware/radio/RadioManager.java4
-rw-r--r--core/java/android/window/TaskConstants.java11
-rw-r--r--core/tests/BroadcastRadioTests/AndroidManifest.xml2
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java2
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java9
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java2
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java9
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java8
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java5
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java18
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java42
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java19
-rw-r--r--media/java/android/media/AudioManager.java10
-rw-r--r--media/java/android/media/IAudioService.aidl4
-rw-r--r--media/java/android/media/tv/OWNERS1
-rw-r--r--media/java/android/media/tv/TvInputManager.java52
-rw-r--r--media/java/android/media/tv/TvInputService.java2
-rw-r--r--media/java/android/media/tv/TvView.java1
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppService.java1
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppView.java1
-rw-r--r--native/android/surface_control.cpp9
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml6
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_preferences.xml2
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java2
-rw-r--r--packages/SystemUI/res/layout/activity_rear_display_education.xml1
-rw-r--r--packages/SystemUI/res/layout/activity_rear_display_education_opened.xml1
-rw-r--r--packages/SystemUI/res/values/config.xml1
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java85
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java12
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java50
-rw-r--r--services/core/java/android/app/usage/UsageStatsManagerInternal.java10
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java12
-rw-r--r--services/core/java/com/android/server/audio/SoundDoseHelper.java8
-rw-r--r--services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java5
-rw-r--r--services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java3
-rw-r--r--services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java5
-rw-r--r--services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java39
-rw-r--r--services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java22
-rw-r--r--services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java20
-rw-r--r--services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java63
-rw-r--r--services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java15
-rw-r--r--services/core/java/com/android/server/cpu/CpuInfoReader.java159
-rw-r--r--services/core/java/com/android/server/cpu/CpuMonitorService.java594
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java4
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java41
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java79
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java616
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java27
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java111
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/job/JobStoreTest.java60
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java38
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java5
-rw-r--r--tools/locked_region_code_injection/Android.bp17
-rw-r--r--tools/locked_region_code_injection/OWNERS4
-rw-r--r--tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java196
-rw-r--r--tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java37
-rw-r--r--tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java9
-rw-r--r--tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java23
-rw-r--r--tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java23
-rw-r--r--tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java166
-rw-r--r--tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedLock.java38
-rw-r--r--tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedTarget.java56
-rw-r--r--tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java12
-rw-r--r--tools/locked_region_code_injection/test/manifest.txt1
-rwxr-xr-xtools/locked_region_code_injection/test/unit-test.sh98
92 files changed, 3149 insertions, 539 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 7f02cb36dfc1..2f6b6897addb 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -432,6 +432,7 @@ public class JobInfo implements Parcelable {
@UnsupportedAppUsage
private final ComponentName service;
private final int constraintFlags;
+ private final int mPreferredConstraintFlags;
private final TriggerContentUri[] triggerContentUris;
private final long triggerContentUpdateDelay;
private final long triggerContentMaxDelay;
@@ -522,6 +523,30 @@ public class JobInfo implements Parcelable {
}
/**
+ * @hide
+ * @see JobInfo.Builder#setPrefersBatteryNotLow(boolean)
+ */
+ public boolean isPreferBatteryNotLow() {
+ return (mPreferredConstraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0;
+ }
+
+ /**
+ * @hide
+ * @see JobInfo.Builder#setPrefersCharging(boolean)
+ */
+ public boolean isPreferCharging() {
+ return (mPreferredConstraintFlags & CONSTRAINT_FLAG_CHARGING) != 0;
+ }
+
+ /**
+ * @hide
+ * @see JobInfo.Builder#setPrefersDeviceIdle(boolean)
+ */
+ public boolean isPreferDeviceIdle() {
+ return (mPreferredConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0;
+ }
+
+ /**
* @see JobInfo.Builder#setRequiresCharging(boolean)
*/
public boolean isRequireCharging() {
@@ -557,6 +582,13 @@ public class JobInfo implements Parcelable {
}
/**
+ * @hide
+ */
+ public int getPreferredConstraintFlags() {
+ return mPreferredConstraintFlags;
+ }
+
+ /**
* Which content: URIs must change for the job to be scheduled. Returns null
* if there are none required.
* @see JobInfo.Builder#addTriggerContentUri(TriggerContentUri)
@@ -800,6 +832,9 @@ public class JobInfo implements Parcelable {
if (constraintFlags != j.constraintFlags) {
return false;
}
+ if (mPreferredConstraintFlags != j.mPreferredConstraintFlags) {
+ return false;
+ }
if (!Arrays.equals(triggerContentUris, j.triggerContentUris)) {
return false;
}
@@ -880,6 +915,7 @@ public class JobInfo implements Parcelable {
hashCode = 31 * hashCode + service.hashCode();
}
hashCode = 31 * hashCode + constraintFlags;
+ hashCode = 31 * hashCode + mPreferredConstraintFlags;
if (triggerContentUris != null) {
hashCode = 31 * hashCode + Arrays.hashCode(triggerContentUris);
}
@@ -922,6 +958,7 @@ public class JobInfo implements Parcelable {
}
service = in.readParcelable(null);
constraintFlags = in.readInt();
+ mPreferredConstraintFlags = in.readInt();
triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR);
triggerContentUpdateDelay = in.readLong();
triggerContentMaxDelay = in.readLong();
@@ -956,6 +993,7 @@ public class JobInfo implements Parcelable {
clipGrantFlags = b.mClipGrantFlags;
service = b.mJobService;
constraintFlags = b.mConstraintFlags;
+ mPreferredConstraintFlags = b.mPreferredConstraintFlags;
triggerContentUris = b.mTriggerContentUris != null
? b.mTriggerContentUris.toArray(new TriggerContentUri[b.mTriggerContentUris.size()])
: null;
@@ -999,6 +1037,7 @@ public class JobInfo implements Parcelable {
}
out.writeParcelable(service, flags);
out.writeInt(constraintFlags);
+ out.writeInt(mPreferredConstraintFlags);
out.writeTypedArray(triggerContentUris, flags);
out.writeLong(triggerContentUpdateDelay);
out.writeLong(triggerContentMaxDelay);
@@ -1146,6 +1185,7 @@ public class JobInfo implements Parcelable {
private int mFlags;
// Requirements.
private int mConstraintFlags;
+ private int mPreferredConstraintFlags;
private NetworkRequest mNetworkRequest;
private long mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN;
private long mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN;
@@ -1199,6 +1239,7 @@ public class JobInfo implements Parcelable {
mBias = job.getBias();
mFlags = job.getFlags();
mConstraintFlags = job.getConstraintFlags();
+ mPreferredConstraintFlags = job.getPreferredConstraintFlags();
mNetworkRequest = job.getRequiredNetwork();
mNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes();
mNetworkUploadBytes = job.getEstimatedNetworkUploadBytes();
@@ -1341,9 +1382,6 @@ public class JobInfo implements Parcelable {
* Calling this method will override any requirements previously defined
* by {@link #setRequiredNetwork(NetworkRequest)}; you typically only
* want to call one of these methods.
- * <p> Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
- * {@link JobScheduler} may try to shift the execution of jobs requiring
- * {@link #NETWORK_TYPE_ANY} to when there is access to an un-metered network.
* <p class="note">
* When your job executes in
* {@link JobService#onStartJob(JobParameters)}, be sure to use the
@@ -1505,10 +1543,105 @@ public class JobInfo implements Parcelable {
}
/**
- * Specify that to run this job, the device must be charging (or be a
+ * Specify that this job would prefer to be run when the device's battery is not low.
+ * This defaults to {@code false}.
+ *
+ * <p>The system may attempt to delay this job until the device's battery is not low,
+ * but may choose to run it even if the device's battery is low. JobScheduler will not stop
+ * this job if this constraint is no longer satisfied after the job has started running.
+ * If this job must only run when the device's battery is not low,
+ * use {@link #setRequiresBatteryNotLow(boolean)} instead.
+ *
+ * <p>
+ * Because it doesn't make sense for a constraint to be both preferred and required,
+ * calling both this and {@link #setRequiresBatteryNotLow(boolean)} with {@code true}
+ * will result in an {@link java.lang.IllegalArgumentException} when
+ * {@link android.app.job.JobInfo.Builder#build()} is called.
+ *
+ * @param prefersBatteryNotLow Pass {@code true} to prefer that the device's battery level
+ * not be low in order to run the job.
+ * @return This object for method chaining
+ * @see JobInfo#isPreferBatteryNotLow()
+ * @hide
+ */
+ @NonNull
+ public Builder setPrefersBatteryNotLow(boolean prefersBatteryNotLow) {
+ mPreferredConstraintFlags =
+ (mPreferredConstraintFlags & ~CONSTRAINT_FLAG_BATTERY_NOT_LOW)
+ | (prefersBatteryNotLow ? CONSTRAINT_FLAG_BATTERY_NOT_LOW : 0);
+ return this;
+ }
+
+ /**
+ * Specify that this job would prefer to be run when the device is charging (or be a
* non-battery-powered device connected to permanent power, such as Android TV
* devices). This defaults to {@code false}.
*
+ * <p>
+ * The system may attempt to delay this job until the device is charging, but may
+ * choose to run it even if the device is not charging. JobScheduler will not stop
+ * this job if this constraint is no longer satisfied after the job has started running.
+ * If this job must only run when the device is charging,
+ * use {@link #setRequiresCharging(boolean)} instead.
+ *
+ * <p>
+ * Because it doesn't make sense for a constraint to be both preferred and required,
+ * calling both this and {@link #setRequiresCharging(boolean)} with {@code true}
+ * will result in an {@link java.lang.IllegalArgumentException} when
+ * {@link android.app.job.JobInfo.Builder#build()} is called.
+ *
+ * @param prefersCharging Pass {@code true} to prefer that the device be
+ * charging in order to run the job.
+ * @return This object for method chaining
+ * @see JobInfo#isPreferCharging()
+ * @hide
+ */
+ @NonNull
+ public Builder setPrefersCharging(boolean prefersCharging) {
+ mPreferredConstraintFlags = (mPreferredConstraintFlags & ~CONSTRAINT_FLAG_CHARGING)
+ | (prefersCharging ? CONSTRAINT_FLAG_CHARGING : 0);
+ return this;
+ }
+
+ /**
+ * Specify that this job would prefer to be run when the device is not in active use.
+ * This defaults to {@code false}.
+ *
+ * <p>The system may attempt to delay this job until the device is not in active use,
+ * but may choose to run it even if the device is not idle. JobScheduler will not stop
+ * this job if this constraint is no longer satisfied after the job has started running.
+ * If this job must only run when the device is not in active use,
+ * use {@link #setRequiresDeviceIdle(boolean)} instead.
+ *
+ * <p>
+ * Because it doesn't make sense for a constraint to be both preferred and required,
+ * calling both this and {@link #setRequiresDeviceIdle(boolean)} with {@code true}
+ * will result in an {@link java.lang.IllegalArgumentException} when
+ * {@link android.app.job.JobInfo.Builder#build()} is called.
+ *
+ * <p class="note">Despite the similar naming, this job constraint is <em>not</em>
+ * related to the system's "device idle" or "doze" states. This constraint only
+ * determines whether a job is allowed to run while the device is directly in use.
+ *
+ * @param prefersDeviceIdle Pass {@code true} to prefer that the device not be in active
+ * use when running this job.
+ * @return This object for method chaining
+ * @see JobInfo#isRequireDeviceIdle()
+ * @hide
+ */
+ @NonNull
+ public Builder setPrefersDeviceIdle(boolean prefersDeviceIdle) {
+ mPreferredConstraintFlags = (mPreferredConstraintFlags & ~CONSTRAINT_FLAG_DEVICE_IDLE)
+ | (prefersDeviceIdle ? CONSTRAINT_FLAG_DEVICE_IDLE : 0);
+ return this;
+ }
+
+ /**
+ * Specify that to run this job, the device must be charging (or be a
+ * non-battery-powered device connected to permanent power, such as Android TV
+ * devices). This defaults to {@code false}. Setting this to {@code false} <b>DOES NOT</b>
+ * mean the job will only run when the device is not charging.
+ *
* <p class="note">For purposes of running jobs, a battery-powered device
* "charging" is not quite the same as simply being connected to power. If the
* device is so busy that the battery is draining despite a power connection, jobs
@@ -1530,7 +1663,9 @@ public class JobInfo implements Parcelable {
* Specify that to run this job, the device's battery level must not be low.
* This defaults to false. If true, the job will only run when the battery level
* is not low, which is generally the point where the user is given a "low battery"
- * warning.
+ * warning. Setting this to {@code false} <b>DOES NOT</b> mean the job will only run
+ * when the battery is low.
+ *
* @param batteryNotLow Whether or not the device's battery level must not be low.
* @see JobInfo#isRequireBatteryNotLow()
*/
@@ -1543,7 +1678,8 @@ public class JobInfo implements Parcelable {
/**
* When set {@code true}, ensure that this job will not run if the device is in active use.
* The default state is {@code false}: that is, the for the job to be runnable even when
- * someone is interacting with the device.
+ * someone is interacting with the device. Setting this to {@code false} <b>DOES NOT</b>
+ * mean the job will only run when the device is not idle.
*
* <p>This state is a loose definition provided by the system. In general, it means that
* the device is not currently being used interactively, and has not been in use for some
@@ -2156,6 +2292,29 @@ public class JobInfo implements Parcelable {
}
}
+ if ((constraintFlags & mPreferredConstraintFlags) != 0) {
+ // Something is marked as both preferred and required. Try to give a clear exception
+ // reason.
+ if ((constraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0
+ && (mPreferredConstraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0) {
+ throw new IllegalArgumentException(
+ "battery-not-low constraint cannot be both preferred and required");
+ }
+ if ((constraintFlags & CONSTRAINT_FLAG_CHARGING) != 0
+ && (mPreferredConstraintFlags & CONSTRAINT_FLAG_CHARGING) != 0) {
+ throw new IllegalArgumentException(
+ "charging constraint cannot be both preferred and required");
+ }
+ if ((constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0
+ && (mPreferredConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
+ throw new IllegalArgumentException(
+ "device idle constraint cannot be both preferred and required");
+ }
+ // Couldn't figure out what the overlap was. Just use a generic message.
+ throw new IllegalArgumentException(
+ "constraints cannot be both preferred and required");
+ }
+
if (isUserInitiated) {
if (hasEarlyConstraint) {
throw new IllegalArgumentException("A user-initiated job cannot have a time delay");
@@ -2173,7 +2332,8 @@ public class JobInfo implements Parcelable {
if (mPriority != PRIORITY_MAX) {
throw new IllegalArgumentException("A user-initiated job must be max priority.");
}
- if ((constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
+ if ((constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0
+ || (mPreferredConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
throw new IllegalArgumentException(
"A user-initiated job cannot have a device-idle constraint");
}
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 9b64edf53d8c..f50a90248030 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -225,6 +225,8 @@ public interface AppStandbyInternal {
void setActiveAdminApps(Set<String> adminPkgs, int userId);
+ void setAdminProtectedPackages(Set<String> packageNames, int userId);
+
/**
* @return {@code true} if the given package is an active device admin app.
*/
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 056b6b913255..08810b549e62 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -483,7 +483,7 @@ public class JobSchedulerService extends com.android.server.SystemService
case Constants.KEY_RUNTIME_UI_LIMIT_MS:
case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR:
case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS:
- case Constants.KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS:
+ case Constants.KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS:
if (!runtimeUpdated) {
mConstants.updateRuntimeConstantsLocked();
runtimeUpdated = true;
@@ -583,8 +583,8 @@ public class JobSchedulerService extends com.android.server.SystemService
"runtime_min_ui_data_transfer_guarantee_buffer_factor";
private static final String KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS =
"runtime_min_ui_data_transfer_guarantee_ms";
- private static final String KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS =
- "runtime_ui_data_transfer_limit_ms";
+ private static final String KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS =
+ "runtime_use_data_estimates_for_limits";
private static final String KEY_PERSIST_IN_SPLIT_FILES = "persist_in_split_files";
@@ -616,15 +616,14 @@ public class JobSchedulerService extends com.android.server.SystemService
@VisibleForTesting
public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
public static final long DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS =
- Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS);
+ Math.max(6 * HOUR_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS);
public static final long DEFAULT_RUNTIME_UI_LIMIT_MS =
- Math.max(60 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS);
+ Math.max(12 * HOUR_IN_MILLIS, DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS);
public static final float DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
1.35f;
public static final long DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS =
Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS);
- public static final long DEFAULT_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS =
- Math.min(Long.MAX_VALUE, DEFAULT_RUNTIME_UI_LIMIT_MS);
+ public static final boolean DEFAULT_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = false;
static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true;
static final int DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 100_000;
@@ -755,13 +754,17 @@ public class JobSchedulerService extends com.android.server.SystemService
/**
* The minimum amount of time we try to guarantee user-initiated data transfer jobs
- * will run for.
+ * will run for. This is only considered when using data estimates to calculate
+ * execution limits.
*/
public long RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS =
DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS;
- /** The maximum amount of time we will let a user-initiated data transfer job run for. */
- public long RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = DEFAULT_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS;
+ /**
+ * Whether to use data estimates to determine execution limits for execution limits.
+ */
+ public boolean RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS =
+ DEFAULT_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS;
/**
* Whether to persist jobs in split files (by UID). If false, all persisted jobs will be
@@ -879,7 +882,7 @@ public class JobSchedulerService extends com.android.server.SystemService
KEY_RUNTIME_MIN_UI_GUARANTEE_MS,
KEY_RUNTIME_UI_LIMIT_MS,
KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
- KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS);
+ KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS);
// Make sure min runtime for regular jobs is at least 10 minutes.
RUNTIME_MIN_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS,
@@ -913,15 +916,10 @@ public class JobSchedulerService extends com.android.server.SystemService
properties.getLong(
KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS));
- // User-initiated requires RUN_USER_INITIATED_JOBS permission, so the upper limit will
- // be higher than other jobs.
- // Max limit should be the min guarantee and the max of other user-initiated jobs.
- RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = Math.max(
- RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
- Math.max(RUNTIME_UI_LIMIT_MS,
- properties.getLong(
- KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
- DEFAULT_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS)));
+
+ RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = properties.getBoolean(
+ KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS,
+ DEFAULT_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS);
}
private boolean updateTareSettingsLocked(@EconomyManager.EnabledMode int enabledMode) {
@@ -975,8 +973,8 @@ public class JobSchedulerService extends com.android.server.SystemService
RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR).println();
pw.print(KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS).println();
- pw.print(KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
- RUNTIME_UI_DATA_TRANSFER_LIMIT_MS).println();
+ pw.print(KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS,
+ RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS).println();
pw.print(KEY_PERSIST_IN_SPLIT_FILES, PERSIST_IN_SPLIT_FILES).println();
pw.print(KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS, MAX_NUM_PERSISTED_JOB_WORK_ITEMS)
@@ -3306,20 +3304,24 @@ public class JobSchedulerService extends com.android.server.SystemService
if (job.shouldTreatAsUserInitiatedJob()
&& checkRunUserInitiatedJobsPermission(
job.getSourceUid(), job.getSourcePackageName())) {
- if (job.getJob().getRequiredNetwork() != null) { // UI+DT
- final long estimatedTransferTimeMs =
- mConnectivityController.getEstimatedTransferTimeMs(job);
- if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
- return mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS;
+ if (job.getJob().getRequiredNetwork() != null) {
+ // User-initiated data transfers.
+ if (mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS) {
+ final long estimatedTransferTimeMs =
+ mConnectivityController.getEstimatedTransferTimeMs(job);
+ if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
+ return mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS;
+ }
+ // Try to give the job at least as much time as we think the transfer
+ // will take, but cap it at the maximum limit.
+ final long factoredTransferTimeMs = (long) (estimatedTransferTimeMs
+ * mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
+ return Math.min(mConstants.RUNTIME_UI_LIMIT_MS,
+ Math.max(factoredTransferTimeMs,
+ mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS));
}
- // Try to give the job at least as much time as we think the transfer will take,
- // but cap it at the maximum limit
- final long factoredTransferTimeMs = (long) (estimatedTransferTimeMs
- * mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
- return Math.min(mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
- Math.max(factoredTransferTimeMs,
- mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS
- ));
+ return Math.max(mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS);
}
return mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
} else if (job.shouldTreatAsExpeditedJob()) {
@@ -3336,13 +3338,9 @@ public class JobSchedulerService extends com.android.server.SystemService
/** Returns the maximum amount of time this job could run for. */
public long getMaxJobExecutionTimeMs(JobStatus job) {
synchronized (mLock) {
- final boolean allowLongerJob = job.shouldTreatAsUserInitiatedJob()
+ if (job.shouldTreatAsUserInitiatedJob()
&& checkRunUserInitiatedJobsPermission(
- job.getSourceUid(), job.getSourcePackageName());
- if (job.getJob().getRequiredNetwork() != null && allowLongerJob) { // UI+DT
- return mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS;
- }
- if (allowLongerJob) { // UI with LRJ permission
+ job.getSourceUid(), job.getSourcePackageName())) {
return mConstants.RUNTIME_UI_LIMIT_MS;
}
if (job.shouldTreatAsUserInitiatedJob()) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 0dcb0b2456d6..a96a4ef951ea 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -883,6 +883,15 @@ public final class JobStore {
if (job.isRequireStorageNotLow()) {
out.attribute(null, "storage-not-low", Boolean.toString(true));
}
+ if (job.isPreferBatteryNotLow()) {
+ out.attributeBoolean(null, "prefer-battery-not-low", true);
+ }
+ if (job.isPreferCharging()) {
+ out.attributeBoolean(null, "prefer-charging", true);
+ }
+ if (job.isPreferDeviceIdle()) {
+ out.attributeBoolean(null, "prefer-idle", true);
+ }
out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS);
}
@@ -1538,6 +1547,13 @@ public final class JobStore {
if (val != null) {
jobBuilder.setRequiresStorageNotLow(true);
}
+
+ jobBuilder.setPrefersBatteryNotLow(
+ parser.getAttributeBoolean(null, "prefer-battery-not-low", false));
+ jobBuilder.setPrefersCharging(
+ parser.getAttributeBoolean(null, "prefer-charging", false));
+ jobBuilder.setPrefersDeviceIdle(
+ parser.getAttributeBoolean(null, "prefer-idle", false));
}
/**
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index e55bda7fab02..3859d89c22cd 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -1147,10 +1147,9 @@ public final class ConnectivityController extends RestrictingController implemen
final boolean changed = jobStatus.setConnectivityConstraintSatisfied(nowElapsed, satisfied);
+ jobStatus.setHasAccessToUnmetered(satisfied && capabilities != null
+ && capabilities.hasCapability(NET_CAPABILITY_NOT_METERED));
if (jobStatus.getPreferUnmetered()) {
- jobStatus.setHasAccessToUnmetered(satisfied && capabilities != null
- && capabilities.hasCapability(NET_CAPABILITY_NOT_METERED));
-
jobStatus.setFlexibilityConstraintSatisfied(nowElapsed,
mFlexibilityController.isFlexibilitySatisfiedLocked(jobStatus));
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 620c48d2c343..234a93c8d168 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -239,14 +239,14 @@ public final class FlexibilityController extends StateController {
return !mFlexibilityEnabled
|| mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP
|| mService.isCurrentlyRunningLocked(js)
- || getNumSatisfiedRequiredConstraintsLocked(js)
+ || getNumSatisfiedFlexibleConstraintsLocked(js)
>= js.getNumRequiredFlexibleConstraints();
}
@VisibleForTesting
@GuardedBy("mLock")
- int getNumSatisfiedRequiredConstraintsLocked(JobStatus js) {
- return Integer.bitCount(mSatisfiedFlexibleConstraints)
+ int getNumSatisfiedFlexibleConstraintsLocked(JobStatus js) {
+ return Integer.bitCount(mSatisfiedFlexibleConstraints & js.getPreferredConstraintFlags())
+ (js.getHasAccessToUnmetered() ? 1 : 0);
}
@@ -651,7 +651,7 @@ public final class FlexibilityController extends StateController {
static final String KEY_RESCHEDULED_JOB_DEADLINE_MS =
FC_CONFIG_PREFIX + "rescheduled_job_deadline_ms";
- private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false;
+ private static final boolean DEFAULT_FLEXIBILITY_ENABLED = true;
@VisibleForTesting
static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
@VisibleForTesting
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 0cc775870f88..17dde9098926 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -16,7 +16,6 @@
package com.android.server.job.controllers;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
@@ -25,8 +24,6 @@ import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-import static com.android.server.job.controllers.FlexibilityController.NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
-import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
@@ -115,12 +112,11 @@ public final class JobStatus {
static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint
static final int CONSTRAINT_PREFETCH = 1 << 23;
static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
- static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
+ static final int CONSTRAINT_FLEXIBLE = 1 << 21;
private static final int IMPLICIT_CONSTRAINTS = 0
| CONSTRAINT_BACKGROUND_NOT_RESTRICTED
| CONSTRAINT_DEVICE_NOT_DOZING
- | CONSTRAINT_FLEXIBLE
| CONSTRAINT_TARE_WEALTH
| CONSTRAINT_WITHIN_QUOTA;
@@ -298,6 +294,7 @@ public final class JobStatus {
// Constraints.
final int requiredConstraints;
+ private final int mPreferredConstraints;
private final int mRequiredConstraintsOfInterest;
int satisfiedConstraints = 0;
private int mSatisfiedConstraintsOfInterest = 0;
@@ -618,24 +615,26 @@ public final class JobStatus {
}
mHasExemptedMediaUrisOnly = exemptedMediaUrisOnly;
- mPreferUnmetered = job.getRequiredNetwork() != null
- && !job.getRequiredNetwork().hasCapability(NET_CAPABILITY_NOT_METERED);
+ mPreferredConstraints = job.getPreferredConstraintFlags();
+
+ // Exposing a preferredNetworkRequest API requires that we make sure that the preferred
+ // NetworkRequest is a subset of the required NetworkRequest. We currently don't have the
+ // code to ensure that, so disable this part for now.
+ // TODO(236261941): look into enabling flexible network constraint requests
+ mPreferUnmetered = false;
+ // && job.getRequiredNetwork() != null
+ // && !job.getRequiredNetwork().hasCapability(NET_CAPABILITY_NOT_METERED);
- final boolean lacksSomeFlexibleConstraints =
- ((~requiredConstraints) & SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS) != 0
- || mPreferUnmetered;
final boolean satisfiesMinWindowException =
(latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis)
>= MIN_WINDOW_FOR_FLEXIBILITY_MS;
// The first time a job is rescheduled it will not be subject to flexible constraints.
// Otherwise, every consecutive reschedule increases a jobs' flexibility deadline.
- if (!isRequestedExpeditedJob() && !job.isUserInitiated()
+ if (mPreferredConstraints != 0 && !isRequestedExpeditedJob() && !job.isUserInitiated()
&& satisfiesMinWindowException
- && (numFailures + numSystemStops) != 1
- && lacksSomeFlexibleConstraints) {
- mNumRequiredFlexibleConstraints =
- NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0);
+ && (numFailures + numSystemStops) != 1) {
+ mNumRequiredFlexibleConstraints = Integer.bitCount(mPreferredConstraints);
requiredConstraints |= CONSTRAINT_FLEXIBLE;
} else {
mNumRequiredFlexibleConstraints = 0;
@@ -1142,6 +1141,10 @@ public final class JobStatus {
mInternalFlags |= flags;
}
+ int getPreferredConstraintFlags() {
+ return mPreferredConstraints;
+ }
+
public int getSatisfiedConstraintFlags() {
return satisfiedConstraints;
}
@@ -2501,6 +2504,9 @@ public final class JobStatus {
pw.print("Required constraints:");
dumpConstraints(pw, requiredConstraints);
pw.println();
+ pw.print("Preferred constraints:");
+ dumpConstraints(pw, mPreferredConstraints);
+ pw.println();
pw.print("Dynamic constraints:");
dumpConstraints(pw, mDynamicConstraints);
pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index c3118ff96413..ab0a8adb5daf 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -268,6 +268,10 @@ public class AppStandbyController
@GuardedBy("mActiveAdminApps")
private final SparseArray<Set<String>> mActiveAdminApps = new SparseArray<>();
+ /** List of admin protected packages. Can contain {@link android.os.UserHandle#USER_ALL}. */
+ @GuardedBy("mAdminProtectedPackages")
+ private final SparseArray<Set<String>> mAdminProtectedPackages = new SparseArray<>();
+
/**
* Set of system apps that are headless (don't have any "front door" activities, enabled or
* disabled). Presence in this map indicates that the app is a headless system app.
@@ -1380,6 +1384,9 @@ public class AppStandbyController
synchronized (mActiveAdminApps) {
mActiveAdminApps.remove(userId);
}
+ synchronized (mAdminProtectedPackages) {
+ mAdminProtectedPackages.remove(userId);
+ }
}
}
@@ -1469,6 +1476,10 @@ public class AppStandbyController
return STANDBY_BUCKET_EXEMPTED;
}
+ if (isAdminProtectedPackages(packageName, userId)) {
+ return STANDBY_BUCKET_EXEMPTED;
+ }
+
if (isActiveNetworkScorer(packageName)) {
return STANDBY_BUCKET_EXEMPTED;
}
@@ -1948,6 +1959,17 @@ public class AppStandbyController
}
}
+ private boolean isAdminProtectedPackages(String packageName, int userId) {
+ synchronized (mAdminProtectedPackages) {
+ if (mAdminProtectedPackages.contains(UserHandle.USER_ALL)
+ && mAdminProtectedPackages.get(UserHandle.USER_ALL).contains(packageName)) {
+ return true;
+ }
+ return mAdminProtectedPackages.contains(userId)
+ && mAdminProtectedPackages.get(userId).contains(packageName);
+ }
+ }
+
@Override
public void addActiveDeviceAdmin(String adminPkg, int userId) {
synchronized (mActiveAdminApps) {
@@ -1972,6 +1994,17 @@ public class AppStandbyController
}
@Override
+ public void setAdminProtectedPackages(Set<String> packageNames, int userId) {
+ synchronized (mAdminProtectedPackages) {
+ if (packageNames == null || packageNames.isEmpty()) {
+ mAdminProtectedPackages.remove(userId);
+ } else {
+ mAdminProtectedPackages.put(userId, packageNames);
+ }
+ }
+ }
+
+ @Override
public void onAdminDataAvailable() {
mAdminDataAvailableLatch.countDown();
}
@@ -1993,6 +2026,13 @@ public class AppStandbyController
}
}
+ @VisibleForTesting
+ Set<String> getAdminProtectedPackagesForTest(int userId) {
+ synchronized (mAdminProtectedPackages) {
+ return mAdminProtectedPackages.get(userId);
+ }
+ }
+
/**
* Returns {@code true} if the supplied package is the device provisioning app. Otherwise,
* returns {@code false}.
diff --git a/core/api/current.txt b/core/api/current.txt
index 771d56cd1987..2acbc35a387c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -27348,9 +27348,13 @@ package android.media.tv {
field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2
field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0
field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1; // 0x1
+ field public static final long TV_MESSAGE_GROUP_ID_NONE = -1L; // 0xffffffffffffffffL
+ field public static final String TV_MESSAGE_KEY_GROUP_ID = "android.media.tv.TvInputManager.group_id";
field public static final String TV_MESSAGE_KEY_RAW_DATA = "android.media.tv.TvInputManager.raw_data";
field public static final String TV_MESSAGE_KEY_STREAM_ID = "android.media.tv.TvInputManager.stream_id";
field public static final String TV_MESSAGE_KEY_SUBTYPE = "android.media.tv.TvInputManager.subtype";
+ field public static final String TV_MESSAGE_SUBTYPE_CC_608E = "CTA 608-E";
+ field public static final String TV_MESSAGE_SUBTYPE_WATERMARKING_A335 = "ATSC A/335";
field public static final int TV_MESSAGE_TYPE_CLOSED_CAPTION = 2; // 0x2
field public static final int TV_MESSAGE_TYPE_OTHER = 1000; // 0x3e8
field public static final int TV_MESSAGE_TYPE_WATERMARK = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 6bfaa352f3f7..658a25dd1faa 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1411,8 +1411,12 @@ package android.hardware.biometrics {
package android.hardware.camera2 {
public final class CameraManager {
+ method @NonNull public android.hardware.camera2.CameraCharacteristics getCameraCharacteristics(@NonNull String, boolean) throws android.hardware.camera2.CameraAccessException;
method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException;
+ method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, boolean, @Nullable android.os.Handler, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
+ method public static boolean shouldOverrideToPortrait(@Nullable android.content.pm.PackageManager, @Nullable String);
+ field public static final String LANDSCAPE_TO_PORTRAIT_PROP = "camera.enable_landscape_to_portrait";
field public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L; // 0xef10e60L
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 90427cb51c26..b96f8c94aaa3 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -994,6 +994,16 @@ public abstract class ActivityManagerInternal {
*/
public abstract void logFgsApiEnd(int apiType, int uid, int pid);
+ /**
+ * Temporarily allow foreground service started by an uid to have while-in-use permission
+ * for durationMs.
+ *
+ * @param uid The UID of the app that starts the foreground service.
+ * @param durationMs elapsedRealTime duration in milliseconds.
+ * @hide
+ */
+ public abstract void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs);
+
/**
* The list of the events about the {@link android.media.projection.IMediaProjection} itself.
*
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6bbbfe1ef4b0..5be43fc8e8f7 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -15840,9 +15840,8 @@ public class DevicePolicyManager {
* Called by a device owner or a profile owner or holder of the permission
* {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APPS_CONTROL} to disable user
* control over apps. User will not be able to clear app data or force-stop packages. When
- * called by a device owner, applies to all users on the device. Starting from Android 13,
- * packages with user control disabled are exempted from being put in the "restricted" App
- * Standby Bucket.
+ * called by a device owner, applies to all users on the device. Packages with user control
+ * disabled are exempted from App Standby Buckets.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
* caller is not a device admin.
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index e6b306955ef0..696873f7e5e4 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -121,6 +121,7 @@ public final class CameraManager {
* System property for allowing the above
* @hide
*/
+ @TestApi
public static final String LANDSCAPE_TO_PORTRAIT_PROP =
"camera.enable_landscape_to_portrait";
@@ -622,6 +623,16 @@ public final class CameraManager {
@NonNull
public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId)
throws CameraAccessException {
+ return getCameraCharacteristics(cameraId, shouldOverrideToPortrait(mContext));
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId,
+ boolean overrideToPortrait) throws CameraAccessException {
CameraCharacteristics characteristics = null;
if (CameraManagerGlobal.sCameraServiceDisabled) {
throw new IllegalArgumentException("No cameras available on device");
@@ -635,7 +646,6 @@ public final class CameraManager {
try {
Size displaySize = getDisplaySize();
- boolean overrideToPortrait = shouldOverrideToPortrait(mContext);
CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId,
mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait);
try {
@@ -727,7 +737,7 @@ public final class CameraManager {
*/
private CameraDevice openCameraDeviceUserAsync(String cameraId,
CameraDevice.StateCallback callback, Executor executor, final int uid,
- final int oomScoreOffset) throws CameraAccessException {
+ final int oomScoreOffset, boolean overrideToPortrait) throws CameraAccessException {
CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
CameraDevice device = null;
Map<String, CameraCharacteristics> physicalIdsToChars =
@@ -755,7 +765,6 @@ public final class CameraManager {
"Camera service is currently unavailable");
}
- boolean overrideToPortrait = shouldOverrideToPortrait(mContext);
cameraUser = cameraService.connectDevice(callbacks, cameraId,
mContext.getOpPackageName(), mContext.getAttributionTag(), uid,
oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion,
@@ -891,6 +900,43 @@ public final class CameraManager {
}
/**
+ * Open a connection to a camera with the given ID. Also specify overrideToPortrait for testing.
+ *
+ * @param cameraId
+ * The unique identifier of the camera device to open
+ * @param handler
+ * The handler on which the callback should be invoked, or
+ * {@code null} to use the current thread's {@link android.os.Looper looper}.
+ * @param callback
+ * The callback which is invoked once the camera is opened
+ * @param overrideToPortrait
+ * Whether to apply the landscape to portrait override, using rotate and crop.
+ *
+ * @throws CameraAccessException if the camera is disabled by device policy,
+ * has been disconnected, or is being used by a higher-priority camera API client.
+ *
+ * @throws IllegalArgumentException if cameraId, the callback or the executor was null,
+ * or the cameraId does not match any currently or previously available
+ * camera device.
+ *
+ * @throws SecurityException if the application does not have permission to
+ * access the camera
+ *
+ * @see #getCameraIdList
+ * @see android.app.admin.DevicePolicyManager#setCameraDisabled
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.CAMERA)
+ public void openCamera(@NonNull String cameraId, boolean overrideToPortrait,
+ @Nullable Handler handler,
+ @NonNull final CameraDevice.StateCallback callback) throws CameraAccessException {
+ openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler),
+ USE_CALLING_UID, /*oomScoreOffset*/0, overrideToPortrait);
+ }
+
+ /**
* Open a connection to a camera with the given ID.
*
* <p>The behavior of this method matches that of
@@ -994,7 +1040,8 @@ public final class CameraManager {
throw new IllegalArgumentException(
"oomScoreOffset < 0, cannot increase priority of camera client");
}
- openCameraForUid(cameraId, callback, executor, USE_CALLING_UID, oomScoreOffset);
+ openCameraForUid(cameraId, callback, executor, USE_CALLING_UID, oomScoreOffset,
+ shouldOverrideToPortrait(mContext));
}
/**
@@ -1016,7 +1063,8 @@ public final class CameraManager {
*/
public void openCameraForUid(@NonNull String cameraId,
@NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
- int clientUid, int oomScoreOffset) throws CameraAccessException {
+ int clientUid, int oomScoreOffset, boolean overrideToPortrait)
+ throws CameraAccessException {
if (cameraId == null) {
throw new IllegalArgumentException("cameraId was null");
@@ -1027,7 +1075,8 @@ public final class CameraManager {
throw new IllegalArgumentException("No cameras available on device");
}
- openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset);
+ openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset,
+ overrideToPortrait);
}
/**
@@ -1048,7 +1097,8 @@ public final class CameraManager {
public void openCameraForUid(@NonNull String cameraId,
@NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
int clientUid) throws CameraAccessException {
- openCameraForUid(cameraId, callback, executor, clientUid, /*oomScoreOffset*/0);
+ openCameraForUid(cameraId, callback, executor, clientUid, /*oomScoreOffset*/0,
+ shouldOverrideToPortrait(mContext));
}
/**
@@ -1191,17 +1241,32 @@ public final class CameraManager {
* @hide
*/
public static boolean shouldOverrideToPortrait(@Nullable Context context) {
+ PackageManager packageManager = null;
+ String packageName = null;
+
+ if (context != null) {
+ packageManager = context.getPackageManager();
+ packageName = context.getOpPackageName();
+ }
+
+ return shouldOverrideToPortrait(packageManager, packageName);
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public static boolean shouldOverrideToPortrait(@Nullable PackageManager packageManager,
+ @Nullable String packageName) {
if (!CameraManagerGlobal.sLandscapeToPortrait) {
return false;
}
- if (context != null) {
- PackageManager packageManager = context.getPackageManager();
-
+ if (packageManager != null && packageName != null) {
try {
return packageManager.getProperty(
PackageManager.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT,
- context.getOpPackageName()).getBoolean();
+ packageName).getBoolean();
} catch (PackageManager.NameNotFoundException e) {
// No such property
}
diff --git a/core/java/android/hardware/radio/IRadioService.aidl b/core/java/android/hardware/radio/IRadioService.aidl
index c7131a7fe9c9..9349cf7d0c32 100644
--- a/core/java/android/hardware/radio/IRadioService.aidl
+++ b/core/java/android/hardware/radio/IRadioService.aidl
@@ -31,7 +31,7 @@ interface IRadioService {
List<RadioManager.ModuleProperties> listModules();
ITuner openTuner(int moduleId, in RadioManager.BandConfig bandConfig, boolean withAudio,
- in ITunerCallback callback, int targetSdkVersion);
+ in ITunerCallback callback);
ICloseHandle addAnnouncementListener(in int[] enabledTypes,
in IAnnouncementListener listener);
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index f072e3b43b4a..8c6083ce49b6 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -1796,7 +1796,7 @@ public class RadioManager {
ITuner tuner;
TunerCallbackAdapter halCallback = new TunerCallbackAdapter(callback, handler);
try {
- tuner = mService.openTuner(moduleId, config, withAudio, halCallback, mTargetSdkVersion);
+ tuner = mService.openTuner(moduleId, config, withAudio, halCallback);
} catch (RemoteException | IllegalArgumentException | IllegalStateException ex) {
Log.e(TAG, "Failed to open tuner", ex);
return null;
@@ -1873,7 +1873,6 @@ public class RadioManager {
@NonNull private final Context mContext;
@NonNull private final IRadioService mService;
- private final int mTargetSdkVersion;
/**
* @hide
@@ -1890,6 +1889,5 @@ public class RadioManager {
public RadioManager(Context context, IRadioService service) {
mContext = context;
mService = service;
- mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
}
}
diff --git a/core/java/android/window/TaskConstants.java b/core/java/android/window/TaskConstants.java
index c40384027adf..3a04198a3add 100644
--- a/core/java/android/window/TaskConstants.java
+++ b/core/java/android/window/TaskConstants.java
@@ -80,14 +80,6 @@ public class TaskConstants {
public static final int TASK_CHILD_LAYER_TASK_OVERLAY = 5 * TASK_CHILD_LAYER_REGION_SIZE;
/**
- * Legacy machanism to force an activity to the top of the task (i.e. for work profile
- * comfirmation).
- * @hide
- */
- public static final int TASK_CHILD_LAYER_TASK_OVERLAY_ACTIVITIES =
- 6 * TASK_CHILD_LAYER_REGION_SIZE;
-
- /**
* Z-orders of task child layers other than activities, task fragments and layers interleaved
* with them, e.g. IME windows. [-10000, 10000) is reserved for these layers.
* @hide
@@ -99,8 +91,7 @@ public class TaskConstants {
TASK_CHILD_LAYER_LETTERBOX_EDUCATION,
TASK_CHILD_LAYER_WINDOW_DECORATIONS,
TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY,
- TASK_CHILD_LAYER_TASK_OVERLAY,
- TASK_CHILD_LAYER_TASK_OVERLAY_ACTIVITIES
+ TASK_CHILD_LAYER_TASK_OVERLAY
})
public @interface TaskChildLayer {}
}
diff --git a/core/tests/BroadcastRadioTests/AndroidManifest.xml b/core/tests/BroadcastRadioTests/AndroidManifest.xml
index 8f655efe4f07..fef3d16a183b 100644
--- a/core/tests/BroadcastRadioTests/AndroidManifest.xml
+++ b/core/tests/BroadcastRadioTests/AndroidManifest.xml
@@ -18,6 +18,8 @@
package="com.android.frameworks.broadcastradiotests">
<uses-permission android:name="android.permission.ACCESS_BROADCAST_RADIO" />
+ <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+ <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
index 75f8c954be5e..7c3d2f2138a4 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
@@ -580,7 +580,7 @@ public final class ProgramListTest {
doAnswer(invocation -> {
mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
return mTunerMock;
- }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any(), anyInt());
+ }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
mRadioTuner = radioManager.openTuner(/* moduleId= */ 0, band,
/* withAudio= */ true, mTunerCallbackMock, /* handler= */ null);
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
index ce3e019520c8..b9f4c3fa0a77 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
@@ -30,7 +30,6 @@ import static org.mockito.Mockito.when;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.os.Build;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.ArrayMap;
@@ -50,8 +49,6 @@ import java.util.Set;
@RunWith(MockitoJUnitRunner.class)
public final class RadioManagerTest {
- private static final int TEST_TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
-
private static final int REGION = RadioManager.REGION_ITU_2;
private static final int FM_LOWER_LIMIT = 87500;
private static final int FM_UPPER_LIMIT = 108000;
@@ -1043,14 +1040,13 @@ public final class RadioManagerTest {
mRadioManager.openTuner(moduleId, FM_BAND_CONFIG, withAudio, mCallbackMock,
/* handler= */ null);
- verify(mRadioServiceMock).openTuner(eq(moduleId), eq(FM_BAND_CONFIG), eq(withAudio), any(),
- anyInt());
+ verify(mRadioServiceMock).openTuner(eq(moduleId), eq(FM_BAND_CONFIG), eq(withAudio), any());
}
@Test
public void openTuner_whenServiceDied_returnsNull() throws Exception {
createRadioManager();
- when(mRadioServiceMock.openTuner(anyInt(), any(), anyBoolean(), any(), anyInt()))
+ when(mRadioServiceMock.openTuner(anyInt(), any(), anyBoolean(), any()))
.thenThrow(new RemoteException());
RadioTuner nullTuner = mRadioManager.openTuner(/* moduleId= */ 0, FM_BAND_CONFIG,
@@ -1166,7 +1162,6 @@ public final class RadioManagerTest {
}
private void createRadioManager() throws RemoteException {
- mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION;
when(mContextMock.getApplicationInfo()).thenReturn(mApplicationInfo);
when(mRadioServiceMock.listModules()).thenReturn(Arrays.asList(AMFM_PROPERTIES));
when(mRadioServiceMock.addAnnouncementListener(any(), any())).thenReturn(mCloseHandleMock);
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
index 8b257e832e43..c7b82b1caca6 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
@@ -86,7 +86,7 @@ public final class TunerAdapterTest {
doAnswer(invocation -> {
mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
return mTunerMock;
- }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any(), anyInt());
+ }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
doAnswer(invocation -> {
ProgramSelector program = (ProgramSelector) invocation.getArguments()[0];
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
index 16c1499a2775..da51ba45174c 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
@@ -24,7 +24,6 @@ import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -35,7 +34,6 @@ import android.hardware.radio.ICloseHandle;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
-import android.os.Build;
import android.os.IBinder;
import android.os.ServiceManager;
@@ -58,7 +56,6 @@ public final class IRadioServiceAidlImplTest extends ExtendedRadioMockitoTestCas
"android.hardware.broadcastradio.IBroadcastRadio/amfm";
private static final String DAB_SERVICE_NAME =
"android.hardware.broadcastradio.IBroadcastRadio/dab";
- private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
private IRadioServiceAidlImpl mAidlImpl;
@@ -86,7 +83,7 @@ public final class IRadioServiceAidlImplTest extends ExtendedRadioMockitoTestCas
doNothing().when(mServiceMock).enforcePolicyAccess();
when(mHalMock.listModules()).thenReturn(List.of(mModuleMock));
- when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any(), eq(TARGET_SDK_VERSION)))
+ when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any()))
.thenReturn(mTunerMock);
when(mHalMock.addAnnouncementListener(any(), any())).thenReturn(mICloseHandle);
@@ -118,7 +115,7 @@ public final class IRadioServiceAidlImplTest extends ExtendedRadioMockitoTestCas
@Test
public void openTuner_forAidlImpl() throws Exception {
ITuner tuner = mAidlImpl.openTuner(/* moduleId= */ 0, mBandConfigMock,
- /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION);
+ /* withAudio= */ true, mTunerCallbackMock);
assertWithMessage("Tuner opened in AIDL HAL")
.that(tuner).isEqualTo(mTunerMock);
@@ -128,7 +125,7 @@ public final class IRadioServiceAidlImplTest extends ExtendedRadioMockitoTestCas
public void openTuner_withNullCallbackForAidlImpl_fails() throws Exception {
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
() -> mAidlImpl.openTuner(/* moduleId= */ 0, mBandConfigMock,
- /* withAudio= */ true, /* callback= */ null, TARGET_SDK_VERSION));
+ /* withAudio= */ true, /* callback= */ null));
assertWithMessage("Exception for opening tuner with null callback")
.that(thrown).hasMessageThat().contains("Callback must not be null");
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
index 164c9aff127a..20bc8d404d76 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
@@ -33,7 +33,6 @@ import android.hardware.radio.ICloseHandle;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
-import android.os.Build;
import org.junit.Before;
import org.junit.Test;
@@ -51,7 +50,6 @@ public final class IRadioServiceHidlImplTest {
private static final int HAL1_MODULE_ID = 0;
private static final int[] ENABLE_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
- private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
private IRadioServiceHidlImpl mHidlImpl;
@@ -106,7 +104,7 @@ public final class IRadioServiceHidlImplTest {
@Test
public void openTuner_withHal1ModuleId_forHidlImpl() throws Exception {
ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID, mBandConfigMock,
- /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION);
+ /* withAudio= */ true, mTunerCallbackMock);
assertWithMessage("Tuner opened in HAL 1")
.that(tuner).isEqualTo(mHal1TunerMock);
@@ -115,7 +113,7 @@ public final class IRadioServiceHidlImplTest {
@Test
public void openTuner_withHal2ModuleId_forHidlImpl() throws Exception {
ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID + 1, mBandConfigMock,
- /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION);
+ /* withAudio= */ true, mTunerCallbackMock);
assertWithMessage("Tuner opened in HAL 2")
.that(tuner).isEqualTo(mHal2TunerMock);
@@ -125,7 +123,7 @@ public final class IRadioServiceHidlImplTest {
public void openTuner_withNullCallbackForHidlImpl_fails() throws Exception {
NullPointerException thrown = assertThrows(NullPointerException.class,
() -> mHidlImpl.openTuner(/* moduleId= */ 0, mBandConfigMock,
- /* withAudio= */ true, /* callback= */ null, TARGET_SDK_VERSION));
+ /* withAudio= */ true, /* callback= */ null));
assertWithMessage("Exception for opening tuner with null callback")
.that(thrown).hasMessageThat().contains("Callback must not be null");
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java
index 161ac2dc6aa0..3e9e9922431b 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java
@@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.compat.CompatChanges;
import android.os.Binder;
import android.os.UserHandle;
@@ -46,8 +47,8 @@ public final class RadioServiceUserControllerTest extends ExtendedRadioMockitoTe
@Override
protected void initializeSession(StaticMockitoSessionBuilder builder) {
- builder.spyStatic(ActivityManager.class)
- .spyStatic(Binder.class);
+ builder.spyStatic(ActivityManager.class).spyStatic(Binder.class)
+ .spyStatic(CompatChanges.class);
}
@Before
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
index 98103f6eddd3..22f3bd4abe11 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
@@ -36,7 +36,6 @@ import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioTuner;
-import android.os.Build;
import android.os.IBinder;
import android.os.IServiceCallback;
import android.os.RemoteException;
@@ -55,8 +54,6 @@ import java.util.Arrays;
public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTestCase {
- private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
-
private static final int FM_RADIO_MODULE_ID = 0;
private static final int DAB_RADIO_MODULE_ID = 1;
private static final ArrayList<String> SERVICE_LIST =
@@ -140,8 +137,7 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes
createBroadcastRadioService();
ITuner session = mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
- /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock,
- TARGET_SDK_VERSION);
+ /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
assertWithMessage("Session opened in FM radio module")
.that(session).isEqualTo(mFmTunerSessionMock);
@@ -152,8 +148,7 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes
createBroadcastRadioService();
ITuner session = mBroadcastRadioService.openSession(DAB_RADIO_MODULE_ID + 1,
- /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock,
- TARGET_SDK_VERSION);
+ /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
assertWithMessage("Session opened with id not found").that(session).isNull();
}
@@ -165,8 +160,7 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes
IllegalStateException thrown = assertThrows(IllegalStateException.class,
() -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
- /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock,
- TARGET_SDK_VERSION));
+ /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock));
assertWithMessage("Exception for opening session by non-current user")
.that(thrown).hasMessageThat().contains("Cannot open session for non-current user");
@@ -178,8 +172,7 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
() -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
- /* legacyConfig= */ null, /* withAudio= */ false, mTunerCallbackMock,
- TARGET_SDK_VERSION));
+ /* legacyConfig= */ null, /* withAudio= */ false, mTunerCallbackMock));
assertWithMessage("Exception for opening session without audio")
.that(thrown).hasMessageThat().contains("not supported");
@@ -247,7 +240,6 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes
return null;
}).when(mFmBinderMock).linkToDeath(any(), anyInt());
- when(mFmRadioModuleMock.openSession(eq(mTunerCallbackMock), eq(TARGET_SDK_VERSION)))
- .thenReturn(mFmTunerSessionMock);
+ when(mFmRadioModuleMock.openSession(mTunerCallbackMock)).thenReturn(mFmTunerSessionMock);
}
}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index 5d0e07613a98..ba0579171c43 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -16,6 +16,9 @@
package com.android.server.broadcastradio.aidl;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import android.app.compat.CompatChanges;
import android.hardware.broadcastradio.AmFmBandRange;
import android.hardware.broadcastradio.AmFmRegionConfig;
import android.hardware.broadcastradio.DabTableEntry;
@@ -29,17 +32,23 @@ import android.hardware.radio.Announcement;
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
-import android.os.Build;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
import com.google.common.truth.Expect;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.util.Map;
import java.util.Set;
-public final class ConversionUtilsTest {
+public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase {
+
+ private static final int U_APP_UID = 1001;
+ private static final int T_APP_UID = 1002;
private static final int FM_LOWER_LIMIT = 87_500;
private static final int FM_UPPER_LIMIT = 108_000;
@@ -118,16 +127,29 @@ public final class ConversionUtilsTest {
@Rule
public final Expect expect = Expect.create();
+ @Override
+ protected void initializeSession(StaticMockitoSessionBuilder builder) {
+ builder.spyStatic(CompatChanges.class);
+ }
+
+ @Before
+ public void setUp() {
+ doReturn(true).when(() -> CompatChanges.isChangeEnabled(
+ ConversionUtils.RADIO_U_VERSION_REQUIRED, U_APP_UID));
+ doReturn(false).when(() -> CompatChanges.isChangeEnabled(
+ ConversionUtils.RADIO_U_VERSION_REQUIRED, T_APP_UID));
+ }
+
@Test
public void isAtLeastU_withTSdkVersion_returnsFalse() {
expect.withMessage("Target SDK version of T")
- .that(ConversionUtils.isAtLeastU(Build.VERSION_CODES.TIRAMISU)).isFalse();
+ .that(ConversionUtils.isAtLeastU(T_APP_UID)).isFalse();
}
@Test
public void isAtLeastU_withCurrentSdkVersion_returnsTrue() {
expect.withMessage("Target SDK version of U")
- .that(ConversionUtils.isAtLeastU(Build.VERSION_CODES.CUR_DEVELOPMENT)).isTrue();
+ .that(ConversionUtils.isAtLeastU(U_APP_UID)).isTrue();
}
@Test
@@ -372,14 +394,14 @@ public final class ConversionUtilsTest {
public void programSelectorMeetsSdkVersionRequirement_withLowerVersionId_returnsFalse() {
expect.withMessage("Selector %s without required SDK version", TEST_DAB_SELECTOR)
.that(ConversionUtils.programSelectorMeetsSdkVersionRequirement(TEST_DAB_SELECTOR,
- Build.VERSION_CODES.TIRAMISU)).isFalse();
+ T_APP_UID)).isFalse();
}
@Test
public void programSelectorMeetsSdkVersionRequirement_withRequiredVersionId_returnsTrue() {
expect.withMessage("Selector %s with required SDK version", TEST_FM_SELECTOR)
.that(ConversionUtils.programSelectorMeetsSdkVersionRequirement(TEST_FM_SELECTOR,
- Build.VERSION_CODES.TIRAMISU)).isTrue();
+ T_APP_UID)).isTrue();
}
@Test
@@ -389,7 +411,7 @@ public final class ConversionUtilsTest {
expect.withMessage("Program info %s without required SDK version", dabProgramInfo)
.that(ConversionUtils.programInfoMeetsSdkVersionRequirement(dabProgramInfo,
- Build.VERSION_CODES.TIRAMISU)).isFalse();
+ T_APP_UID)).isFalse();
}
@Test
@@ -399,7 +421,7 @@ public final class ConversionUtilsTest {
expect.withMessage("Program info %s with required SDK version", fmProgramInfo)
.that(ConversionUtils.programInfoMeetsSdkVersionRequirement(fmProgramInfo,
- Build.VERSION_CODES.TIRAMISU)).isTrue();
+ T_APP_UID)).isTrue();
}
@Test
@@ -413,7 +435,7 @@ public final class ConversionUtilsTest {
Set.of(TEST_DAB_SID_EXT_ID, TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID));
ProgramList.Chunk convertedChunk = ConversionUtils.convertChunkToTargetSdkVersion(chunk,
- Build.VERSION_CODES.TIRAMISU);
+ T_APP_UID);
expect.withMessage(
"Purged state of the converted program list chunk with lower SDK version")
@@ -441,7 +463,7 @@ public final class ConversionUtilsTest {
Set.of(TEST_DAB_SID_EXT_ID, TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID));
ProgramList.Chunk convertedChunk = ConversionUtils.convertChunkToTargetSdkVersion(chunk,
- Build.VERSION_CODES.CUR_DEVELOPMENT);
+ U_APP_UID);
expect.withMessage("Converted program list chunk with required SDK version")
.that(convertedChunk).isEqualTo(chunk);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 464ecb2b50a1..78b5a4ad71de 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -33,6 +33,7 @@ import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.compat.CompatChanges;
import android.graphics.Bitmap;
import android.hardware.broadcastradio.IBroadcastRadio;
import android.hardware.broadcastradio.ITunerCallback;
@@ -46,7 +47,6 @@ import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioTuner;
-import android.os.Build;
import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -73,7 +73,6 @@ import java.util.Set;
*/
public final class TunerSessionTest extends ExtendedRadioMockitoTestCase {
- private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
private static final VerificationWithTimeout CALLBACK_TIMEOUT =
timeout(/* millis= */ 200);
private static final int SIGNAL_QUALITY = 90;
@@ -125,11 +124,13 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase {
@Override
protected void initializeSession(StaticMockitoSessionBuilder builder) {
- builder.spyStatic(RadioServiceUserController.class);
+ builder.spyStatic(RadioServiceUserController.class).spyStatic(CompatChanges.class);
}
@Before
public void setup() throws Exception {
+ doReturn(true).when(() -> CompatChanges.isChangeEnabled(
+ eq(ConversionUtils.RADIO_U_VERSION_REQUIRED), anyInt()));
doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
mRadioModule = new RadioModule(mBroadcastRadioMock,
@@ -341,7 +342,9 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase {
@Test
public void tune_withLowerSdkVersion() throws Exception {
- openAidlClients(/* numClients= */ 1, Build.VERSION_CODES.TIRAMISU);
+ doReturn(false).when(() -> CompatChanges.isChangeEnabled(
+ eq(ConversionUtils.RADIO_U_VERSION_REQUIRED), anyInt()));
+ openAidlClients(/* numClients= */ 1);
ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
RadioManager.ProgramInfo tuneInfo =
AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
@@ -1175,17 +1178,13 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase {
.onParametersUpdated(parametersExpected);
}
}
- private void openAidlClients(int numClients) throws Exception {
- openAidlClients(numClients, TARGET_SDK_VERSION);
- }
- private void openAidlClients(int numClients, int targetSdkVersion) throws Exception {
+ private void openAidlClients(int numClients) throws Exception {
mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients];
mTunerSessions = new TunerSession[numClients];
for (int index = 0; index < numClients; index++) {
mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
- mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index],
- targetSdkVersion);
+ mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
}
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f9b6ce0a199c..8efd180b6c93 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6752,7 +6752,7 @@ public class AudioManager {
/**
* @hide
- * Lower media volume to RS1
+ * Lower media volume to RS1 interval
*/
public void lowerVolumeToRs1() {
try {
@@ -6764,13 +6764,13 @@ public class AudioManager {
/**
* @hide
- * @return the RS2 value used for momentary exposure warnings
+ * @return the RS2 upper bound used for momentary exposure warnings
*/
@TestApi
@RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
public float getRs2Value() {
try {
- return getService().getRs2Value();
+ return getService().getOutputRs2UpperBound();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -6778,13 +6778,13 @@ public class AudioManager {
/**
* @hide
- * Sets the RS2 value used for momentary exposure warnings
+ * Sets the RS2 upper bound used for momentary exposure warnings
*/
@TestApi
@RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
public void setRs2Value(float rs2Value) {
try {
- getService().setRs2Value(rs2Value);
+ getService().setOutputRs2UpperBound(rs2Value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index cebdc820cd02..f9d4efea57e2 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -296,10 +296,10 @@ interface IAudioService {
void lowerVolumeToRs1(String callingPackage);
@EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
- float getRs2Value();
+ float getOutputRs2UpperBound();
@EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
- oneway void setRs2Value(float rs2Value);
+ oneway void setOutputRs2UpperBound(float rs2Value);
@EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
float getCsd();
diff --git a/media/java/android/media/tv/OWNERS b/media/java/android/media/tv/OWNERS
index fa0429350a25..0b022e514662 100644
--- a/media/java/android/media/tv/OWNERS
+++ b/media/java/android/media/tv/OWNERS
@@ -1,6 +1,7 @@
quxiangfang@google.com
shubang@google.com
hgchen@google.com
+qingxun@google.com
# For android remote service
per-file ITvRemoteServiceInput.aidl = file:/media/lib/tvremote/OWNERS
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index ef2b5a5b6e50..7364daa53f89 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -156,20 +156,51 @@ public final class TvInputManager {
"android.media.tv.TvInputManager.stream_id";
/**
+ * This value for {@link #TV_MESSAGE_KEY_GROUP_ID} denotes that the message doesn't
+ * belong to any group.
+ */
+ public static final long TV_MESSAGE_GROUP_ID_NONE = -1;
+
+ /**
+ * This constant is used as a {@link Bundle} key for TV messages. This is used to
+ * optionally identify messages that belong together, such as headers and bodies
+ * of the same event. For messages that do not have a group, this value
+ * should be {@link #TV_MESSAGE_GROUP_ID_NONE}.
+ *
+ * <p> As -1 is a reserved value, -1 should not be used as a valid groupId.
+ *
+ * <p> Type: long
+ */
+ public static final String TV_MESSAGE_KEY_GROUP_ID =
+ "android.media.tv.TvInputManager.group_id";
+
+ /**
+ * This is a subtype for TV messages that can be potentially found as a value
+ * at {@link #TV_MESSAGE_KEY_SUBTYPE}. It identifies the subtype of the message
+ * as the watermarking format ATSC A/335.
+ */
+ public static final String TV_MESSAGE_SUBTYPE_WATERMARKING_A335 = "ATSC A/335";
+
+ /**
+ * This is a subtype for TV messages that can be potentially found as a value
+ * at {@link #TV_MESSAGE_KEY_SUBTYPE}. It identifies the subtype of the message
+ * as the CC format CTA 608-E.
+ */
+ public static final String TV_MESSAGE_SUBTYPE_CC_608E = "CTA 608-E";
+
+ /**
* This constant is used as a {@link Bundle} key for TV messages. The value of the key
* identifies the subtype of the data, such as the format of the CC data. The format
* found at this key can then be used to identify how to parse the data at
* {@link #TV_MESSAGE_KEY_RAW_DATA}.
*
- * To parse the raw data bsed on the subtype, please refer to the official documentation of the
- * concerning subtype. For example, for the subtype "ATSC A/335" for watermarking, the
- * document for A/335 from the ATSC standard details how this data is formatted.
- *
- * Some other examples of common formats include:
- * <ul>
- * <li>Watermarking - ATSC A/336</li>
- * <li>Closed Captioning - CTA 608-E</li>
- * </ul>
+ * <p> To parse the raw data based on the subtype, please refer to the official
+ * documentation of the concerning subtype. For example, for the subtype
+ * {@link #TV_MESSAGE_SUBTYPE_WATERMARKING_A335}, the document for A/335 from the ATSC
+ * standard details how this data is formatted. Similarly, the subtype
+ * {@link #TV_MESSAGE_SUBTYPE_CC_608E} is documented in the ANSI/CTA standard for
+ * 608-E. These subtypes are examples of common formats for their respective uses
+ * and other subtypes may exist.
*
* <p> Type: String
*/
@@ -178,7 +209,7 @@ public final class TvInputManager {
/**
* This constant is used as a {@link Bundle} key for TV messages. The value of the key
- * stores the raw data contained in this TV Message. The format of this data is determined
+ * stores the raw data contained in this TV message. The format of this data is determined
* by the format defined by the subtype, found using the key at
* {@link #TV_MESSAGE_KEY_SUBTYPE}. See {@link #TV_MESSAGE_KEY_SUBTYPE} for more
* information on how to parse this data.
@@ -839,6 +870,7 @@ public final class TvInputManager {
* @param type The type of message received, such as {@link #TV_MESSAGE_TYPE_WATERMARK}
* @param data The raw data of the message. The bundle keys are:
* {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
* {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
* {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
* See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 0d283fa87c19..85b02ad54718 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1032,6 +1032,7 @@ public abstract class TvInputService extends Service {
* {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
* @param data The raw data of the message. The bundle keys are:
* {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
* {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
* {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
* See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
@@ -1507,6 +1508,7 @@ public abstract class TvInputService extends Service {
* {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
* @param data The raw data of the message. The bundle keys are:
* {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
* {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
* {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
* See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 8a886832e88e..c91f62db95c4 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -1256,6 +1256,7 @@ public class TvView extends ViewGroup {
* {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
* @param data The raw data of the message. The bundle keys are:
* {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
* {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
* {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
* See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 69ff9c63b32b..06dfe4fffefe 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -921,6 +921,7 @@ public abstract class TvInteractiveAppService extends Service {
* {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
* @param data The raw data of the message. The bundle keys are:
* {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
* {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
* {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
* See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 80a14354ef7a..cbaf5e482faa 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -946,6 +946,7 @@ public class TvInteractiveAppView extends ViewGroup {
* {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
* @param data The raw data of the message. The bundle keys are:
* {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
* {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
* {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
* See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 904fa7484027..4b63fbf14d4c 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -628,14 +628,15 @@ void ASurfaceTransaction_setExtendedRangeBrightness(ASurfaceTransaction* aSurfac
CHECK_NOT_NULL(aSurfaceControl);
if (!isfinite(currentBufferRatio) || currentBufferRatio < 1.0f) {
- ALOGE("Ignore setExtendedRangeBrightness, currentBufferRatio %f isn't finite or >= 1.0f",
- currentBufferRatio);
+ LOG_ALWAYS_FATAL("setExtendedRangeBrightness, currentBufferRatio %f isn't finite or >= "
+ "1.0f",
+ currentBufferRatio);
return;
}
if (!isfinite(desiredRatio) || desiredRatio < 1.0f) {
- ALOGE("Ignore setExtendedRangeBrightness, desiredRatio %f isn't finite or >= 1.0f",
- desiredRatio);
+ LOG_ALWAYS_FATAL("setExtendedRangeBrightness, desiredRatio %f isn't finite or >= 1.0f",
+ desiredRatio);
return;
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml
index 0747ef089a8d..81fa8e6bba21 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml
@@ -49,7 +49,6 @@
<!-- Short summary of app that appears as subtext on the service preference in Settings -->
<string name="accessibility_menu_summary">Control device via large menu</string>
- <!-- TODO(b/113371047): string need to be reviewed -->
<!-- String defining the settings name -->
<string name="accessibility_menu_settings_name">Accessibility Menu Settings</string>
@@ -57,8 +56,6 @@
<string name="accessibility_menu_large_buttons_title">Large buttons</string>
<!-- String defining the summary of Large button setting -->
<string name="accessibility_menu_large_buttons_summary">Increase size of Accessibility Menu Buttons</string>
- <!-- String defining the title of the preference to show help and feedback menu [CHAR LIMIT=40] -->
- <string name="pref_help_and_feedback_title">Help &#38; feedback</string>
<!-- String defining the title of the preference to show help menu [CHAR LIMIT=40] -->
<string name="pref_help_title">Help</string>
@@ -67,7 +64,4 @@
<!-- The percentage of the music volume, and double "%" is required to represent the symbol "%" -->
<string name="music_volume_percentage_label">Music volume <xliff:g id="percentage">%1$s</xliff:g> %%</string>
- <!-- The label of a settings item that displays legal information about the licenses used in this app. [CHAR LIMIT=NONE] -->
- <string name="pref_item_licenses">Open Source Licenses</string>
-
</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_preferences.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_preferences.xml
index 3b79287ad202..e42c3cd5a368 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_preferences.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_preferences.xml
@@ -28,6 +28,6 @@
<Preference
android:key="@string/pref_help"
- android:title="@string/pref_help_and_feedback_title"/>
+ android:title="@string/pref_help_title"/>
</androidx.preference.PreferenceScreen> \ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index 8f2934882ede..4b6f9a430390 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -71,8 +71,6 @@ public class A11yMenuSettingsActivity extends FragmentActivity {
private void initializeHelpAndFeedbackPreference() {
final Preference prefHelp = findPreference(getString(R.string.pref_help));
if (prefHelp != null) {
- prefHelp.setTitle(R.string.pref_help_title);
-
// Do not allow access to web during setup.
if (Settings.Secure.getInt(
getContext().getContentResolver(),
diff --git a/packages/SystemUI/res/layout/activity_rear_display_education.xml b/packages/SystemUI/res/layout/activity_rear_display_education.xml
index 094807e0fad4..c295cfe7a2e0 100644
--- a/packages/SystemUI/res/layout/activity_rear_display_education.xml
+++ b/packages/SystemUI/res/layout/activity_rear_display_education.xml
@@ -30,6 +30,7 @@
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/rear_display_folded_animation"
+ android:importantForAccessibility="no"
android:layout_width="@dimen/rear_display_animation_width"
android:layout_height="@dimen/rear_display_animation_height"
android:layout_gravity="center"
diff --git a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
index e970bc573b01..c12bfcca4eff 100644
--- a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
+++ b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
@@ -31,6 +31,7 @@
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/rear_display_folded_animation"
+ android:importantForAccessibility="no"
android:layout_width="@dimen/rear_display_animation_width"
android:layout_height="@dimen/rear_display_animation_height"
android:layout_gravity="center"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e5cd0c55027b..082f3855affc 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -99,6 +99,7 @@
<item>accessibility_display_daltonizer_enabled:color_correction</item>
<item>accessibility_display_inversion_enabled:inversion</item>
<item>one_handed_mode_enabled:onehanded</item>
+ <item>accessibility_font_scaling_has_been_changed:font_scaling</item>
</string-array>
<!-- Use collapsed layout for media player in landscape QQS -->
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
index 53a421d9eccc..1836ce857783 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.accessibility.fontscaling
+import android.annotation.WorkerThread
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.res.Configuration
@@ -27,18 +28,26 @@ import android.widget.SeekBar.OnSeekBarChangeListener
import android.widget.TextView
import com.android.systemui.R
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SystemSettings
+import java.util.concurrent.Executor
import kotlin.math.roundToInt
/** The Dialog that contains a seekbar for changing the font size. */
-class FontScalingDialog(context: Context, private val systemSettings: SystemSettings) :
- SystemUIDialog(context) {
+class FontScalingDialog(
+ context: Context,
+ private val systemSettings: SystemSettings,
+ private val secureSettings: SecureSettings,
+ @Background private val backgroundExecutor: Executor
+) : SystemUIDialog(context) {
private val strEntryValues: Array<String> =
context.resources.getStringArray(com.android.settingslib.R.array.entryvalues_font_size)
private lateinit var title: TextView
private lateinit var doneButton: Button
private lateinit var seekBarWithIconButtonsView: SeekBarWithIconButtonsView
+ private var lastProgress: Int = -1
private val configuration: Configuration =
Configuration(context.getResources().getConfiguration())
@@ -70,12 +79,22 @@ class FontScalingDialog(context: Context, private val systemSettings: SystemSett
seekBarWithIconButtonsView.setMax((strEntryValues).size - 1)
val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, 1.0f)
- seekBarWithIconButtonsView.setProgress(fontSizeValueToIndex(currentScale))
+ lastProgress = fontSizeValueToIndex(currentScale)
+ seekBarWithIconButtonsView.setProgress(lastProgress)
seekBarWithIconButtonsView.setOnSeekBarChangeListener(
object : OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
- systemSettings.putString(Settings.System.FONT_SCALE, strEntryValues[progress])
+ if (progress != lastProgress) {
+ if (!fontSizeHasBeenChangedFromTile) {
+ backgroundExecutor.execute { updateSecureSettingsIfNeeded() }
+ fontSizeHasBeenChangedFromTile = true
+ }
+
+ backgroundExecutor.execute { updateFontScale(strEntryValues[progress]) }
+
+ lastProgress = progress
+ }
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
@@ -115,4 +134,28 @@ class FontScalingDialog(context: Context, private val systemSettings: SystemSett
}
}
}
+
+ @WorkerThread
+ fun updateFontScale(newScale: String) {
+ systemSettings.putString(Settings.System.FONT_SCALE, newScale)
+ }
+
+ @WorkerThread
+ fun updateSecureSettingsIfNeeded() {
+ if (
+ secureSettings.getString(Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED) !=
+ ON
+ ) {
+ secureSettings.putString(
+ Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
+ ON
+ )
+ }
+ }
+
+ companion object {
+ private const val ON = "1"
+ private const val OFF = "0"
+ private var fontSizeHasBeenChangedFromTile = false
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index bb35355ba03a..19bd86aba177 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -42,6 +42,7 @@ import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
+import android.hardware.input.InputManager;
import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
@@ -169,6 +170,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@NonNull private final SecureSettings mSecureSettings;
@NonNull private final UdfpsUtils mUdfpsUtils;
+ @NonNull private final InputManager mInputManager;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@@ -576,6 +578,10 @@ public class UdfpsController implements DozeReceiver, Dumpable {
data.getTime(),
data.getGestureStart(),
mStatusBarStateController.isDozing());
+
+ // Pilfer if valid overlap, don't allow following events to reach keyguard
+ mInputManager.pilferPointers(
+ mOverlay.getOverlayView().getViewRootImpl().getInputToken());
break;
case UP:
@@ -599,7 +605,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
break;
case UNCHANGED:
- if (!isWithinSensorArea(mOverlay.getOverlayView(), event.getX(), event.getY(),
+ if (!isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(),
true) && mActivePointerId == MotionEvent.INVALID_POINTER_ID
&& event.getActionMasked() == MotionEvent.ACTION_DOWN
&& mAlternateBouncerInteractor.isVisibleState()) {
@@ -612,6 +618,13 @@ public class UdfpsController implements DozeReceiver, Dumpable {
}
logBiometricTouch(processedTouch.getEvent(), data);
+ // Always pilfer pointers that are within sensor area
+ if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true)) {
+ Log.d("Austin", "pilferTouch invalid overlap");
+ mInputManager.pilferPointers(
+ mOverlay.getOverlayView().getViewRootImpl().getInputToken());
+ }
+
return processedTouch.getTouchData().isWithinSensor(mOverlayParams.getNativeSensorBounds());
}
@@ -798,6 +811,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull SessionTracker sessionTracker,
@NonNull AlternateBouncerInteractor alternateBouncerInteractor,
@NonNull SecureSettings secureSettings,
+ @NonNull InputManager inputManager,
@NonNull UdfpsUtils udfpsUtils) {
mContext = context;
mExecution = execution;
@@ -841,6 +855,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mAlternateBouncerInteractor = alternateBouncerInteractor;
mSecureSettings = secureSettings;
mUdfpsUtils = udfpsUtils;
+ mInputManager = inputManager;
mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
? singlePointerTouchProcessor : null;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index b4724ddebb9a..1a85a0f47749 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -89,6 +89,9 @@ constructor(
R.id.turbulence_noise_view,
R.id.touch_ripple_view,
)
+
+ // Sizing view id for recommendation card view.
+ val recSizingViewId = R.id.sizing_view
}
/** A listener when the current dimensions of the player change */
@@ -176,7 +179,21 @@ constructor(
// Layout dimensions are possibly changing, so we need to update them. (at
// least on large screen devices)
lastOrientation = newOrientation
- loadLayoutForType(type)
+ // Update the height of media controls for the expanded layout. it is needed
+ // for large screen devices.
+ if (type == TYPE.PLAYER) {
+ backgroundIds.forEach { id ->
+ expandedLayout.getConstraint(id).layout.mHeight =
+ context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_session_height_expanded
+ )
+ }
+ } else {
+ expandedLayout.getConstraint(recSizingViewId).layout.mHeight =
+ context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_session_height_expanded
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index a915ddba4f1c..3f514344cee1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles
import android.content.Intent
import android.os.Handler
+import android.os.HandlerExecutor
import android.os.Looper
import android.provider.Settings
import android.view.View
@@ -38,6 +39,7 @@ import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SystemSettings
import javax.inject.Inject
@@ -54,6 +56,7 @@ constructor(
qsLogger: QSLogger,
private val dialogLaunchAnimator: DialogLaunchAnimator,
private val systemSettings: SystemSettings,
+ private val secureSettings: SecureSettings,
private val featureFlags: FeatureFlags
) :
QSTileImpl<QSTile.State?>(
@@ -78,7 +81,13 @@ constructor(
override fun handleClick(view: View?) {
mUiHandler.post {
- val dialog: SystemUIDialog = FontScalingDialog(mContext, systemSettings)
+ val dialog: SystemUIDialog =
+ FontScalingDialog(
+ mContext,
+ systemSettings,
+ secureSettings,
+ HandlerExecutor(mHandler)
+ )
if (view != null) {
dialogLaunchAnimator.showFromView(
dialog,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 30865aa0c45d..32c59afc3794 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2805,6 +2805,7 @@ public final class NotificationPanelViewController implements Dumpable {
public void setIsLaunchAnimationRunning(boolean running) {
boolean wasRunning = mIsLaunchAnimationRunning;
mIsLaunchAnimationRunning = running;
+ mCentralSurfaces.updateIsKeyguard();
if (wasRunning != mIsLaunchAnimationRunning) {
mShadeExpansionStateManager.notifyLaunchingActivityChanged(running);
}
@@ -3862,6 +3863,10 @@ public final class NotificationPanelViewController implements Dumpable {
return mClosing || mIsLaunchAnimationRunning;
}
+ public boolean isLaunchAnimationRunning() {
+ return mIsLaunchAnimationRunning;
+ }
+
public boolean isTracking() {
return mTracking;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 156e4fd1889f..e7759df6e81b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -301,9 +301,11 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
private void applyKeyguardFlags(NotificationShadeWindowState state) {
- final boolean keyguardOrAod = state.keyguardShowing
+ // Keyguard is visible if it's showing or if it's fading away (in which case we're animating
+ // it out, but the wallpaper should remain visible as a backdrop for the animation);
+ final boolean keyguardOrAodVisible = (state.keyguardShowing || state.keyguardFadingAway)
|| (state.dozing && mDozeParameters.getAlwaysOn());
- if ((keyguardOrAod && !state.mediaBackdropShowing && !state.lightRevealScrimOpaque)
+ if ((keyguardOrAodVisible && !state.mediaBackdropShowing && !state.lightRevealScrimOpaque)
|| mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()) {
// Show the wallpaper if we're on keyguard/AOD and the wallpaper is not occluded by a
// solid backdrop. Also, show it if we are currently animating between the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 5438a59fadc2..5e3c1c59666d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1052,6 +1052,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// The light reveal scrim should always be fully revealed by the time the keyguard
// is done going away. Double check that this is true.
if (!mKeyguardStateController.isKeyguardGoingAway()) {
+ updateIsKeyguard();
+
if (mLightRevealScrim.getRevealAmount() != 1f) {
Log.e(TAG, "Keyguard is done going away, but someone left the light reveal "
+ "scrim at reveal amount: " + mLightRevealScrim.getRevealAmount());
@@ -2943,6 +2945,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
showKeyguardImpl();
}
} else {
+ final boolean isLaunchingOrGoingAway =
+ mNotificationPanelViewController.isLaunchAnimationRunning()
+ || mKeyguardStateController.isKeyguardGoingAway();
+
// During folding a foldable device this might be called as a result of
// 'onScreenTurnedOff' call for the inner display.
// In this case:
@@ -2954,7 +2960,14 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
if (!mScreenOffAnimationController.isKeyguardHideDelayed()
// If we're animating occluded, there's an activity launching over the keyguard
// UI. Wait to hide it until after the animation concludes.
- && !mKeyguardViewMediator.isOccludeAnimationPlaying()) {
+ && !mKeyguardViewMediator.isOccludeAnimationPlaying()
+ // If we're occluded, but playing an animation (launch or going away animations)
+ // the keyguard is visible behind the animation.
+ && !(mKeyguardStateController.isOccluded() && isLaunchingOrGoingAway)) {
+ // If we're going away and occluded, it means we are launching over the
+ // unsecured keyguard, which will subsequently go away. Wait to hide it until
+ // after the animation concludes to avoid the lockscreen UI changing into the
+ // shade UI behind the launch animation.
return hideKeyguardImpl(forceStateChange);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
index ca6f42618e2a..eb8295653199 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
@@ -25,12 +25,19 @@ import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SystemSettings
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+private const val ON: Int = 1
+private const val OFF: Int = 0
/** Tests for [FontScalingDialog]. */
@SmallTest
@@ -39,6 +46,8 @@ import org.junit.runner.RunWith
class FontScalingDialogTest : SysuiTestCase() {
private lateinit var fontScalingDialog: FontScalingDialog
private lateinit var systemSettings: SystemSettings
+ private lateinit var secureSettings: SecureSettings
+ private lateinit var backgroundExecutor: FakeExecutor
private val fontSizeValueArray: Array<String> =
mContext
.getResources()
@@ -46,9 +55,13 @@ class FontScalingDialogTest : SysuiTestCase() {
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
val mainHandler = Handler(TestableLooper.get(this).getLooper())
systemSettings = FakeSettings()
- fontScalingDialog = FontScalingDialog(mContext, systemSettings as FakeSettings)
+ secureSettings = FakeSettings()
+ backgroundExecutor = FakeExecutor(FakeSystemClock())
+ fontScalingDialog =
+ FontScalingDialog(mContext, systemSettings, secureSettings, backgroundExecutor)
}
@Test
@@ -76,6 +89,7 @@ class FontScalingDialogTest : SysuiTestCase() {
seekBarWithIconButtonsView.setProgress(0)
iconEndFrame.performClick()
+ backgroundExecutor.runAllReady()
val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
assertThat(seekBar.getProgress()).isEqualTo(1)
@@ -96,6 +110,7 @@ class FontScalingDialogTest : SysuiTestCase() {
seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1)
iconStartFrame.performClick()
+ backgroundExecutor.runAllReady()
val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2)
@@ -104,4 +119,26 @@ class FontScalingDialogTest : SysuiTestCase() {
fontScalingDialog.dismiss()
}
+
+ @Test
+ fun progressChanged_keyWasNotSetBefore_fontScalingHasBeenChangedIsOn() {
+ fontScalingDialog.show()
+
+ val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
+ fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
+ secureSettings.putInt(Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, OFF)
+
+ // Default seekbar progress for font size is 1, set it to another progress 0
+ seekBarWithIconButtonsView.setProgress(0)
+ backgroundExecutor.runAllReady()
+
+ val currentSettings =
+ secureSettings.getInt(
+ Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
+ /* def = */ OFF
+ )
+ assertThat(currentSettings).isEqualTo(ON)
+
+ fontScalingDialog.dismiss()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 9a73898ca7c7..445cc8739463 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -53,6 +53,7 @@ import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
+import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.PowerManager;
import android.os.RemoteException;
@@ -63,6 +64,7 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
@@ -231,6 +233,10 @@ public class UdfpsControllerTest extends SysuiTestCase {
private FingerprintSensorPropertiesInternal mOpticalProps;
private FingerprintSensorPropertiesInternal mUltrasonicProps;
private UdfpsUtils mUdfpsUtils;
+ @Mock
+ private InputManager mInputManager;
+ @Mock
+ private ViewRootImpl mViewRootImpl;
@Before
public void setUp() {
@@ -248,6 +254,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
when(mSessionTracker.getSessionId(anyInt())).thenReturn(
(new InstanceIdSequence(1 << 20)).newInstanceId());
+ when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl);
final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
@@ -304,7 +311,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
- mAlternateBouncerInteractor, mSecureSettings, mUdfpsUtils);
+ mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -1262,6 +1269,81 @@ public class UdfpsControllerTest extends SysuiTestCase {
}
@Test
+ public void onTouch_withNewTouchDetection_pilferPointer() throws RemoteException {
+ final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+ 0L);
+ final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch(
+ InteractionEvent.DOWN, 1 /* pointerId */, touchData);
+
+ // Enable new touch detection.
+ when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+
+ // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+ initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+ // Configure UdfpsView to accept the ACTION_DOWN event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+ // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+ // WHEN ACTION_DOWN is received
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ processorResultDown);
+ MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ mBiometricExecutor.runAllReady();
+ downEvent.recycle();
+
+ // THEN the touch is pilfered, expected twice (valid overlap and touch on sensor)
+ verify(mInputManager, times(2)).pilferPointers(any());
+ }
+
+ @Test
+ public void onTouch_withNewTouchDetection_doNotPilferPointer() throws RemoteException {
+ final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+ 0L);
+ final TouchProcessorResult processorResultUnchanged =
+ new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED,
+ 1 /* pointerId */, touchData);
+
+ // Enable new touch detection.
+ when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+
+ // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+ initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+ // Configure UdfpsView to not accept the ACTION_DOWN event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false);
+
+ // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+ // WHEN ACTION_DOWN is received and touch is not within sensor
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ processorResultUnchanged);
+ MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ mBiometricExecutor.runAllReady();
+ downEvent.recycle();
+
+ // THEN the touch is NOT pilfered
+ verify(mInputManager, times(0)).pilferPointers(any());
+ }
+
+ @Test
public void onAodInterrupt_onAcquiredGood_fingerNoLongerDown() throws RemoteException {
// GIVEN UDFPS overlay is showing
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
@@ -1285,6 +1367,5 @@ public class UdfpsControllerTest extends SysuiTestCase {
// THEN is fingerDown should be FALSE
assertFalse(mUdfpsController.isFingerDown());
-
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 0fac3db2dc1f..4565762929d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -21,7 +21,6 @@ import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
-import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
@@ -61,8 +60,6 @@ class MediaViewControllerTest : SysuiTestCase() {
@Mock private lateinit var mediaSubTitleWidgetState: WidgetState
@Mock private lateinit var mediaContainerWidgetState: WidgetState
@Mock private lateinit var mediaFlags: MediaFlags
- @Mock private lateinit var expandedLayout: ConstraintSet
- @Mock private lateinit var collapsedLayout: ConstraintSet
val delta = 0.1F
@@ -82,16 +79,47 @@ class MediaViewControllerTest : SysuiTestCase() {
}
@Test
- fun testOrientationChanged_layoutsAreLoaded() {
- mediaViewController.expandedLayout = expandedLayout
- mediaViewController.collapsedLayout = collapsedLayout
+ fun testOrientationChanged_heightOfPlayerIsUpdated() {
+ val newConfig = Configuration()
+
+ mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
+ // Change the height to see the effect of orientation change.
+ MediaViewController.backgroundIds.forEach { id ->
+ mediaViewController.expandedLayout.getConstraint(id).layout.mHeight = 10
+ }
+ newConfig.orientation = ORIENTATION_LANDSCAPE
+ configurationController.onConfigurationChanged(newConfig)
+ MediaViewController.backgroundIds.forEach { id ->
+ assertTrue(
+ mediaViewController.expandedLayout.getConstraint(id).layout.mHeight ==
+ context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_session_height_expanded
+ )
+ )
+ }
+ }
+
+ @Test
+ fun testOrientationChanged_heightOfRecCardIsUpdated() {
val newConfig = Configuration()
+
+ mediaViewController.attach(recommendation, MediaViewController.TYPE.RECOMMENDATION)
+ // Change the height to see the effect of orientation change.
+ mediaViewController.expandedLayout
+ .getConstraint(MediaViewController.recSizingViewId)
+ .layout
+ .mHeight = 10
newConfig.orientation = ORIENTATION_LANDSCAPE
configurationController.onConfigurationChanged(newConfig)
- verify(expandedLayout).load(context, R.xml.media_session_expanded)
- verify(collapsedLayout).load(context, R.xml.media_session_collapsed)
+ assertTrue(
+ mediaViewController.expandedLayout
+ .getConstraint(MediaViewController.recSizingViewId)
+ .layout
+ .mHeight ==
+ context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded)
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
index bd99cd482859..eeebd4fb7792 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
@@ -84,6 +84,7 @@ class FontScalingTileTest : SysuiTestCase() {
qsLogger,
dialogLaunchAnimator,
FakeSettings(),
+ FakeSettings(),
featureFlags
)
fontScalingTile.initialize()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 526dc8d150fe..dd7929771bb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -223,6 +223,16 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
}
@Test
+ public void attach_fadingAway_wallpaperVisible() {
+ clearInvocations(mWindowManager);
+ mNotificationShadeWindowController.attach();
+ mNotificationShadeWindowController.setKeyguardFadingAway(true);
+
+ verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+ assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) != 0).isTrue();
+ }
+
+ @Test
public void setBackgroundBlurRadius_expandedWithBlurs() {
mNotificationShadeWindowController.setBackgroundBlurRadius(10);
verify(mNotificationShadeWindowView).setVisibility(eq(View.VISIBLE));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 7db219719bf0..1aba1fc37b64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -1342,6 +1342,18 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
}
@Test
+ public void keyguard_notHidden_ifGoingAwayAndOccluded() {
+ setKeyguardShowingAndOccluded(true /* showing */, false /* occluded */);
+
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+
+ mCentralSurfaces.updateIsKeyguard(false);
+
+ verify(mStatusBarStateController, never()).setState(eq(SHADE), anyBoolean());
+ }
+
+ @Test
public void frpLockedDevice_shadeDisabled() {
when(mDeviceProvisionedController.isFrpActive()).thenReturn(true);
when(mDozeServiceHost.isPulsing()).thenReturn(true);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index d422f9a1ab63..0edb8f27d413 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2263,6 +2263,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
if (userState.mEnabledServices.contains(componentName)
&& !mUiAutomationManager.suppressingAccessibilityServicesLocked()) {
+ // Skip the enabling service disallowed by device admin policy.
+ if (!isAccessibilityTargetAllowed(componentName.getPackageName(),
+ installedService.getResolveInfo().serviceInfo.applicationInfo.uid,
+ userState.mUserId)) {
+ Slog.d(LOG_TAG, "Skipping enabling service disallowed by device admin policy: "
+ + componentName);
+ disableAccessibilityServiceLocked(componentName, userState.mUserId);
+ continue;
+ }
if (service == null) {
service = new AccessibilityServiceConnection(userState, mContext, componentName,
installedService, sIdCounter++, mMainHandler, mLock, mSecurityPolicy,
@@ -3875,32 +3884,29 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
- @Override
- @RequiresPermission(anyOf = {
- android.Manifest.permission.MANAGE_USERS,
- android.Manifest.permission.QUERY_ADMIN_POLICY})
public boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId) {
- final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
- final List<String> permittedServices = dpm.getPermittedAccessibilityServices(userId);
-
- // permittedServices null means all accessibility services are allowed.
- boolean allowed = permittedServices == null || permittedServices.contains(packageName);
- if (allowed) {
- final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
- final int mode = appOps.noteOpNoThrow(
- AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
- uid, packageName, /* attributionTag= */ null, /* message= */ null);
- final boolean ecmEnabled = mContext.getResources().getBoolean(
- R.bool.config_enhancedConfirmationModeEnabled);
- return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ final List<String> permittedServices = dpm.getPermittedAccessibilityServices(userId);
+
+ // permittedServices null means all accessibility services are allowed.
+ boolean allowed = permittedServices == null || permittedServices.contains(packageName);
+ if (allowed) {
+ final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
+ final int mode = appOps.noteOpNoThrow(
+ AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
+ uid, packageName, /* attributionTag= */ null, /* message= */ null);
+ final boolean ecmEnabled = mContext.getResources().getBoolean(
+ R.bool.config_enhancedConfirmationModeEnabled);
+ return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED;
+ }
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
- return false;
}
- @Override
- @RequiresPermission(anyOf = {
- android.Manifest.permission.MANAGE_USERS,
- android.Manifest.permission.QUERY_ADMIN_POLICY})
public boolean sendRestrictedDialogIntent(String packageName, int uid, int userId) {
// The accessibility service is allowed. Don't show the restricted dialog.
if (isAccessibilityTargetAllowed(packageName, uid, userId)) {
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 70eeb7fecc8f..fc565111dbe8 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -203,6 +203,16 @@ public abstract class UsageStatsManagerInternal {
public abstract void setActiveAdminApps(Set<String> adminApps, int userId);
/**
+ * Called by DevicePolicyManagerService to inform about the protected packages for a user.
+ * User control will be disabled for protected packages.
+ *
+ * @param packageNames the set of protected packages for {@code userId}.
+ * @param userId the userId to which the protected packages belong.
+ */
+ public abstract void setAdminProtectedPackages(@Nullable Set<String> packageNames,
+ @UserIdInt int userId);
+
+ /**
* Called by DevicePolicyManagerService during boot to inform that admin data is loaded and
* pushed to UsageStatsService.
*/
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3e86c454fae8..0e4331ee0d38 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -10471,16 +10471,16 @@ public class AudioService extends IAudioService.Stub
@Override
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
- public float getRs2Value() {
- super.getRs2Value_enforcePermission();
- return mSoundDoseHelper.getRs2Value();
+ public float getOutputRs2UpperBound() {
+ super.getOutputRs2UpperBound_enforcePermission();
+ return mSoundDoseHelper.getOutputRs2UpperBound();
}
@Override
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
- public void setRs2Value(float rs2Value) {
- super.setRs2Value_enforcePermission();
- mSoundDoseHelper.setRs2Value(rs2Value);
+ public void setOutputRs2UpperBound(float rs2Value) {
+ super.setOutputRs2UpperBound_enforcePermission();
+ mSoundDoseHelper.setOutputRs2UpperBound(rs2Value);
}
@Override
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index c2483671a065..d6a9d3aa8ce3 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -300,7 +300,7 @@ public class SoundDoseHelper {
SAFE_MEDIA_VOLUME_UNINITIALIZED);
}
- float getRs2Value() {
+ float getOutputRs2UpperBound() {
if (!mEnableCsd) {
return 0.f;
}
@@ -312,14 +312,14 @@ public class SoundDoseHelper {
}
try {
- return soundDose.getOutputRs2();
+ return soundDose.getOutputRs2UpperBound();
} catch (RemoteException e) {
Log.e(TAG, "Exception while getting the RS2 exposure value", e);
return 0.f;
}
}
- void setRs2Value(float rs2Value) {
+ void setOutputRs2UpperBound(float rs2Value) {
if (!mEnableCsd) {
return;
}
@@ -331,7 +331,7 @@ public class SoundDoseHelper {
}
try {
- soundDose.setOutputRs2(rs2Value);
+ soundDose.setOutputRs2UpperBound(rs2Value);
} catch (RemoteException e) {
Log.e(TAG, "Exception while setting the RS2 exposure value", e);
}
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
index b66120d428c3..6a010424db13 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
@@ -86,8 +86,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
@Override
public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
- boolean withAudio, ITunerCallback callback, int targetSdkVersion)
- throws RemoteException {
+ boolean withAudio, ITunerCallback callback) throws RemoteException {
if (isDebugEnabled()) {
Slogf.d(TAG, "Opening module %d", moduleId);
}
@@ -95,7 +94,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
if (callback == null) {
throw new IllegalArgumentException("Callback must not be null");
}
- return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback, targetSdkVersion);
+ return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback);
}
@Override
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
index 8a1ba19e24ab..408fba1bff3b 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
@@ -92,8 +92,7 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {
@Override
public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
- boolean withAudio, ITunerCallback callback, int targetSdkVersion)
- throws RemoteException {
+ boolean withAudio, ITunerCallback callback) throws RemoteException {
if (isDebugEnabled()) {
Slog.d(TAG, "Opening module " + moduleId);
}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
index 772cd4144f11..03acf72725e7 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -199,8 +199,7 @@ public final class BroadcastRadioServiceImpl {
*/
@Nullable
public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
- boolean withAudio, ITunerCallback callback, int targetSdkVersion)
- throws RemoteException {
+ boolean withAudio, ITunerCallback callback) throws RemoteException {
if (DEBUG) {
Slogf.d(TAG, "Open AIDL radio session");
}
@@ -223,7 +222,7 @@ public final class BroadcastRadioServiceImpl {
}
}
- TunerSession tunerSession = radioModule.openSession(callback, targetSdkVersion);
+ TunerSession tunerSession = radioModule.openSession(callback);
if (legacyConfig != null) {
tunerSession.setConfiguration(legacyConfig);
}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
index c0a238feb9b5..4f2bfd1c1e52 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
@@ -17,6 +17,10 @@
package com.android.server.broadcastradio.aidl;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.hardware.broadcastradio.AmFmRegionConfig;
import android.hardware.broadcastradio.Announcement;
import android.hardware.broadcastradio.DabTableEntry;
@@ -57,16 +61,24 @@ import java.util.Set;
* {@link android.hardware.radio}
*/
final class ConversionUtils {
- // TODO(b/241118988): Add unit test for ConversionUtils class
private static final String TAG = "BcRadioAidlSrv.convert";
+ /**
+ * With RADIO_U_VERSION_REQUIRED enabled, 44-bit DAB identifier
+ * {@link IdentifierType#DAB_SID_EXT} from broadcast radio HAL can be passed as
+ * {@link ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT} to {@link RadioTuner}.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long RADIO_U_VERSION_REQUIRED = 261770108L;
+
private ConversionUtils() {
throw new UnsupportedOperationException("ConversionUtils class is noninstantiable");
}
- static boolean isAtLeastU(int targetSdkVersion) {
- // TODO(b/261770108): Use version code for U.
- return targetSdkVersion >= Build.VERSION_CODES.CUR_DEVELOPMENT;
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ static boolean isAtLeastU(int uid) {
+ return CompatChanges.isChangeEnabled(RADIO_U_VERSION_REQUIRED, uid);
}
static RuntimeException throwOnError(RuntimeException halException, String action) {
@@ -584,9 +596,8 @@ final class ConversionUtils {
return id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT;
}
- static boolean programSelectorMeetsSdkVersionRequirement(ProgramSelector sel,
- int targetSdkVersion) {
- if (isAtLeastU(targetSdkVersion)) {
+ static boolean programSelectorMeetsSdkVersionRequirement(ProgramSelector sel, int uid) {
+ if (isAtLeastU(uid)) {
return true;
}
if (sel.getPrimaryId().getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
@@ -601,12 +612,11 @@ final class ConversionUtils {
return true;
}
- static boolean programInfoMeetsSdkVersionRequirement(RadioManager.ProgramInfo info,
- int targetSdkVersion) {
- if (isAtLeastU(targetSdkVersion)) {
+ static boolean programInfoMeetsSdkVersionRequirement(RadioManager.ProgramInfo info, int uid) {
+ if (isAtLeastU(uid)) {
return true;
}
- if (!programSelectorMeetsSdkVersionRequirement(info.getSelector(), targetSdkVersion)) {
+ if (!programSelectorMeetsSdkVersionRequirement(info.getSelector(), uid)) {
return false;
}
if (isNewIdentifierInU(info.getLogicallyTunedTo())
@@ -622,16 +632,15 @@ final class ConversionUtils {
return true;
}
- static ProgramList.Chunk convertChunkToTargetSdkVersion(ProgramList.Chunk chunk,
- int targetSdkVersion) {
- if (isAtLeastU(targetSdkVersion)) {
+ static ProgramList.Chunk convertChunkToTargetSdkVersion(ProgramList.Chunk chunk, int uid) {
+ if (isAtLeastU(uid)) {
return chunk;
}
Set<RadioManager.ProgramInfo> modified = new ArraySet<>();
Iterator<RadioManager.ProgramInfo> modifiedIterator = chunk.getModified().iterator();
while (modifiedIterator.hasNext()) {
RadioManager.ProgramInfo info = modifiedIterator.next();
- if (programInfoMeetsSdkVersionRequirement(info, targetSdkVersion)) {
+ if (programInfoMeetsSdkVersionRequirement(info, uid)) {
modified.add(info);
}
}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index f8c19ae2b8f1..132fb8ef8a95 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -101,9 +101,9 @@ final class RadioModule {
ConversionUtils.programSelectorFromHalProgramSelector(programSelector);
int tunerResult = ConversionUtils.halResultToTunerResult(result);
synchronized (mLock) {
- fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+ fanoutAidlCallbackLocked((cb, uid) -> {
if (csel != null && !ConversionUtils
- .programSelectorMeetsSdkVersionRequirement(csel, sdkVersion)) {
+ .programSelectorMeetsSdkVersionRequirement(csel, uid)) {
Slogf.e(TAG, "onTuneFailed: cannot send program selector "
+ "requiring higher target SDK version");
return;
@@ -123,9 +123,9 @@ final class RadioModule {
"Program info from AIDL HAL is invalid");
synchronized (mLock) {
mCurrentProgramInfo = currentProgramInfo;
- fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+ fanoutAidlCallbackLocked((cb, uid) -> {
if (!ConversionUtils.programInfoMeetsSdkVersionRequirement(
- currentProgramInfo, sdkVersion)) {
+ currentProgramInfo, uid)) {
Slogf.e(TAG, "onCurrentProgramInfoChanged: cannot send "
+ "program info requiring higher target SDK version");
return;
@@ -156,7 +156,7 @@ final class RadioModule {
fireLater(() -> {
synchronized (mLock) {
mAntennaConnected = connected;
- fanoutAidlCallbackLocked((cb, sdkVersion) -> cb.onAntennaState(connected));
+ fanoutAidlCallbackLocked((cb, uid) -> cb.onAntennaState(connected));
}
});
}
@@ -165,7 +165,7 @@ final class RadioModule {
public void onConfigFlagUpdated(int flag, boolean value) {
fireLater(() -> {
synchronized (mLock) {
- fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+ fanoutAidlCallbackLocked((cb, uid) -> {
cb.onConfigFlagUpdated(flag, value);
});
}
@@ -178,7 +178,7 @@ final class RadioModule {
synchronized (mLock) {
Map<String, String> cparam =
ConversionUtils.vendorInfoFromHalVendorKeyValues(parameters);
- fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+ fanoutAidlCallbackLocked((cb, uid) -> {
cb.onParametersUpdated(cparam);
});
}
@@ -244,14 +244,14 @@ final class RadioModule {
mService.setTunerCallback(mHalTunerCallback);
}
- TunerSession openSession(android.hardware.radio.ITunerCallback userCb, int targetSdkVersion)
+ TunerSession openSession(android.hardware.radio.ITunerCallback userCb)
throws RemoteException {
mLogger.logRadioEvent("Open TunerSession");
TunerSession tunerSession;
Boolean antennaConnected;
RadioManager.ProgramInfo currentProgramInfo;
synchronized (mLock) {
- tunerSession = new TunerSession(this, mService, userCb, targetSdkVersion);
+ tunerSession = new TunerSession(this, mService, userCb);
mAidlTunerSessions.add(tunerSession);
antennaConnected = mAntennaConnected;
currentProgramInfo = mCurrentProgramInfo;
@@ -404,7 +404,7 @@ final class RadioModule {
}
interface AidlCallbackRunnable {
- void run(android.hardware.radio.ITunerCallback callback, int targetSdkVersion)
+ void run(android.hardware.radio.ITunerCallback callback, int uid)
throws RemoteException;
}
@@ -423,7 +423,7 @@ final class RadioModule {
for (int i = 0; i < mAidlTunerSessions.size(); i++) {
try {
runnable.run(mAidlTunerSessions.valueAt(i).mCallback,
- mAidlTunerSessions.valueAt(i).getTargetSdkVersion());
+ mAidlTunerSessions.valueAt(i).getUid());
} catch (DeadObjectException ex) {
// The other side died without calling close(), so just purge it from our records.
Slogf.e(TAG, "Removing dead TunerSession");
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
index fe8c23845d2a..0a3823f038fa 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
@@ -24,6 +24,7 @@ import android.hardware.radio.ITuner;
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
+import android.os.Binder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -46,7 +47,7 @@ final class TunerSession extends ITuner.Stub {
private final RadioLogger mLogger;
private final RadioModule mModule;
final android.hardware.radio.ITunerCallback mCallback;
- private final int mTargetSdkVersion;
+ private final int mUid;
private final IBroadcastRadio mService;
@GuardedBy("mLock")
@@ -61,11 +62,11 @@ final class TunerSession extends ITuner.Stub {
private RadioManager.BandConfig mPlaceHolderConfig;
TunerSession(RadioModule radioModule, IBroadcastRadio service,
- android.hardware.radio.ITunerCallback callback, int targetSdkVersion) {
+ android.hardware.radio.ITunerCallback callback) {
mModule = Objects.requireNonNull(radioModule, "radioModule cannot be null");
mService = Objects.requireNonNull(service, "service cannot be null");
mCallback = Objects.requireNonNull(callback, "callback cannot be null");
- mTargetSdkVersion = targetSdkVersion;
+ mUid = Binder.getCallingUid();
mLogger = new RadioLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
}
@@ -130,7 +131,7 @@ final class TunerSession extends ITuner.Stub {
mPlaceHolderConfig = Objects.requireNonNull(config, "config cannot be null");
}
Slogf.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL AIDL");
- mModule.fanoutAidlCallback((cb, sdkVersion) -> cb.onConfigurationChanged(config));
+ mModule.fanoutAidlCallback((cb, mUid) -> cb.onConfigurationChanged(config));
}
@Override
@@ -254,7 +255,7 @@ final class TunerSession extends ITuner.Stub {
Slogf.w(TAG, "Cannot start background scan on AIDL HAL client from non-current user");
return false;
}
- mModule.fanoutAidlCallback((cb, sdkVersion) -> {
+ mModule.fanoutAidlCallback((cb, mUid) -> {
cb.onBackgroundScanComplete();
});
return true;
@@ -284,8 +285,8 @@ final class TunerSession extends ITuner.Stub {
mModule.onTunerSessionProgramListFilterChanged(this);
}
- int getTargetSdkVersion() {
- return mTargetSdkVersion;
+ int getUid() {
+ return mUid;
}
ProgramList.Filter getProgramListFilter() {
@@ -323,10 +324,9 @@ final class TunerSession extends ITuner.Stub {
}
for (int i = 0; i < chunks.size(); i++) {
try {
- if (!ConversionUtils.isAtLeastU(getTargetSdkVersion())) {
+ if (!ConversionUtils.isAtLeastU(getUid())) {
ProgramList.Chunk downgradedChunk =
- ConversionUtils.convertChunkToTargetSdkVersion(chunks.get(i),
- getTargetSdkVersion());
+ ConversionUtils.convertChunkToTargetSdkVersion(chunks.get(i), getUid());
mCallback.onProgramListUpdated(downgradedChunk);
} else {
mCallback.onProgramListUpdated(chunks.get(i));
diff --git a/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java b/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java
index 06b45bf0fb4b..97507be96d84 100644
--- a/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java
+++ b/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java
@@ -21,6 +21,8 @@ import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACK
import com.android.internal.util.Preconditions;
+import java.util.Objects;
+
/** CPU availability information. */
public final class CpuAvailabilityInfo {
/** Constant to indicate missing CPU availability percent. */
@@ -35,29 +37,64 @@ public final class CpuAvailabilityInfo {
@CpuAvailabilityMonitoringConfig.Cpuset
public final int cpuset;
+ /** Uptime (in milliseconds) when the data in this object was captured. */
+ public final long dataTimestampUptimeMillis;
+
/** The latest average CPU availability percent. */
public final int latestAvgAvailabilityPercent;
- /** The past N-second average CPU availability percent. */
- public final int pastNSecAvgAvailabilityPercent;
+ /**
+ * The past N-millisecond average CPU availability percent.
+ *
+ * <p>When there is not enough data to calculate the past N-millisecond average, this field will
+ * contain the value {@link MISSING_CPU_AVAILABILITY_PERCENT}.
+ */
+ public final int pastNMillisAvgAvailabilityPercent;
- /** The duration over which the {@link pastNSecAvgAvailabilityPercent} was calculated. */
- public final int avgAvailabilityDurationSec;
+ /** The duration over which the {@link pastNMillisAvgAvailabilityPercent} was calculated. */
+ public final long pastNMillisDuration;
@Override
public String toString() {
- return "CpuAvailabilityInfo{" + "cpuset=" + cpuset + ", latestAvgAvailabilityPercent="
- + latestAvgAvailabilityPercent + ", pastNSecAvgAvailabilityPercent="
- + pastNSecAvgAvailabilityPercent + ", avgAvailabilityDurationSec="
- + avgAvailabilityDurationSec + '}';
+ return "CpuAvailabilityInfo{" + "cpuset = " + cpuset + ", dataTimestampUptimeMillis = "
+ + dataTimestampUptimeMillis + ", latestAvgAvailabilityPercent = "
+ + latestAvgAvailabilityPercent + ", pastNMillisAvgAvailabilityPercent = "
+ + pastNMillisAvgAvailabilityPercent + ", pastNMillisDuration = "
+ + pastNMillisDuration + '}';
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof CpuAvailabilityInfo)) {
+ return false;
+ }
+ CpuAvailabilityInfo info = (CpuAvailabilityInfo) obj;
+ return cpuset == info.cpuset && dataTimestampUptimeMillis == info.dataTimestampUptimeMillis
+ && latestAvgAvailabilityPercent == info.latestAvgAvailabilityPercent
+ && pastNMillisAvgAvailabilityPercent == info.pastNMillisAvgAvailabilityPercent
+ && pastNMillisDuration == info.pastNMillisDuration;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(cpuset, dataTimestampUptimeMillis, latestAvgAvailabilityPercent,
+ pastNMillisAvgAvailabilityPercent, pastNMillisDuration);
}
- CpuAvailabilityInfo(int cpuset, int latestAvgAvailabilityPercent,
- int pastNSecAvgAvailabilityPercent, int avgAvailabilityDurationSec) {
+ CpuAvailabilityInfo(int cpuset, long dataTimestampUptimeMillis,
+ int latestAvgAvailabilityPercent, int pastNMillisAvgAvailabilityPercent,
+ long pastNMillisDuration) {
this.cpuset = Preconditions.checkArgumentInRange(cpuset, CPUSET_ALL, CPUSET_BACKGROUND,
"cpuset");
- this.latestAvgAvailabilityPercent = latestAvgAvailabilityPercent;
- this.pastNSecAvgAvailabilityPercent = pastNSecAvgAvailabilityPercent;
- this.avgAvailabilityDurationSec = avgAvailabilityDurationSec;
+ this.dataTimestampUptimeMillis =
+ Preconditions.checkArgumentNonnegative(dataTimestampUptimeMillis);
+ this.latestAvgAvailabilityPercent = Preconditions.checkArgumentNonnegative(
+ latestAvgAvailabilityPercent);
+ this.pastNMillisAvgAvailabilityPercent = pastNMillisAvgAvailabilityPercent;
+ this.pastNMillisDuration = Preconditions.checkArgumentNonnegative(
+ pastNMillisDuration);
}
}
diff --git a/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java b/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java
index a3c4c9e828b4..cbe02fc09d84 100644
--- a/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java
+++ b/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java
@@ -90,8 +90,19 @@ public final class CpuAvailabilityMonitoringConfig {
@Override
public String toString() {
- return "CpuAvailabilityMonitoringConfig{cpuset=" + cpuset + ", mThresholds=" + mThresholds
- + ')';
+ return "CpuAvailabilityMonitoringConfig{cpuset=" + toCpusetString(cpuset) + ", mThresholds="
+ + mThresholds + ')';
+ }
+
+ /** Returns the string equivalent of the provided cpuset. */
+ public static String toCpusetString(int cpuset) {
+ switch (cpuset) {
+ case CPUSET_ALL:
+ return "CPUSET_ALL";
+ case CPUSET_BACKGROUND:
+ return "CPUSET_BACKGROUND";
+ }
+ return "Invalid cpuset: " + cpuset;
}
private CpuAvailabilityMonitoringConfig(Builder builder) {
diff --git a/services/core/java/com/android/server/cpu/CpuInfoReader.java b/services/core/java/com/android/server/cpu/CpuInfoReader.java
index ca97a9847b39..ce68edbb9fa1 100644
--- a/services/core/java/com/android/server/cpu/CpuInfoReader.java
+++ b/services/core/java/com/android/server/cpu/CpuInfoReader.java
@@ -21,8 +21,10 @@ import static com.android.server.cpu.CpuMonitorService.TAG;
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.os.SystemClock;
import android.system.Os;
import android.system.OsConstants;
+import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.LongSparseLongArray;
import android.util.SparseArray;
@@ -50,6 +52,9 @@ public final class CpuInfoReader {
private static final String POLICY_DIR_PREFIX = "policy";
private static final String RELATED_CPUS_FILE = "related_cpus";
private static final String AFFECTED_CPUS_FILE = "affected_cpus";
+ // TODO(b/263154344): Avoid reading from cpuinfo_cur_freq because non-root users don't have
+ // read permission for this file. The file permissions are set by the Kernel. Instead, read
+ // the current frequency only from scaling_cur_freq.
private static final String CUR_CPUFREQ_FILE = "cpuinfo_cur_freq";
private static final String MAX_CPUFREQ_FILE = "cpuinfo_max_freq";
private static final String CUR_SCALING_FREQ_FILE = "scaling_cur_freq";
@@ -70,16 +75,18 @@ public final class CpuInfoReader {
private static final Pattern TIME_IN_STATE_PATTERN =
Pattern.compile("(?<freqKHz>[0-9]+)\\s(?<time>[0-9]+)");
private static final long MILLIS_PER_CLOCK_TICK = 1000L / Os.sysconf(OsConstants._SC_CLK_TCK);
+ private static final long MIN_READ_INTERVAL_MILLISECONDS = 500;
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"FLAG_CPUSET_CATEGORY_"}, flag = true, value = {
FLAG_CPUSET_CATEGORY_TOP_APP,
FLAG_CPUSET_CATEGORY_BACKGROUND
})
- private @interface CpusetCategory{}
+ /** package **/ @interface CpusetCategory{}
// TODO(b/242722241): Protect updatable variables with a local lock.
private final File mCpusetDir;
+ private final long mMinReadIntervalMillis;
private final SparseIntArray mCpusetCategoriesByCpus = new SparseIntArray();
private final SparseArray<File> mCpuFreqPolicyDirsById = new SparseArray<>();
private final SparseArray<StaticPolicyInfo> mStaticPolicyInfoById = new SparseArray<>();
@@ -90,16 +97,20 @@ public final class CpuInfoReader {
private SparseArray<CpuUsageStats> mCumulativeCpuUsageStats = new SparseArray<>();
private boolean mIsEnabled;
private boolean mHasTimeInStateFile;
+ private long mLastReadUptimeMillis;
+ private SparseArray<CpuInfo> mLastReadCpuInfos;
public CpuInfoReader() {
- this(new File(CPUSET_DIR_PATH), new File(CPUFREQ_DIR_PATH), new File(PROC_STAT_FILE_PATH));
+ this(new File(CPUSET_DIR_PATH), new File(CPUFREQ_DIR_PATH), new File(PROC_STAT_FILE_PATH),
+ MIN_READ_INTERVAL_MILLISECONDS);
}
@VisibleForTesting
- CpuInfoReader(File cpusetDir, File cpuFreqDir, File procStatFile) {
+ CpuInfoReader(File cpusetDir, File cpuFreqDir, File procStatFile, long minReadIntervalMillis) {
mCpusetDir = cpusetDir;
mCpuFreqDir = cpuFreqDir;
mProcStatFile = procStatFile;
+ mMinReadIntervalMillis = minReadIntervalMillis;
}
/**
@@ -167,6 +178,16 @@ public final class CpuInfoReader {
if (!mIsEnabled) {
return null;
}
+ long uptimeMillis = SystemClock.uptimeMillis();
+ if (mLastReadUptimeMillis > 0
+ && uptimeMillis - mLastReadUptimeMillis < mMinReadIntervalMillis) {
+ Slogf.w(TAG, "Skipping reading from device and returning the last read CpuInfos. "
+ + "Last read was %d ms ago, min read interval is %d ms",
+ uptimeMillis - mLastReadUptimeMillis, mMinReadIntervalMillis);
+ return mLastReadCpuInfos;
+ }
+ mLastReadUptimeMillis = uptimeMillis;
+ mLastReadCpuInfos = null;
SparseArray<CpuUsageStats> cpuUsageStatsByCpus = readLatestCpuUsageStats();
if (cpuUsageStatsByCpus == null || cpuUsageStatsByCpus.size() == 0) {
Slogf.e(TAG, "Failed to read latest CPU usage stats");
@@ -202,6 +223,12 @@ public final class CpuInfoReader {
+ " policy ID %d", policyId);
continue;
}
+ if (curFreqKHz > maxFreqKHz) {
+ Slogf.w(TAG, "Current CPU frequency (%d) is greater than maximum CPU frequency"
+ + " (%d) for policy ID (%d). Skipping CPU frequency policy", curFreqKHz,
+ maxFreqKHz, policyId);
+ continue;
+ }
for (int coreIdx = 0; coreIdx < staticPolicyInfo.relatedCpuCores.size(); coreIdx++) {
int relatedCpuCore = staticPolicyInfo.relatedCpuCores.get(coreIdx);
CpuInfo prevCpuInfo = cpuInfoByCpus.get(relatedCpuCore);
@@ -241,9 +268,73 @@ public final class CpuInfoReader {
}
}
}
+ mLastReadCpuInfos = cpuInfoByCpus;
return cpuInfoByCpus;
}
+ /** Dumps the current state. */
+ public void dump(IndentingPrintWriter writer) {
+ writer.printf("*%s*\n", getClass().getSimpleName());
+ writer.increaseIndent(); // Add intend for the outermost block.
+
+ writer.printf("mCpusetDir = %s\n", mCpusetDir.getAbsolutePath());
+ writer.printf("mCpuFreqDir = %s\n", mCpuFreqDir.getAbsolutePath());
+ writer.printf("mProcStatFile = %s\n", mProcStatFile.getAbsolutePath());
+ writer.printf("mIsEnabled = %s\n", mIsEnabled);
+ writer.printf("mHasTimeInStateFile = %s\n", mHasTimeInStateFile);
+ writer.printf("mLastReadUptimeMillis = %d\n", mLastReadUptimeMillis);
+ writer.printf("mMinReadIntervalMillis = %d\n", mMinReadIntervalMillis);
+
+ writer.printf("Cpuset categories by CPU core:\n");
+ writer.increaseIndent();
+ for (int i = 0; i < mCpusetCategoriesByCpus.size(); i++) {
+ writer.printf("CPU core id = %d, %s\n", mCpusetCategoriesByCpus.keyAt(i),
+ toCpusetCategoriesStr(mCpusetCategoriesByCpus.valueAt(i)));
+ }
+ writer.decreaseIndent();
+
+ writer.println("Cpu frequency policy directories by policy id:");
+ writer.increaseIndent();
+ for (int i = 0; i < mCpuFreqPolicyDirsById.size(); i++) {
+ writer.printf("Policy id = %d, Dir = %s\n", mCpuFreqPolicyDirsById.keyAt(i),
+ mCpuFreqPolicyDirsById.valueAt(i));
+ }
+ writer.decreaseIndent();
+
+ writer.println("Static cpu frequency policy infos by policy id:");
+ writer.increaseIndent();
+ for (int i = 0; i < mStaticPolicyInfoById.size(); i++) {
+ writer.printf("Policy id = %d, %s\n", mStaticPolicyInfoById.keyAt(i),
+ mStaticPolicyInfoById.valueAt(i));
+ }
+ writer.decreaseIndent();
+
+ writer.println("Cpu time in frequency state by policy id:");
+ writer.increaseIndent();
+ for (int i = 0; i < mTimeInStateByPolicyId.size(); i++) {
+ writer.printf("Policy id = %d, Time(millis) in state by CPU frequency(KHz) = %s\n",
+ mTimeInStateByPolicyId.keyAt(i), mTimeInStateByPolicyId.valueAt(i));
+ }
+ writer.decreaseIndent();
+
+ writer.println("Last read CPU infos:");
+ writer.increaseIndent();
+ for (int i = 0; i < mLastReadCpuInfos.size(); i++) {
+ writer.printf("%s\n", mLastReadCpuInfos.valueAt(i));
+ }
+ writer.decreaseIndent();
+
+ writer.println("Latest cumulative CPU usage stats by CPU core:");
+ writer.increaseIndent();
+ for (int i = 0; i < mCumulativeCpuUsageStats.size(); i++) {
+ writer.printf("CPU core id = %d, %s\n", mCumulativeCpuUsageStats.keyAt(i),
+ mCumulativeCpuUsageStats.valueAt(i));
+ }
+ writer.decreaseIndent();
+
+ writer.decreaseIndent(); // Remove intend for the outermost block.
+ }
+
/**
* Sets the CPU frequency for testing.
*
@@ -496,6 +587,9 @@ public final class CpuInfoReader {
for (int i = 0; i < timeInState.size(); i++) {
totalTimeInState += timeInState.valueAt(i);
}
+ if (totalTimeInState == 0) {
+ return CpuInfo.MISSING_FREQUENCY;
+ }
double avgFreqKHz = 0;
for (int i = 0; i < timeInState.size(); i++) {
avgFreqKHz += (timeInState.keyAt(i) * timeInState.valueAt(i)) / totalTimeInState;
@@ -624,16 +718,29 @@ public final class CpuInfoReader {
@CpusetCategory
public final int cpusetCategories;
public final boolean isOnline;
+ public final long maxCpuFreqKHz;
// Values in the below fields may be missing when a CPU core is offline.
public final long curCpuFreqKHz;
- public final long maxCpuFreqKHz;
public final long avgTimeInStateCpuFreqKHz;
@Nullable
public final CpuUsageStats latestCpuUsageStats;
+ private long mNormalizedAvailableCpuFreqKHz;
+
CpuInfo(int cpuCore, @CpusetCategory int cpusetCategories, boolean isOnline,
long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz,
CpuUsageStats latestCpuUsageStats) {
+ this(cpuCore, cpusetCategories, isOnline, curCpuFreqKHz, maxCpuFreqKHz,
+ avgTimeInStateCpuFreqKHz, /* normalizedAvailableCpuFreqKHz= */ 0,
+ latestCpuUsageStats);
+ this.mNormalizedAvailableCpuFreqKHz = computeNormalizedAvailableCpuFreqKHz();
+ }
+
+ // Should be used only for testing.
+ @VisibleForTesting
+ CpuInfo(int cpuCore, @CpusetCategory int cpusetCategories, boolean isOnline,
+ long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz,
+ long normalizedAvailableCpuFreqKHz, CpuUsageStats latestCpuUsageStats) {
this.cpuCore = cpuCore;
this.cpusetCategories = cpusetCategories;
this.isOnline = isOnline;
@@ -641,6 +748,11 @@ public final class CpuInfoReader {
this.maxCpuFreqKHz = maxCpuFreqKHz;
this.avgTimeInStateCpuFreqKHz = avgTimeInStateCpuFreqKHz;
this.latestCpuUsageStats = latestCpuUsageStats;
+ this.mNormalizedAvailableCpuFreqKHz = normalizedAvailableCpuFreqKHz;
+ }
+
+ public long getNormalizedAvailableCpuFreqKHz() {
+ return mNormalizedAvailableCpuFreqKHz;
}
@Override
@@ -657,6 +769,8 @@ public final class CpuInfoReader {
.append(avgTimeInStateCpuFreqKHz == MISSING_FREQUENCY ? "missing"
: avgTimeInStateCpuFreqKHz)
.append(", latestCpuUsageStats = ").append(latestCpuUsageStats)
+ .append(", mNormalizedAvailableCpuFreqKHz = ")
+ .append(mNormalizedAvailableCpuFreqKHz)
.append(" }").toString();
}
@@ -673,13 +787,32 @@ public final class CpuInfoReader {
&& isOnline == other.isOnline && curCpuFreqKHz == other.curCpuFreqKHz
&& maxCpuFreqKHz == other.maxCpuFreqKHz
&& avgTimeInStateCpuFreqKHz == other.avgTimeInStateCpuFreqKHz
- && latestCpuUsageStats.equals(other.latestCpuUsageStats);
+ && latestCpuUsageStats.equals(other.latestCpuUsageStats)
+ && mNormalizedAvailableCpuFreqKHz == other.mNormalizedAvailableCpuFreqKHz;
}
@Override
public int hashCode() {
return Objects.hash(cpuCore, cpusetCategories, isOnline, curCpuFreqKHz, maxCpuFreqKHz,
- avgTimeInStateCpuFreqKHz, latestCpuUsageStats);
+ avgTimeInStateCpuFreqKHz, latestCpuUsageStats, mNormalizedAvailableCpuFreqKHz);
+ }
+
+ private long computeNormalizedAvailableCpuFreqKHz() {
+ if (!isOnline) {
+ return MISSING_FREQUENCY;
+ }
+ long totalTimeMillis = latestCpuUsageStats.getTotalTimeMillis();
+ if (totalTimeMillis == 0) {
+ Slogf.wtf(TAG, "Total CPU time millis is 0. This shouldn't happen unless stats are"
+ + " polled too frequently");
+ return MISSING_FREQUENCY;
+ }
+ double nonIdlePercent = 100.0 * (totalTimeMillis
+ - (double) latestCpuUsageStats.idleTimeMillis) / totalTimeMillis;
+ long curFreqKHz = avgTimeInStateCpuFreqKHz == MISSING_FREQUENCY
+ ? curCpuFreqKHz : avgTimeInStateCpuFreqKHz;
+ double availablePercent = 100.0 - (nonIdlePercent * curFreqKHz / maxCpuFreqKHz);
+ return (long) ((availablePercent * maxCpuFreqKHz) / 100.0);
}
}
@@ -712,7 +845,7 @@ public final class CpuInfoReader {
this.guestNiceTimeMillis = guestNiceTimeMillis;
}
- public long getTotalTime() {
+ public long getTotalTimeMillis() {
return userTimeMillis + niceTimeMillis + systemTimeMillis + idleTimeMillis
+ iowaitTimeMillis + irqTimeMillis + softirqTimeMillis + stealTimeMillis
+ guestTimeMillis + guestNiceTimeMillis;
@@ -796,8 +929,8 @@ public final class CpuInfoReader {
@Override
public String toString() {
- return "FrequencyPair{cpuFreqKHz=" + cpuFreqKHz + ", scalingFreqKHz=" + scalingFreqKHz
- + '}';
+ return "FrequencyPair{cpuFreqKHz = " + cpuFreqKHz + ", scalingFreqKHz = "
+ + scalingFreqKHz + '}';
}
}
@@ -812,7 +945,7 @@ public final class CpuInfoReader {
@Override
public String toString() {
- return "StaticPolicyInfo{maxCpuFreqPair=" + maxCpuFreqPair + ", relatedCpuCores="
+ return "StaticPolicyInfo{maxCpuFreqPair = " + maxCpuFreqPair + ", relatedCpuCores = "
+ relatedCpuCores + '}';
}
}
@@ -831,9 +964,9 @@ public final class CpuInfoReader {
@Override
public String toString() {
- return "DynamicPolicyInfo{curCpuFreqPair=" + curCpuFreqPair
- + ", avgTimeInStateCpuFreqKHz=" + avgTimeInStateCpuFreqKHz
- + ", affectedCpuCores=" + affectedCpuCores + '}';
+ return "DynamicPolicyInfo{curCpuFreqPair = " + curCpuFreqPair
+ + ", avgTimeInStateCpuFreqKHz = " + avgTimeInStateCpuFreqKHz
+ + ", affectedCpuCores = " + affectedCpuCores + '}';
}
}
}
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java
index 4eefe5c8cad5..df8cfad4ab03 100644
--- a/services/core/java/com/android/server/cpu/CpuMonitorService.java
+++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java
@@ -18,15 +18,33 @@ package com.android.server.cpu;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL;
+import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND;
+import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_BACKGROUND;
+import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_TOP_APP;
+
+import android.annotation.Nullable;
import android.content.Context;
import android.os.Binder;
-import android.util.ArrayMap;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.SystemClock;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.util.SparseArrayMap;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
+import com.android.server.ServiceThread;
import com.android.server.SystemService;
+import com.android.server.Watchdog;
import com.android.server.utils.PriorityDump;
import com.android.server.utils.Slogf;
@@ -34,28 +52,55 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
/** Service to monitor CPU availability and usage. */
public final class CpuMonitorService extends SystemService {
static final String TAG = CpuMonitorService.class.getSimpleName();
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- // TODO(b/242722241): Make this a resource overlay property.
- // Maintain 3 monitoring intervals:
- // * One to poll very frequently when mCpuAvailabilityCallbackInfoByCallbacks are available and
- // CPU availability is above a threshold (such as at least 10% of CPU is available).
- // * One to poll less frequently when mCpuAvailabilityCallbackInfoByCallbacks are available
- // and CPU availability is below a threshold (such as less than 10% of CPU is available).
- // * One to poll very less frequently when no callbacks are available and the build is either
- // user-debug or eng. This will be useful for debugging in development environment.
- static final int DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS = 5_000;
+ // TODO(b/267500110): Make these constants resource overlay properties.
+ /** Default monitoring interval when no monitoring is in progress. */
+ static final long DEFAULT_MONITORING_INTERVAL_MILLISECONDS = -1;
+ /** Monitoring interval when callbacks are registered and the CPU load is normal. */
+ private static final long NORMAL_MONITORING_INTERVAL_MILLISECONDS =
+ TimeUnit.SECONDS.toMillis(5);
+
+ /**
+ * Monitoring interval when no registered callbacks and the build is either user-debug or eng.
+ */
+ private static final long DEBUG_MONITORING_INTERVAL_MILLISECONDS = TimeUnit.MINUTES.toMillis(1);
+ /**
+ * Size of the in-memory cache relative to the current uptime.
+ *
+ * On user-debug or eng builds, continuously cache stats with a bigger cache size for debugging
+ * purposes.
+ */
+ private static final long CACHE_DURATION_MILLISECONDS = Build.IS_USERDEBUG || Build.IS_ENG
+ ? TimeUnit.MINUTES.toMillis(30) : TimeUnit.MINUTES.toMillis(10);
+ // TODO(b/267500110): Investigate whether this duration should change when the monitoring
+ // interval is updated. When the CPU is under heavy load, the monitoring will happen less
+ // frequently. Should this duration be increased as well when this happens?
+ private static final long LATEST_AVAILABILITY_DURATION_MILLISECONDS =
+ TimeUnit.SECONDS.toMillis(30);
private final Context mContext;
+ private final HandlerThread mHandlerThread;
+ private final CpuInfoReader mCpuInfoReader;
+ private final boolean mShouldDebugMonitor;
+ private final long mNormalMonitoringIntervalMillis;
+ private final long mDebugMonitoringIntervalMillis;
+ private final long mLatestAvailabilityDurationMillis;
private final Object mLock = new Object();
@GuardedBy("mLock")
- private final ArrayMap<CpuMonitorInternal.CpuAvailabilityCallback, CpuAvailabilityCallbackInfo>
- mCpuAvailabilityCallbackInfoByCallbacks = new ArrayMap<>();
+ private final SparseArrayMap<CpuMonitorInternal.CpuAvailabilityCallback,
+ CpuAvailabilityCallbackInfo> mAvailabilityCallbackInfosByCallbacksByCpuset;
@GuardedBy("mLock")
- private long mMonitoringIntervalMilliseconds = DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS;
+ private final SparseArray<CpusetInfo> mCpusetInfosByCpuset;
+ private final Runnable mMonitorCpuStats = this::monitorCpuStats;
+
+ @GuardedBy("mLock")
+ private long mCurrentMonitoringIntervalMillis = DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
+ private Handler mHandler;
private final CpuMonitorInternal mLocalService = new CpuMonitorInternal() {
@Override
@@ -63,89 +108,389 @@ public final class CpuMonitorService extends SystemService {
CpuAvailabilityMonitoringConfig config, CpuAvailabilityCallback callback) {
Objects.requireNonNull(callback, "Callback must be non-null");
Objects.requireNonNull(config, "Config must be non-null");
+ CpuAvailabilityCallbackInfo callbackInfo;
synchronized (mLock) {
- if (mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) {
- Slogf.i(TAG, "Overwriting the existing CpuAvailabilityCallback %s",
- mCpuAvailabilityCallbackInfoByCallbacks.get(callback));
- // TODO(b/242722241): Overwrite any internal cache (will be added in future CLs)
- // that maps callbacks based on the CPU availability thresholds.
- }
- CpuAvailabilityCallbackInfo info = new CpuAvailabilityCallbackInfo(config,
- executor);
- mCpuAvailabilityCallbackInfoByCallbacks.put(callback, info);
- if (DEBUG) {
- Slogf.d(TAG, "Added a CPU availability callback: %s", info);
+ // Verify all CPUSET entries before adding the callback because this will help
+ // delete any previously added callback for a different CPUSET.
+ for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
+ int cpuset = mAvailabilityCallbackInfosByCallbacksByCpuset.keyAt(i);
+ callbackInfo = mAvailabilityCallbackInfosByCallbacksByCpuset.delete(cpuset,
+ callback);
+ if (callbackInfo != null) {
+ Slogf.i(TAG, "Overwriting the existing %s", callbackInfo);
+ }
}
+ callbackInfo = newCallbackInfoLocked(config, callback, executor);
+ }
+ asyncNotifyMonitoringIntervalChangeToClient(callbackInfo);
+ if (DEBUG) {
+ Slogf.d(TAG, "Successfully added %s", callbackInfo);
}
- // TODO(b/242722241):
- // * On the executor or on the handler thread, call the callback with the latest CPU
- // availability info and monitoring interval.
- // * Monitor the CPU stats more frequently when the first callback is added.
}
@Override
public void removeCpuAvailabilityCallback(CpuAvailabilityCallback callback) {
synchronized (mLock) {
- if (!mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) {
- Slogf.i(TAG, "CpuAvailabilityCallback was not previously added."
- + " Ignoring the remove request");
- return;
- }
- CpuAvailabilityCallbackInfo info =
- mCpuAvailabilityCallbackInfoByCallbacks.remove(callback);
- if (DEBUG) {
- Slogf.d(TAG, "Removed a CPU availability callback: %s", info);
+ for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
+ int cpuset = mAvailabilityCallbackInfosByCallbacksByCpuset.keyAt(i);
+ CpuAvailabilityCallbackInfo callbackInfo =
+ mAvailabilityCallbackInfosByCallbacksByCpuset.delete(cpuset, callback);
+ if (callbackInfo != null) {
+ if (DEBUG) {
+ Slogf.d(TAG, "Successfully removed %s", callbackInfo);
+ }
+ checkAndStopMonitoringLocked();
+ return;
+ }
}
+ Slogf.w(TAG, "CpuAvailabilityCallback was not previously added. Ignoring the remove"
+ + " request");
}
- // TODO(b/242722241): Increase CPU monitoring interval when all callbacks are removed.
}
};
public CpuMonitorService(Context context) {
+ this(context, new CpuInfoReader(), new ServiceThread(TAG,
+ Process.THREAD_PRIORITY_BACKGROUND, /* allowIo= */ true),
+ Build.IS_USERDEBUG || Build.IS_ENG, NORMAL_MONITORING_INTERVAL_MILLISECONDS,
+ DEBUG_MONITORING_INTERVAL_MILLISECONDS, LATEST_AVAILABILITY_DURATION_MILLISECONDS);
+ }
+
+ @VisibleForTesting
+ CpuMonitorService(Context context, CpuInfoReader cpuInfoReader, HandlerThread handlerThread,
+ boolean shouldDebugMonitor, long normalMonitoringIntervalMillis,
+ long debugMonitoringIntervalMillis, long latestAvailabilityDurationMillis) {
super(context);
mContext = context;
+ mHandlerThread = handlerThread;
+ mShouldDebugMonitor = shouldDebugMonitor;
+ mNormalMonitoringIntervalMillis = normalMonitoringIntervalMillis;
+ mDebugMonitoringIntervalMillis = debugMonitoringIntervalMillis;
+ mLatestAvailabilityDurationMillis = latestAvailabilityDurationMillis;
+ mCpuInfoReader = cpuInfoReader;
+ mCpusetInfosByCpuset = new SparseArray<>(2);
+ mCpusetInfosByCpuset.append(CPUSET_ALL, new CpusetInfo(CPUSET_ALL));
+ mCpusetInfosByCpuset.append(CPUSET_BACKGROUND, new CpusetInfo(CPUSET_BACKGROUND));
+ mAvailabilityCallbackInfosByCallbacksByCpuset = new SparseArrayMap<>();
}
@Override
public void onStart() {
+ // Initialize CPU info reader and perform the first read to make sure the CPU stats are
+ // readable without any issues.
+ if (!mCpuInfoReader.init() || mCpuInfoReader.readCpuInfos() == null) {
+ Slogf.wtf(TAG, "Failed to initialize CPU info reader. This happens when the CPU "
+ + "frequency stats are not available or the sysfs interface has changed in "
+ + "the Kernel. Cannot monitor CPU without these stats. Terminating CPU monitor "
+ + "service");
+ return;
+ }
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
publishLocalService(CpuMonitorInternal.class, mLocalService);
publishBinderService("cpu_monitor", new CpuMonitorBinder(), /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL);
+ Watchdog.getInstance().addThread(mHandler);
+ synchronized (mLock) {
+ if (mShouldDebugMonitor && !mHandler.hasCallbacks(mMonitorCpuStats)) {
+ mCurrentMonitoringIntervalMillis = mDebugMonitoringIntervalMillis;
+ Slogf.i(TAG, "Starting debug monitoring");
+ mHandler.post(mMonitorCpuStats);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ long getCurrentMonitoringIntervalMillis() {
+ synchronized (mLock) {
+ return mCurrentMonitoringIntervalMillis;
+ }
}
private void doDump(IndentingPrintWriter writer) {
writer.printf("*%s*\n", getClass().getSimpleName());
writer.increaseIndent();
+ mCpuInfoReader.dump(writer);
+ writer.printf("mShouldDebugMonitor = %s\n", mShouldDebugMonitor ? "Yes" : "No");
+ writer.printf("mNormalMonitoringIntervalMillis = %d\n", mNormalMonitoringIntervalMillis);
+ writer.printf("mDebugMonitoringIntervalMillis = %d\n", mDebugMonitoringIntervalMillis);
+ writer.printf("mLatestAvailabilityDurationMillis = %d\n",
+ mLatestAvailabilityDurationMillis);
synchronized (mLock) {
- writer.printf("CPU monitoring interval: %d ms\n", mMonitoringIntervalMilliseconds);
- if (!mCpuAvailabilityCallbackInfoByCallbacks.isEmpty()) {
+ writer.printf("mCurrentMonitoringIntervalMillis = %d\n",
+ mCurrentMonitoringIntervalMillis);
+ if (hasClientCallbacksLocked()) {
writer.println("CPU availability change callbacks:");
writer.increaseIndent();
- for (int i = 0; i < mCpuAvailabilityCallbackInfoByCallbacks.size(); i++) {
- writer.printf("%s: %s\n", mCpuAvailabilityCallbackInfoByCallbacks.keyAt(i),
- mCpuAvailabilityCallbackInfoByCallbacks.valueAt(i));
+ mAvailabilityCallbackInfosByCallbacksByCpuset.forEach(
+ (callbackInfo) -> writer.printf("%s\n", callbackInfo));
+ writer.decreaseIndent();
+ }
+ if (mCpusetInfosByCpuset.size() > 0) {
+ writer.println("Cpuset infos:");
+ writer.increaseIndent();
+ for (int i = 0; i < mCpusetInfosByCpuset.size(); i++) {
+ writer.printf("%s\n", mCpusetInfosByCpuset.valueAt(i));
}
writer.decreaseIndent();
}
}
- // TODO(b/242722241): Print the recent past CPU stats.
writer.decreaseIndent();
}
+ private void monitorCpuStats() {
+ long uptimeMillis = SystemClock.uptimeMillis();
+ // Remove duplicate callbacks caused by switching form debug to normal monitoring.
+ // The removal of the duplicate callback done in the {@link newCallbackInfoLocked} method
+ // may result in a no-op when a duplicate execution of this callback has already started
+ // on the handler thread.
+ mHandler.removeCallbacks(mMonitorCpuStats);
+ SparseArray<CpuInfoReader.CpuInfo> cpuInfosByCoreId = mCpuInfoReader.readCpuInfos();
+ if (cpuInfosByCoreId == null) {
+ // This shouldn't happen because the CPU infos are read & verified during
+ // the {@link onStart} call.
+ Slogf.wtf(TAG, "Failed to read CPU info from device");
+ synchronized (mLock) {
+ stopMonitoringCpuStatsLocked();
+ }
+ // Monitoring is stopped but no client callback is removed.
+ // TODO(b/267500110): Identify whether the clients should be notified about this state.
+ return;
+ }
+
+ synchronized (mLock) {
+ // 1. Populate the {@link mCpusetInfosByCpuset} with the latest cpuInfo.
+ for (int i = 0; i < cpuInfosByCoreId.size(); i++) {
+ CpuInfoReader.CpuInfo cpuInfo = cpuInfosByCoreId.valueAt(i);
+ for (int j = 0; j < mCpusetInfosByCpuset.size(); j++) {
+ mCpusetInfosByCpuset.valueAt(j).appendCpuInfo(uptimeMillis, cpuInfo);
+ }
+ }
+
+ // 2. Verify whether any monitoring thresholds are crossed and notify the corresponding
+ // clients.
+ for (int i = 0; i < mCpusetInfosByCpuset.size(); i++) {
+ CpusetInfo cpusetInfo = mCpusetInfosByCpuset.valueAt(i);
+ cpusetInfo.populateLatestCpuAvailabilityInfo(uptimeMillis,
+ mLatestAvailabilityDurationMillis);
+ checkClientThresholdsAndNotifyLocked(cpusetInfo);
+ }
+
+ // TODO(b/267500110): Detect heavy CPU load. On detecting heavy CPU load, increase
+ // the monitoring interval and notify the clients.
+
+ // 3. Continue monitoring only when either there is at least one registered client
+ // callback or debug monitoring is enabled.
+ if (mCurrentMonitoringIntervalMillis > 0
+ && (hasClientCallbacksLocked() || mShouldDebugMonitor)) {
+ mHandler.postAtTime(mMonitorCpuStats,
+ uptimeMillis + mCurrentMonitoringIntervalMillis);
+ } else {
+ stopMonitoringCpuStatsLocked();
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void checkClientThresholdsAndNotifyLocked(CpusetInfo cpusetInfo) {
+ int prevAvailabilityPercent = cpusetInfo.getPrevCpuAvailabilityPercent();
+ CpuAvailabilityInfo latestAvailabilityInfo = cpusetInfo.getLatestCpuAvailabilityInfo();
+ if (latestAvailabilityInfo == null || prevAvailabilityPercent < 0
+ || mAvailabilityCallbackInfosByCallbacksByCpuset.numElementsForKey(
+ cpusetInfo.cpuset) == 0) {
+ // When either the current or the previous CPU availability percents are
+ // missing, skip the current cpuset as there is not enough data to verify
+ // whether the CPU availability has crossed any monitoring threshold.
+ return;
+ }
+ for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
+ for (int j = 0; j < mAvailabilityCallbackInfosByCallbacksByCpuset.numElementsForKeyAt(
+ i); j++) {
+ CpuAvailabilityCallbackInfo callbackInfo =
+ mAvailabilityCallbackInfosByCallbacksByCpuset.valueAt(i, j);
+ if (callbackInfo.config.cpuset != cpusetInfo.cpuset) {
+ continue;
+ }
+ if (didCrossAnyThreshold(prevAvailabilityPercent,
+ latestAvailabilityInfo.latestAvgAvailabilityPercent,
+ callbackInfo.config.getThresholds())) {
+ asyncNotifyCpuAvailabilityToClient(latestAvailabilityInfo, callbackInfo);
+ }
+ }
+ }
+ }
+
+ private void asyncNotifyMonitoringIntervalChangeToClient(
+ CpuAvailabilityCallbackInfo callbackInfo) {
+ if (callbackInfo.executor == null) {
+ mHandler.post(callbackInfo.notifyMonitoringIntervalChangeRunnable);
+ } else {
+ callbackInfo.executor.execute(callbackInfo.notifyMonitoringIntervalChangeRunnable);
+ }
+ }
+
+ private void asyncNotifyCpuAvailabilityToClient(CpuAvailabilityInfo availabilityInfo,
+ CpuAvailabilityCallbackInfo callbackInfo) {
+ callbackInfo.notifyCpuAvailabilityChangeRunnable.prepare(availabilityInfo);
+ if (callbackInfo.executor == null) {
+ mHandler.post(callbackInfo.notifyCpuAvailabilityChangeRunnable);
+ } else {
+ callbackInfo.executor.execute(callbackInfo.notifyCpuAvailabilityChangeRunnable);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private CpuAvailabilityCallbackInfo newCallbackInfoLocked(
+ CpuAvailabilityMonitoringConfig config,
+ CpuMonitorInternal.CpuAvailabilityCallback callback, Executor executor) {
+ CpuAvailabilityCallbackInfo callbackInfo = new CpuAvailabilityCallbackInfo(this, config,
+ callback, executor);
+ String cpusetStr = CpuAvailabilityMonitoringConfig.toCpusetString(
+ callbackInfo.config.cpuset);
+ CpusetInfo cpusetInfo = mCpusetInfosByCpuset.get(callbackInfo.config.cpuset);
+ Preconditions.checkState(cpusetInfo != null, "Missing cpuset info for cpuset %s",
+ cpusetStr);
+ boolean hasExistingClientCallbacks = hasClientCallbacksLocked();
+ mAvailabilityCallbackInfosByCallbacksByCpuset.add(callbackInfo.config.cpuset,
+ callbackInfo.callback, callbackInfo);
+ if (DEBUG) {
+ Slogf.d(TAG, "Added a CPU availability callback: %s", callbackInfo);
+ }
+ CpuAvailabilityInfo latestInfo = cpusetInfo.getLatestCpuAvailabilityInfo();
+ if (latestInfo != null) {
+ asyncNotifyCpuAvailabilityToClient(latestInfo, callbackInfo);
+ }
+ if (hasExistingClientCallbacks && mHandler.hasCallbacks(mMonitorCpuStats)) {
+ return callbackInfo;
+ }
+ // Remove existing callbacks to ensure any debug monitoring (if started) is stopped before
+ // starting normal monitoring.
+ mHandler.removeCallbacks(mMonitorCpuStats);
+ mCurrentMonitoringIntervalMillis = mNormalMonitoringIntervalMillis;
+ mHandler.post(mMonitorCpuStats);
+ return callbackInfo;
+ }
+
+ @GuardedBy("mLock")
+ private void checkAndStopMonitoringLocked() {
+ if (hasClientCallbacksLocked()) {
+ return;
+ }
+ if (mShouldDebugMonitor) {
+ if (DEBUG) {
+ Slogf.e(TAG, "Switching to debug monitoring");
+ }
+ mCurrentMonitoringIntervalMillis = mDebugMonitoringIntervalMillis;
+ } else {
+ stopMonitoringCpuStatsLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean hasClientCallbacksLocked() {
+ for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
+ if (mAvailabilityCallbackInfosByCallbacksByCpuset.numElementsForKeyAt(i) > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ private void stopMonitoringCpuStatsLocked() {
+ mHandler.removeCallbacks(mMonitorCpuStats);
+ mCurrentMonitoringIntervalMillis = DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
+ // When the monitoring is stopped, the latest CPU availability info and the snapshots in
+ // {@code mCpusetInfosByCpuset} will become obsolete soon. So, remove them.
+ for (int i = 0; i < mCpusetInfosByCpuset.size(); i++) {
+ mCpusetInfosByCpuset.valueAt(i).clear();
+ }
+ }
+
+ private static boolean containsCpuset(@CpuInfoReader.CpusetCategory int cpusetCategories,
+ @CpuAvailabilityMonitoringConfig.Cpuset int expectedCpuset) {
+ switch (expectedCpuset) {
+ case CPUSET_ALL:
+ return (cpusetCategories & FLAG_CPUSET_CATEGORY_TOP_APP) != 0;
+ case CPUSET_BACKGROUND:
+ return (cpusetCategories & FLAG_CPUSET_CATEGORY_BACKGROUND) != 0;
+ default:
+ Slogf.wtf(TAG, "Provided invalid expectedCpuset %d", expectedCpuset);
+ }
+ return false;
+ }
+
+ private static boolean didCrossAnyThreshold(int prevAvailabilityPercent,
+ int curAvailabilityPercent, IntArray thresholds) {
+ if (prevAvailabilityPercent == curAvailabilityPercent) {
+ return false;
+ }
+ for (int i = 0; i < thresholds.size(); i++) {
+ int threshold = thresholds.get(i);
+ // TODO(b/267500110): Identify whether or not the clients need to be notified when
+ // the CPU availability jumps too frequently around the provided thresholds.
+ // A. Should the client be notified twice - once when the availability reaches
+ // the threshold and once when it moves away (increase/decrease) from the threshold
+ // immediately?
+ // B. Should there be some sort of rate-limiting to avoid notifying the client too
+ // frequently? Should the client be able to config the rate-limit?
+ if (prevAvailabilityPercent < threshold && curAvailabilityPercent >= threshold) {
+ return true;
+ }
+ if (prevAvailabilityPercent >= threshold && curAvailabilityPercent < threshold) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static final class CpuAvailabilityCallbackInfo {
+ public final CpuMonitorService service;
public final CpuAvailabilityMonitoringConfig config;
+ public final CpuMonitorInternal.CpuAvailabilityCallback callback;
+ @Nullable
public final Executor executor;
+ public final Runnable notifyMonitoringIntervalChangeRunnable = new Runnable() {
+ @Override
+ public void run() {
+ callback.onMonitoringIntervalChanged(service.getCurrentMonitoringIntervalMillis());
+ }
+ };
+ public final NotifyCpuAvailabilityChangeRunnable notifyCpuAvailabilityChangeRunnable =
+ new NotifyCpuAvailabilityChangeRunnable();
- CpuAvailabilityCallbackInfo(CpuAvailabilityMonitoringConfig config,
- Executor executor) {
+ CpuAvailabilityCallbackInfo(CpuMonitorService service,
+ CpuAvailabilityMonitoringConfig config,
+ CpuMonitorInternal.CpuAvailabilityCallback callback, @Nullable Executor executor) {
+ this.service = service;
this.config = config;
+ this.callback = callback;
this.executor = executor;
}
@Override
public String toString() {
- return "CpuAvailabilityCallbackInfo{" + "config=" + config + ", mExecutor=" + executor
- + '}';
+ return "CpuAvailabilityCallbackInfo{config = " + config + ", callback = " + callback
+ + ", mExecutor = " + executor + '}';
+ }
+
+ private final class NotifyCpuAvailabilityChangeRunnable implements Runnable {
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private CpuAvailabilityInfo mCpuAvailabilityInfo;
+
+ public void prepare(CpuAvailabilityInfo cpuAvailabilityInfo) {
+ synchronized (mLock) {
+ mCpuAvailabilityInfo = cpuAvailabilityInfo;
+ }
+ }
+
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ callback.onAvailabilityChanged(mCpuAvailabilityInfo);
+ }
+ }
}
}
@@ -170,4 +515,157 @@ public final class CpuMonitorService extends SystemService {
PriorityDump.dump(mPriorityDumper, fd, pw, args);
}
}
+
+ private static final class CpusetInfo {
+ @CpuAvailabilityMonitoringConfig.Cpuset
+ public final int cpuset;
+ private final LongSparseArray<Snapshot> mSnapshotsByUptime;
+ @Nullable
+ private CpuAvailabilityInfo mLatestCpuAvailabilityInfo;
+
+ CpusetInfo(int cpuset) {
+ this.cpuset = cpuset;
+ mSnapshotsByUptime = new LongSparseArray<>();
+ }
+
+ public void appendCpuInfo(long uptimeMillis, CpuInfoReader.CpuInfo cpuInfo) {
+ if (!containsCpuset(cpuInfo.cpusetCategories, cpuset)) {
+ return;
+ }
+ Snapshot currentSnapshot = mSnapshotsByUptime.get(uptimeMillis);
+ if (currentSnapshot == null) {
+ currentSnapshot = new Snapshot(uptimeMillis);
+ mSnapshotsByUptime.append(uptimeMillis, currentSnapshot);
+ if (mSnapshotsByUptime.size() > 0
+ && (uptimeMillis - mSnapshotsByUptime.valueAt(0).uptimeMillis)
+ > CACHE_DURATION_MILLISECONDS) {
+ mSnapshotsByUptime.removeAt(0);
+ }
+ }
+ currentSnapshot.appendCpuInfo(cpuInfo);
+ }
+
+ @Nullable
+ public CpuAvailabilityInfo getLatestCpuAvailabilityInfo() {
+ return mLatestCpuAvailabilityInfo;
+ }
+
+ public void populateLatestCpuAvailabilityInfo(long currentUptimeMillis,
+ long latestAvailabilityDurationMillis) {
+ int numSnapshots = mSnapshotsByUptime.size();
+ if (numSnapshots == 0) {
+ mLatestCpuAvailabilityInfo = null;
+ return;
+ }
+ Snapshot latestSnapshot = mSnapshotsByUptime.valueAt(numSnapshots - 1);
+ if (latestSnapshot.uptimeMillis != currentUptimeMillis) {
+ // When the cpuset has no stats available for the current polling, the uptime will
+ // mismatch. When this happens, return {@code null} to avoid returning stale
+ // information.
+ if (DEBUG) {
+ Slogf.d(TAG, "Skipping stale CPU availability information for cpuset %s",
+ CpuAvailabilityMonitoringConfig.toCpusetString(cpuset));
+ }
+ mLatestCpuAvailabilityInfo = null;
+ return;
+ }
+ // Avoid constructing {@link mLatestCpuAvailabilityInfo} if the uptime hasn't changed.
+ if (mLatestCpuAvailabilityInfo != null
+ && mLatestCpuAvailabilityInfo.dataTimestampUptimeMillis
+ == latestSnapshot.uptimeMillis) {
+ return;
+ }
+ long earliestUptimeMillis = currentUptimeMillis - latestAvailabilityDurationMillis;
+ mLatestCpuAvailabilityInfo = new CpuAvailabilityInfo(cpuset,
+ latestSnapshot.uptimeMillis, latestSnapshot.getAverageAvailableCpuFreqPercent(),
+ getCumulativeAvgAvailabilityPercent(earliestUptimeMillis),
+ latestAvailabilityDurationMillis);
+ }
+
+ public int getPrevCpuAvailabilityPercent() {
+ int numSnapshots = mSnapshotsByUptime.size();
+ if (numSnapshots < 2) {
+ return -1;
+ }
+ return mSnapshotsByUptime.valueAt(numSnapshots - 2).getAverageAvailableCpuFreqPercent();
+ }
+
+ private int getCumulativeAvgAvailabilityPercent(long earliestUptimeMillis) {
+ long totalAvailableCpuFreqKHz = 0;
+ long totalOnlineMaxCpuFreqKHz = 0;
+ int totalAccountedSnapshots = 0;
+ long earliestSeenUptimeMillis = Long.MAX_VALUE;
+ for (int i = mSnapshotsByUptime.size() - 1; i >= 0; i--) {
+ Snapshot snapshot = mSnapshotsByUptime.valueAt(i);
+ earliestSeenUptimeMillis = snapshot.uptimeMillis;
+ if (snapshot.uptimeMillis <= earliestUptimeMillis) {
+ break;
+ }
+ totalAccountedSnapshots++;
+ totalAvailableCpuFreqKHz += snapshot.totalNormalizedAvailableCpuFreqKHz;
+ totalOnlineMaxCpuFreqKHz += snapshot.totalOnlineMaxCpuFreqKHz;
+ }
+ // The cache must have at least 2 snapshots within the given duration and
+ // the {@link earliestSeenUptimeMillis} must be earlier than (i,e., less than) the given
+ // {@link earliestUptimeMillis}. Otherwise, the cache doesn't have enough data to
+ // calculate the cumulative average for the given duration.
+ // TODO(b/267500110): Investigate whether the cumulative average duration should be
+ // shrunk when not enough data points are available.
+ if (earliestSeenUptimeMillis > earliestUptimeMillis || totalAccountedSnapshots < 2) {
+ return CpuAvailabilityInfo.MISSING_CPU_AVAILABILITY_PERCENT;
+ }
+ return (int) ((totalAvailableCpuFreqKHz * 100.0) / totalOnlineMaxCpuFreqKHz);
+ }
+
+ public void clear() {
+ mLatestCpuAvailabilityInfo = null;
+ mSnapshotsByUptime.clear();
+ }
+
+ @Override
+ public String toString() {
+ return "CpusetInfo{cpuset = " + CpuAvailabilityMonitoringConfig.toCpusetString(cpuset)
+ + ", mSnapshotsByUptime = " + mSnapshotsByUptime
+ + ", mLatestCpuAvailabilityInfo = " + mLatestCpuAvailabilityInfo + '}';
+ }
+
+ private static final class Snapshot {
+ public final long uptimeMillis;
+ public int totalOnlineCpus;
+ public int totalOfflineCpus;
+ public long totalNormalizedAvailableCpuFreqKHz;
+ public long totalOnlineMaxCpuFreqKHz;
+ public long totalOfflineMaxCpuFreqKHz;
+
+ Snapshot(long uptimeMillis) {
+ this.uptimeMillis = uptimeMillis;
+ }
+
+ public void appendCpuInfo(CpuInfoReader.CpuInfo cpuInfo) {
+ if (!cpuInfo.isOnline) {
+ totalOfflineCpus++;
+ totalOfflineMaxCpuFreqKHz += cpuInfo.maxCpuFreqKHz;
+ return;
+ }
+ ++totalOnlineCpus;
+ totalNormalizedAvailableCpuFreqKHz += cpuInfo.getNormalizedAvailableCpuFreqKHz();
+ totalOnlineMaxCpuFreqKHz += cpuInfo.maxCpuFreqKHz;
+ }
+
+ public int getAverageAvailableCpuFreqPercent() {
+ return (int) ((totalNormalizedAvailableCpuFreqKHz * 100.0)
+ / totalOnlineMaxCpuFreqKHz);
+ }
+
+ @Override
+ public String toString() {
+ return "Snapshot{uptimeMillis = " + uptimeMillis + ", totalOnlineCpus = "
+ + totalOnlineCpus + ", totalOfflineCpus = " + totalOfflineCpus
+ + ", totalNormalizedAvailableCpuFreqKHz = "
+ + totalNormalizedAvailableCpuFreqKHz
+ + ", totalOnlineMaxCpuFreqKHz = " + totalOnlineMaxCpuFreqKHz
+ + ", totalOfflineMaxCpuFreqKHz = " + totalOfflineMaxCpuFreqKHz + '}';
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index fdc2affda8e7..741464072fd8 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -4737,9 +4737,9 @@ public class UserManagerService extends IUserManager.Stub {
UserManager.USER_OPERATION_ERROR_MAX_USERS);
}
// Keep logic in sync with getRemainingCreatableUserCount()
- if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) {
+ if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
// If the user limit has been reached, we cannot add a user (except guest/demo).
- // Note that profiles can bypass it in certain circumstances (taken
+ // Note that managed profiles can bypass it in certain circumstances (taken
// into account in the profile check below).
throwCheckedUserOperationException(
"Cannot add user. Maximum user limit is reached.",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 321924c79c58..746672968da5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3579,6 +3579,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.getPackageManagerInternal().setOwnerProtectedPackages(
targetUserId, protectedPackages));
+ mUsageStatsManagerInternal.setAdminProtectedPackages(new ArraySet(protectedPackages),
+ targetUserId);
}
void handleUnlockUser(int userId) {
@@ -11777,6 +11779,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(canManageUsers(caller) || canQueryAdminPolicy(caller));
+ // Move AccessibilityManager out of lock to prevent potential deadlock
+ final List<AccessibilityServiceInfo> installedServices;
+ long id = mInjector.binderClearCallingIdentity();
+ try {
+ UserInfo user = getUserInfo(userId);
+ if (user.isManagedProfile()) {
+ userId = user.profileGroupId;
+ }
+ installedServices = withAccessibilityManager(userId,
+ AccessibilityManager::getInstalledAccessibilityServiceList);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+
synchronized (getLockObject()) {
List<String> result = null;
// If we have multiple profiles we return the intersection of the
@@ -11803,27 +11819,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// If we have a permitted list add all system accessibility services.
if (result != null) {
- long id = mInjector.binderClearCallingIdentity();
- try {
- UserInfo user = getUserInfo(userId);
- if (user.isManagedProfile()) {
- userId = user.profileGroupId;
- }
- final List<AccessibilityServiceInfo> installedServices =
- withAccessibilityManager(userId,
- AccessibilityManager::getInstalledAccessibilityServiceList);
-
- if (installedServices != null) {
- for (AccessibilityServiceInfo service : installedServices) {
- ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
- ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
- if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- result.add(serviceInfo.packageName);
- }
+ if (installedServices != null) {
+ for (AccessibilityServiceInfo service : installedServices) {
+ ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
+ ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
+ if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ result.add(serviceInfo.packageName);
}
}
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
index b21478753301..04f6f8b2e9f0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
@@ -92,6 +92,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
/* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 488_095,
+ /* normalizedAvailableCpuFreqKHz= */ 2_402_267,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
/* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
/* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
@@ -101,6 +102,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
/* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
+ /* normalizedAvailableCpuFreqKHz= */ 2_693_525,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
/* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
/* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
@@ -111,6 +113,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
+ /* normalizedAvailableCpuFreqKHz= */ 1_901_608,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
/* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
/* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
@@ -121,6 +124,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
+ /* normalizedAvailableCpuFreqKHz= */ 1_907_125,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
/* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
/* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
@@ -139,6 +143,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
/* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 419_354,
+ /* normalizedAvailableCpuFreqKHz= */ 2_425_919,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
/* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
/* idleTimeMillis= */ 110_000_000, /* iowaitTimeMillis= */ 1_100_000,
@@ -148,6 +153,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 2_800_000,
/* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 429_032,
+ /* normalizedAvailableCpuFreqKHz= */ 2_403_009,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 900_000,
/* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
/* idleTimeMillis= */ 1_000_000, /* iowaitTimeMillis= */ 90_000,
@@ -158,6 +164,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 403_225,
+ /* normalizedAvailableCpuFreqKHz= */ 1_688_209,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
/* niceTimeMillis= */ 2_000_000, /* systemTimeMillis= */ 0,
/* idleTimeMillis= */ 10_000_000, /* iowaitTimeMillis= */ 1_000_000,
@@ -168,6 +175,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ false, /* curCpuFreqKHz= */ MISSING_FREQUENCY,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+ /* normalizedAvailableCpuFreqKHz= */ MISSING_FREQUENCY,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 2_000_000,
/* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 1_000_000,
/* idleTimeMillis= */ 100_000, /* iowaitTimeMillis= */ 100_000,
@@ -189,6 +197,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
/* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+ /* normalizedAvailableCpuFreqKHz= */ 2_253_713,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
/* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
/* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
@@ -198,6 +207,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
/* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+ /* normalizedAvailableCpuFreqKHz= */ 2_492_687,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
/* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
/* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
@@ -208,6 +218,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+ /* normalizedAvailableCpuFreqKHz= */ 1_788_079,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
/* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
/* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
@@ -218,6 +229,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+ /* normalizedAvailableCpuFreqKHz= */ 1_799_962,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
/* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
/* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
@@ -237,6 +249,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
/* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+ /* normalizedAvailableCpuFreqKHz= */ 2323347,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
/* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
/* idleTimeMillis= */ 110_000_000, /* iowaitTimeMillis= */ 1_100_000,
@@ -246,6 +259,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 2_800_000,
/* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+ /* normalizedAvailableCpuFreqKHz= */ 209111,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 900_000,
/* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
/* idleTimeMillis= */ 1_000_000, /* iowaitTimeMillis= */ 90_000,
@@ -256,6 +270,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+ /* normalizedAvailableCpuFreqKHz= */ 453514,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
/* niceTimeMillis= */ 2_000_000, /* systemTimeMillis= */ 0,
/* idleTimeMillis= */ 10_000_000, /* iowaitTimeMillis= */ 1_000_000,
@@ -266,6 +281,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
/* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000,
/* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+ /* normalizedAvailableCpuFreqKHz= */ 37728,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 2_000_000,
/* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 1_000_000,
/* idleTimeMillis= */ 100_000, /* iowaitTimeMillis= */ 100_000,
@@ -323,38 +339,8 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos = new SparseArray<>();
- expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
- FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 3_000_000,
- /* maxCpuFreqKHz= */ 1_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
- new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
- /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
- /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
- /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
- /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
- /* guestNiceTimeMillis= */ 0)));
- expectedCpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2,
- FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
- /* isOnline= */ true, /* curCpuFreqKHz= */ 9, /* maxCpuFreqKHz= */ 2,
- /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
- new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
- /* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
- /* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
- /* irqTimeMillis= */ 14_796_940, /* softirqTimeMillis= */ 1_478_130,
- /* stealTimeMillis= */ 88_780, /* guestTimeMillis= */ 0,
- /* guestNiceTimeMillis= */ 0)));
- expectedCpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3,
- FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
- /* isOnline= */ true, /* curCpuFreqKHz= */ 9, /* maxCpuFreqKHz= */ 2,
- /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
- new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
- /* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
- /* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
- /* irqTimeMillis= */ 8_136_740, /* softirqTimeMillis= */ 438_970,
- /* stealTimeMillis= */ 71_950, /* guestTimeMillis= */ 0,
- /* guestNiceTimeMillis= */ 0)));
- compareCpuInfos("CPU infos with corrupted CPU frequency", expectedCpuInfos,
- actualCpuInfos);
+ compareCpuInfos("CPU infos with corrupted CPU frequency", expectedCpuInfos, actualCpuInfos);
}
@Test
@@ -368,6 +354,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
/* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 488_095,
+ /* normalizedAvailableCpuFreqKHz= */ 2_402_267,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
/* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
/* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
@@ -377,6 +364,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
/* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
+ /* normalizedAvailableCpuFreqKHz= */ 2_693_525,
new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
/* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
/* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
@@ -393,7 +381,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
assertWithMessage("Make empty dir %s", emptyDir).that(emptyDir.mkdir()).isTrue();
CpuInfoReader cpuInfoReader = new CpuInfoReader(emptyDir, getCacheFile(
VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR),
- getCacheFile(VALID_PROC_STAT));
+ getCacheFile(VALID_PROC_STAT), /* minReadIntervalMillis= */0);
assertWithMessage("Init CPU reader info").that(cpuInfoReader.init()).isFalse();
@@ -406,7 +394,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
File emptyDir = getCacheFile(EMPTY_DIR);
assertWithMessage("Make empty dir %s", emptyDir).that(emptyDir.mkdir()).isTrue();
CpuInfoReader cpuInfoReader = new CpuInfoReader(getCacheFile(VALID_CPUSET_DIR), emptyDir,
- getCacheFile(VALID_PROC_STAT));
+ getCacheFile(VALID_PROC_STAT), /* minReadIntervalMillis= */0);
assertWithMessage("Init CPU reader info").that(cpuInfoReader.init()).isFalse();
@@ -420,12 +408,32 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
assertWithMessage("Create empty file %s", emptyFile).that(emptyFile.createNewFile())
.isTrue();
CpuInfoReader cpuInfoReader = new CpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
- getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(EMPTY_FILE));
+ getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(EMPTY_FILE),
+ /* minReadIntervalMillis= */0);
assertWithMessage("Cpu infos with empty proc stat").that(cpuInfoReader.readCpuInfos())
.isNull();
}
+ @Test
+ public void testReadingTooFrequentlyReturnsLastReadCpuInfos() throws Exception {
+ CpuInfoReader cpuInfoReader = new CpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
+ getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(VALID_PROC_STAT),
+ /* minReadIntervalMillis= */ 60_000);
+ assertWithMessage("Initialize CPU info reader").that(cpuInfoReader.init()).isTrue();
+
+ SparseArray<CpuInfoReader.CpuInfo> firstCpuInfos = cpuInfoReader.readCpuInfos();
+ assertWithMessage("CPU infos first snapshot").that(firstCpuInfos).isNotNull();
+ assertWithMessage("CPU infos first snapshot size").that(firstCpuInfos.size())
+ .isGreaterThan(0);
+
+ SparseArray<CpuInfoReader.CpuInfo> secondCpuInfos = cpuInfoReader.readCpuInfos();
+ compareCpuInfos("CPU infos second snapshot", firstCpuInfos, secondCpuInfos);
+
+ SparseArray<CpuInfoReader.CpuInfo> thirdCpuInfos = cpuInfoReader.readCpuInfos();
+ compareCpuInfos("CPU infos third snapshot", firstCpuInfos, thirdCpuInfos);
+ }
+
private void compareCpuInfos(String message,
SparseArray<CpuInfoReader.CpuInfo> expected,
SparseArray<CpuInfoReader.CpuInfo> actual) {
@@ -462,7 +470,8 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
private static CpuInfoReader newCpuInfoReader(File cpusetDir, File cpuFreqDir,
File procStatFile) {
- CpuInfoReader cpuInfoReader = new CpuInfoReader(cpusetDir, cpuFreqDir, procStatFile);
+ CpuInfoReader cpuInfoReader = new CpuInfoReader(cpusetDir, cpuFreqDir, procStatFile,
+ /* minReadIntervalMillis= */ 0);
assertWithMessage("Initialize CPU info reader").that(cpuInfoReader.init()).isTrue();
return cpuInfoReader;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
index 49a2cc696744..5a5f5256e37d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
@@ -17,105 +17,659 @@
package com.android.server.cpu;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.cpu.CpuAvailabilityInfo.MISSING_CPU_AVAILABILITY_PERCENT;
import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL;
+import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND;
+import static com.android.server.cpu.CpuInfoReader.CpuInfo.MISSING_FREQUENCY;
+import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_BACKGROUND;
+import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_TOP_APP;
+import static com.android.server.cpu.CpuMonitorService.DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
+
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.ServiceManager;
+import android.util.ArraySet;
+import android.util.SparseArray;
import com.android.server.ExtendedMockitoRule;
import com.android.server.LocalServices;
+import com.android.server.Watchdog;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.stubbing.OngoingStubbing;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
public final class CpuMonitorServiceTest {
- private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG =
+ private static final String TAG = CpuMonitorServiceTest.class.getSimpleName();
+ private static final String USER_BUILD_TAG = TAG + "UserBuild";
+ private static final long ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS =
+ TimeUnit.SECONDS.toMillis(1);
+ private static final long HANDLER_THREAD_SYNC_TIMEOUT_MILLISECONDS =
+ TimeUnit.SECONDS.toMillis(5);
+ private static final long TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS = 100;
+ private static final long TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS = 150;
+ private static final long TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS = 300;
+ private static final CpuAvailabilityMonitoringConfig TEST_MONITORING_CONFIG_ALL_CPUSET =
new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
.addThreshold(30).addThreshold(70).build();
+ private static final CpuAvailabilityMonitoringConfig TEST_MONITORING_CONFIG_BG_CPUSET =
+ new CpuAvailabilityMonitoringConfig.Builder(CPUSET_BACKGROUND)
+ .addThreshold(50).addThreshold(90).build();
+ private static final List<StaticCpuInfo> STATIC_CPU_INFOS = List.of(
+ new StaticCpuInfo(/* cpuCore= */ 0,
+ /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP,
+ /* maxCpuFreqKHz= */ 4000),
+ new StaticCpuInfo(/* cpuCore= */ 1,
+ /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP,
+ /* maxCpuFreqKHz= */ 3000),
+ new StaticCpuInfo(/* cpuCore= */ 2, /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP
+ | FLAG_CPUSET_CATEGORY_BACKGROUND, /* maxCpuFreqKHz= */ 3000),
+ new StaticCpuInfo(/* cpuCore= */ 3, /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP
+ | FLAG_CPUSET_CATEGORY_BACKGROUND, /* maxCpuFreqKHz= */ 3000),
+ new StaticCpuInfo(/* cpuCore= */ 4, /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP
+ | FLAG_CPUSET_CATEGORY_BACKGROUND, /* maxCpuFreqKHz= */ 2000));
+ private static final ArraySet<Integer> NO_OFFLINE_CORES = new ArraySet<>();
- private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2 =
- new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
- .addThreshold(10).addThreshold(90).build();
-
@Mock
- private Context mContext;
+ private Context mMockContext;
+ @Mock
+ private CpuInfoReader mMockCpuInfoReader;
+ @Captor
+ private ArgumentCaptor<CpuAvailabilityInfo> mCpuAvailabilityInfoCaptor;
+ private HandlerThread mServiceHandlerThread;
+ private Handler mServiceHandler;
private CpuMonitorService mService;
- private HandlerExecutor mHandlerExecutor;
private CpuMonitorInternal mLocalService;
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
.mockStatic(ServiceManager.class)
+ .mockStatic(Watchdog.class)
.build();
@Before
- public void setUp() {
- mService = new CpuMonitorService(mContext);
- mHandlerExecutor = new HandlerExecutor(new Handler(Looper.getMainLooper()));
+ public void setUp() throws Exception {
+ mServiceHandlerThread = new HandlerThread(TAG);
+ mService = new CpuMonitorService(mMockContext, mMockCpuInfoReader, mServiceHandlerThread,
+ /* shouldDebugMonitor= */ true, TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS,
+ TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS);
+
doNothing().when(() -> ServiceManager.addService(eq("cpu_monitor"), any(Binder.class),
anyBoolean(), anyInt()));
- mService.onStart();
- mLocalService = LocalServices.getService(CpuMonitorInternal.class);
+ doReturn(mock(Watchdog.class)).when(Watchdog::getInstance);
+ when(mMockCpuInfoReader.init()).thenReturn(true);
+ when(mMockCpuInfoReader.readCpuInfos()).thenReturn(new SparseArray<>());
+
+ startService();
}
@After
- public void tearDown() {
- // The CpuMonitorInternal.class service is added by the mService.onStart call.
- // Remove the service to ensure the setUp procedure can add this service again.
- LocalServices.removeServiceForTest(CpuMonitorInternal.class);
+ public void tearDown() throws Exception {
+ terminateService();
}
@Test
- public void testAddRemoveCpuAvailabilityCallback() {
+ public void testAddRemoveCpuAvailabilityCallbackOnDebugBuild() throws Exception {
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
CpuMonitorInternal.CpuAvailabilityCallback.class);
- mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
- TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
+ mLocalService.addCpuAvailabilityCallback(/* executor= */ null,
+ TEST_MONITORING_CONFIG_ALL_CPUSET, mockCallback);
+
+ assertWithMessage("Monitoring interval after adding a client callback")
+ .that(mService.getCurrentMonitoringIntervalMillis())
+ .isEqualTo(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
+
+ // Monitoring interval changed notification is sent asynchronously from the handler thread.
+ // So, sync with this thread before verifying the client call.
+ syncWithHandler(mServiceHandler, /* delayMillis= */ 0);
+
+ verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
+ .onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
- // TODO(b/242722241): Verify that {@link mockCallback.onAvailabilityChanged} and
- // {@link mockCallback.onMonitoringIntervalChanged} are called when the callback is added.
+ verify(mockCallback, never()).onAvailabilityChanged(any());
mLocalService.removeCpuAvailabilityCallback(mockCallback);
- }
+ assertWithMessage("Monitoring interval after removing all client callbacks")
+ .that(mService.getCurrentMonitoringIntervalMillis())
+ .isEqualTo(TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS);
+ }
@Test
- public void testDuplicateAddCpuAvailabilityCallback() {
+ public void testAddRemoveCpuAvailabilityCallbackOnUserBuild() throws Exception {
+ // The default service instantiated during test setUp has the debug monitoring enabled.
+ // But on a user build, debug monitoring is disabled. So, replace the default service with
+ // an equivalent user build service.
+ replaceServiceWithUserBuildService();
+
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
CpuMonitorInternal.CpuAvailabilityCallback.class);
- mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
- TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
+ mLocalService.addCpuAvailabilityCallback(/* executor= */ null,
+ TEST_MONITORING_CONFIG_ALL_CPUSET, mockCallback);
+
+ assertWithMessage("Monitoring interval after adding a client callback")
+ .that(mService.getCurrentMonitoringIntervalMillis())
+ .isEqualTo(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
- mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
- TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2, mockCallback);
+ // Monitoring interval changed notification is sent asynchronously from the handler thread.
+ // So, sync with this thread before verifying the client call.
+ syncWithHandler(mServiceHandler, /* delayMillis= */ 0);
- // TODO(b/242722241): Verify that {@link mockCallback} is called only when CPU availability
- // thresholds cross the bounds specified in the
- // {@link TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2} config.
+ verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
+ .onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
+
+ verify(mockCallback, never()).onAvailabilityChanged(any());
mLocalService.removeCpuAvailabilityCallback(mockCallback);
+
+ assertWithMessage("Monitoring interval after removing all client callbacks")
+ .that(mService.getCurrentMonitoringIntervalMillis())
+ .isEqualTo(DEFAULT_MONITORING_INTERVAL_MILLISECONDS);
}
@Test
- public void testRemoveInvalidCpuAvailabilityCallback() {
+ public void testRemoveInvalidCpuAvailabilityCallback() throws Exception {
CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
CpuMonitorInternal.CpuAvailabilityCallback.class);
mLocalService.removeCpuAvailabilityCallback(mockCallback);
}
+
+ @Test
+ public void testReceiveCpuAvailabilityCallbackOnAddingFirstCallback() throws Exception {
+ // Debug monitoring is in progress but the default {@link CpuInfoReader.CpuInfo} returned by
+ // the {@link CpuInfoReader.readCpuInfos} is empty, so the client won't be notified when
+ // adding a callback. Inject {@link CpuInfoReader.CpuInfo}, so the client callback is
+ // notified on adding a callback.
+ injectCpuInfosAndWait(List.of(
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
+ NO_OFFLINE_CORES)));
+
+ CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
+ addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
+
+ verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
+ .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
+
+ List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
+
+ List<CpuAvailabilityInfo> expected = List.of(
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 10, MISSING_CPU_AVAILABILITY_PERCENT,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+ assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
+ }
+
+ @Test
+ public void testReceiveCpuAvailabilityCallbackOnAddingMultipleCallbacks() throws Exception {
+ addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
+
+ injectCpuInfosAndWait(List.of(
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
+ NO_OFFLINE_CORES)));
+
+ CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
+ addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
+
+ verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
+ .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
+
+ List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
+
+ List<CpuAvailabilityInfo> expected = List.of(
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 10, MISSING_CPU_AVAILABILITY_PERCENT,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+ assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
+ }
+
+ @Test
+ public void testCrossCpuAvailabilityThresholdsWithSingleCallback() throws Exception {
+ CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
+ addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
+
+ injectCpuInfosAndWait(List.of(
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 82.0f,
+ NO_OFFLINE_CORES)));
+
+ verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(4))
+ .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
+
+ List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
+
+ List<CpuAvailabilityInfo> expected = List.of(
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 90, MISSING_CPU_AVAILABILITY_PERCENT,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 15, MISSING_CPU_AVAILABILITY_PERCENT,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 30,
+ /* pastNMillisAvgAvailabilityPercent= */ 45,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(3).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 82,
+ /* pastNMillisAvgAvailabilityPercent= */ 57,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+ assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
+ }
+
+ @Test
+ public void testCrossCpuAvailabilityThresholdsWithMultipleCallbacks() throws Exception {
+ CpuMonitorInternal.CpuAvailabilityCallback mockAllCpusetCallback =
+ addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
+
+ CpuMonitorInternal.CpuAvailabilityCallback mockBgCpusetCallback =
+ addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
+
+ injectCpuInfosAndWait(List.of(
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 5.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 20.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 75.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
+ NO_OFFLINE_CORES)));
+
+ verify(mockAllCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
+ .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
+
+ List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
+ List<CpuAvailabilityInfo> expected = List.of(
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 30, MISSING_CPU_AVAILABILITY_PERCENT,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 75,
+ /* pastNMillisAvgAvailabilityPercent= */ 55,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 15,
+ /* pastNMillisAvgAvailabilityPercent= */ 60,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+ assertWithMessage("CPU availability infos for CPUSET_ALL callback").that(actual)
+ .isEqualTo(expected);
+
+ ArgumentCaptor<CpuAvailabilityInfo> bgCpusetAvailabilityInfoCaptor =
+ ArgumentCaptor.forClass(CpuAvailabilityInfo.class);
+
+ verify(mockBgCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
+ .onAvailabilityChanged(bgCpusetAvailabilityInfoCaptor.capture());
+
+ actual = bgCpusetAvailabilityInfoCaptor.getAllValues();
+ expected = List.of(
+ new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(0).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 60,
+ /* pastNMillisAvgAvailabilityPercent= */ 36,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+ new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(1).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 90,
+ /* pastNMillisAvgAvailabilityPercent= */ 75,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+ new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(2).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 15,
+ /* pastNMillisAvgAvailabilityPercent= */ 60,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+ assertWithMessage("CPU availability infos for CPUSET_BACKGROUND callback").that(actual)
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testCrossCpuAvailabilityThresholdsWithOfflineCores() throws Exception {
+ CpuMonitorInternal.CpuAvailabilityCallback mockAllCpusetCallback =
+ addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
+
+ CpuMonitorInternal.CpuAvailabilityCallback mockBgCpusetCallback =
+ addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
+
+ // Disable one top-app and one all cpuset core.
+ ArraySet<Integer> offlineCoresA = new ArraySet<>();
+ offlineCoresA.add(1);
+ offlineCoresA.add(3);
+
+ // Disable two all cpuset cores.
+ ArraySet<Integer> offlineCoresB = new ArraySet<>();
+ offlineCoresB.add(2);
+ offlineCoresB.add(4);
+
+ injectCpuInfosAndWait(List.of(
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 5.0f, offlineCoresA),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 20.0f, offlineCoresB),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f, offlineCoresA),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f, offlineCoresB),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 75.0f, offlineCoresA),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f, offlineCoresB),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
+ offlineCoresA)));
+
+ verify(mockAllCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
+ .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
+
+ List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
+ List<CpuAvailabilityInfo> expected = List.of(
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 30, MISSING_CPU_AVAILABILITY_PERCENT,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 75,
+ /* pastNMillisAvgAvailabilityPercent= */ 55,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 15,
+ /* pastNMillisAvgAvailabilityPercent= */ 61,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+ assertWithMessage("CPU availability infos for CPUSET_ALL callback").that(actual)
+ .isEqualTo(expected);
+
+ ArgumentCaptor<CpuAvailabilityInfo> bgCpusetAvailabilityInfoCaptor =
+ ArgumentCaptor.forClass(CpuAvailabilityInfo.class);
+
+ verify(mockBgCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
+ .onAvailabilityChanged(bgCpusetAvailabilityInfoCaptor.capture());
+
+ actual = bgCpusetAvailabilityInfoCaptor.getAllValues();
+ expected = List.of(
+ new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(0).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 60,
+ /* pastNMillisAvgAvailabilityPercent= */ 35,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+ new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(1).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 90,
+ /* pastNMillisAvgAvailabilityPercent= */ 75,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+ new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(2).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 15,
+ /* pastNMillisAvgAvailabilityPercent= */ 55,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+ assertWithMessage("CPU availability infos for CPUSET_BACKGROUND callback").that(actual)
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testReceiveCpuAvailabilityCallbacksOnExecutorThread() throws Exception {
+ Handler testHandler = new Handler(Looper.getMainLooper());
+
+ assertWithMessage("Test main handler").that(testHandler).isNotNull();
+
+ HandlerExecutor testExecutor = new HandlerExecutor(testHandler);
+
+ assertWithMessage("Test main executor").that(testExecutor).isNotNull();
+
+ CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
+ addCpuAvailabilityCallback(testHandler, testExecutor,
+ TEST_MONITORING_CONFIG_ALL_CPUSET);
+
+ // CPU monitoring is started on the service handler thread. Sync with this thread before
+ // proceeding. Otherwise, debug monitoring may consume the injected CPU infos and cause
+ // the test to be flaky. Because the {@link addCpuAvailabilityCallback} syncs only with
+ // the passed handler, the test must explicitly sync with the service handler.
+ syncWithHandler(mServiceHandler, /* delayMillis= */ 0);
+
+ injectCpuInfosAndWait(testHandler, List.of(
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 82.0f,
+ NO_OFFLINE_CORES)));
+
+ verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(4))
+ .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
+
+ List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
+
+ List<CpuAvailabilityInfo> expected = List.of(
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 90, MISSING_CPU_AVAILABILITY_PERCENT,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 15, MISSING_CPU_AVAILABILITY_PERCENT,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 30,
+ /* pastNMillisAvgAvailabilityPercent= */ 45,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+ new CpuAvailabilityInfo(CPUSET_ALL, actual.get(3).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 82,
+ /* pastNMillisAvgAvailabilityPercent= */ 57,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+ assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
+ }
+
+ @Test
+ public void testDuplicateAddCpuAvailabilityCallback() throws Exception {
+ addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
+
+ CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
+ addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
+
+ injectCpuInfosAndWait(List.of(
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 40.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 80.0f,
+ NO_OFFLINE_CORES),
+ generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 95.0f,
+ NO_OFFLINE_CORES)));
+
+ verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(2))
+ .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
+
+ List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
+
+ // Verify that the callback is called for the last added monitoring config.
+ List<CpuAvailabilityInfo> expected = List.of(
+ new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(0).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 60, MISSING_CPU_AVAILABILITY_PERCENT,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+ new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(1).dataTimestampUptimeMillis,
+ /* latestAvgAvailabilityPercent= */ 95,
+ /* pastNMillisAvgAvailabilityPercent= */ 78,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+ assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
+ }
+
+ @Test
+ public void testHeavyCpuLoadMonitoring() throws Exception {
+ // TODO(b/267500110): Once heavy CPU load detection logic is added, add unittest.
+ }
+
+ private void startService() {
+ mService.onStart();
+ mServiceHandler = mServiceHandlerThread.getThreadHandler();
+
+ assertWithMessage("Service thread handler").that(mServiceHandler).isNotNull();
+
+ mLocalService = LocalServices.getService(CpuMonitorInternal.class);
+
+ assertWithMessage("CpuMonitorInternal local service").that(mLocalService).isNotNull();
+ }
+
+ private void terminateService() {
+ // The CpuMonitorInternal.class service is added by the {@link CpuMonitorService#onStart}
+ // call. Remove the service to ensure this service can be added again during
+ // the {@link CpuMonitorService#onStart} call.
+ LocalServices.removeServiceForTest(CpuMonitorInternal.class);
+ if (mServiceHandlerThread != null && mServiceHandlerThread.isAlive()) {
+ mServiceHandlerThread.quitSafely();
+ }
+ }
+
+ private void replaceServiceWithUserBuildService() {
+ terminateService();
+ mServiceHandlerThread = new HandlerThread(USER_BUILD_TAG);
+ mService = new CpuMonitorService(mMockContext, mMockCpuInfoReader,
+ mServiceHandlerThread, /* shouldDebugMonitor= */ false,
+ TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS,
+ TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS,
+ TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS);
+
+ startService();
+ }
+
+ private CpuMonitorInternal.CpuAvailabilityCallback addCpuAvailabilityCallback(
+ CpuAvailabilityMonitoringConfig config) throws Exception {
+ return addCpuAvailabilityCallback(mServiceHandler, /* executor= */ null, config);
+ }
+
+ private CpuMonitorInternal.CpuAvailabilityCallback addCpuAvailabilityCallback(Handler handler,
+ HandlerExecutor executor, CpuAvailabilityMonitoringConfig config) throws Exception {
+ CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
+ CpuMonitorInternal.CpuAvailabilityCallback.class);
+
+ mLocalService.addCpuAvailabilityCallback(executor, config, mockCallback);
+
+ // Monitoring interval changed notification is sent asynchronously from the given handler.
+ // So, sync with this thread before verifying the client call.
+ syncWithHandler(handler, /* delayMillis= */ 0);
+
+ verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
+ .onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
+
+ return mockCallback;
+ }
+
+ private void injectCpuInfosAndWait(List<SparseArray<CpuInfoReader.CpuInfo>> cpuInfos)
+ throws Exception {
+ injectCpuInfosAndWait(mServiceHandler, cpuInfos);
+ }
+
+ private void injectCpuInfosAndWait(Handler handler,
+ List<SparseArray<CpuInfoReader.CpuInfo>> cpuInfos) throws Exception {
+ assertWithMessage("CPU info configs").that(cpuInfos).isNotEmpty();
+
+ OngoingStubbing<SparseArray<CpuInfoReader.CpuInfo>> ongoingStubbing =
+ when(mMockCpuInfoReader.readCpuInfos());
+ for (SparseArray<CpuInfoReader.CpuInfo> cpuInfo : cpuInfos) {
+ ongoingStubbing = ongoingStubbing.thenReturn(cpuInfo);
+ }
+
+ // CPU infos are read asynchronously on a separate handler thread. So, wait based on
+ // the current monitoring interval and the number of CPU infos were injected.
+ syncWithHandler(handler,
+ /* delayMillis= */ mService.getCurrentMonitoringIntervalMillis() * cpuInfos.size());
+ }
+
+ private void syncWithHandler(Handler handler, long delayMillis) throws Exception {
+ AtomicBoolean didRun = new AtomicBoolean(false);
+ handler.postDelayed(() -> {
+ synchronized (didRun) {
+ didRun.set(true);
+ didRun.notifyAll();
+ }
+ }, delayMillis);
+ synchronized (didRun) {
+ while (!didRun.get()) {
+ didRun.wait(HANDLER_THREAD_SYNC_TIMEOUT_MILLISECONDS);
+ }
+ }
+ }
+
+ private static SparseArray<CpuInfoReader.CpuInfo> generateCpuInfosForAvailability(
+ double cpuAvailabilityPercent, ArraySet<Integer> offlineCores) {
+ SparseArray<CpuInfoReader.CpuInfo> cpuInfos = new SparseArray<>(STATIC_CPU_INFOS.size());
+ for (StaticCpuInfo staticCpuInfo : STATIC_CPU_INFOS) {
+ boolean isOnline = !offlineCores.contains(staticCpuInfo.cpuCore);
+ cpuInfos.append(staticCpuInfo.cpuCore, constructCpuInfo(staticCpuInfo.cpuCore,
+ staticCpuInfo.cpusetCategories, isOnline, staticCpuInfo.maxCpuFreqKHz,
+ cpuAvailabilityPercent));
+ }
+ return cpuInfos;
+ }
+
+ private static CpuInfoReader.CpuInfo constructCpuInfo(int cpuCore,
+ @CpuInfoReader.CpusetCategory int cpusetCategories, boolean isOnline,
+ long maxCpuFreqKHz, double cpuAvailabilityPercent) {
+ long availCpuFreqKHz = (long) (maxCpuFreqKHz * (cpuAvailabilityPercent / 100.0));
+ long curCpuFreqKHz = maxCpuFreqKHz - availCpuFreqKHz;
+ return new CpuInfoReader.CpuInfo(cpuCore, cpusetCategories, isOnline,
+ isOnline ? curCpuFreqKHz : MISSING_FREQUENCY, maxCpuFreqKHz,
+ /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+ isOnline ? availCpuFreqKHz : MISSING_FREQUENCY,
+ /* latestCpuUsageStats= */ null);
+ }
+
+ private static final class StaticCpuInfo {
+ public final int cpuCore;
+ public final int cpusetCategories;
+ public final int maxCpuFreqKHz;
+
+ StaticCpuInfo(int cpuCore, @CpuInfoReader.CpusetCategory int cpusetCategories,
+ int maxCpuFreqKHz) {
+ this.cpuCore = cpuCore;
+ this.cpusetCategories = cpusetCategories;
+ this.maxCpuFreqKHz = maxCpuFreqKHz;
+ }
+
+ @Override
+ public String toString() {
+ return "StaticCpuInfo{cpuCore=" + cpuCore + ", cpusetCategories=" + cpusetCategories
+ + ", maxCpuFreqKHz=" + maxCpuFreqKHz + '}';
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index e056417811c7..ba70c584f2de 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -262,9 +262,10 @@ public class JobSchedulerServiceTest {
ConnectivityController connectivityController = mService.getConnectivityController();
spyOn(connectivityController);
mService.mConstants.RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
+ mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS = 2 * HOUR_IN_MILLIS;
mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
- mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
+ mService.mConstants.RUNTIME_UI_LIMIT_MS = 6 * HOUR_IN_MILLIS;
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(ejMax));
@@ -283,9 +284,9 @@ public class JobSchedulerServiceTest {
// Permission isn't granted, so it should just be treated as a regular job.
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+
grantRunUserInitiatedJobsPermission(true); // With permission
- assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
- mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+ mService.mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = true;
doReturn(ConnectivityController.UNKNOWN_TIME)
.when(connectivityController).getEstimatedTransferTimeMs(any());
assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
@@ -294,16 +295,20 @@ public class JobSchedulerServiceTest {
.when(connectivityController).getEstimatedTransferTimeMs(any());
assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
- doReturn(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS * 2)
+ final long estimatedTransferTimeMs =
+ mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS * 2;
+ doReturn(estimatedTransferTimeMs)
.when(connectivityController).getEstimatedTransferTimeMs(any());
- assertEquals(
- (long) (mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS
- * 2 * mService.mConstants
- .RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR),
+ assertEquals((long) (estimatedTransferTimeMs
+ * mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR),
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
- doReturn(mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS * 2)
+ doReturn(mService.mConstants.RUNTIME_UI_LIMIT_MS * 2)
.when(connectivityController).getEstimatedTransferTimeMs(any());
- assertEquals(mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+
+ mService.mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = false;
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
}
@@ -325,7 +330,7 @@ public class JobSchedulerServiceTest {
.when(quotaController).getMaxJobExecutionTimeMsLocked(any());
grantRunUserInitiatedJobsPermission(true);
- assertEquals(mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUIDT));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 32e5c836ac3d..a3ae83428af5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -840,8 +840,9 @@ public class ConnectivityControllerTest {
final JobStatus blue = createJobStatus(createJob()
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
+ // Unmetered preference is disabled for now.
assertFalse(red.getPreferUnmetered());
- assertTrue(blue.getPreferUnmetered());
+ assertFalse(blue.getPreferUnmetered());
controller.maybeStartTrackingJobLocked(red, null);
controller.maybeStartTrackingJobLocked(blue, null);
@@ -895,7 +896,7 @@ public class ConnectivityControllerTest {
generalCallback.onLost(meteredNet);
assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
- assertFalse(red.getHasAccessToUnmetered());
+ assertTrue(red.getHasAccessToUnmetered());
assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
assertTrue(blue.getHasAccessToUnmetered());
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 6bc552c7f32e..4b19bbb72805 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -189,7 +189,10 @@ public class FlexibilityControllerTest {
}
private static JobInfo.Builder createJob(int id) {
- return new JobInfo.Builder(id, new ComponentName("foo", "bar"));
+ return new JobInfo.Builder(id, new ComponentName("foo", "bar"))
+ .setPrefersBatteryNotLow(true)
+ .setPrefersCharging(true)
+ .setPrefersDeviceIdle(true);
}
private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
@@ -533,12 +536,15 @@ public class FlexibilityControllerTest {
jb = createJob(i);
if (i > 0) {
jb.setRequiresDeviceIdle(true);
+ jb.setPrefersDeviceIdle(false);
}
if (i > 1) {
jb.setRequiresBatteryNotLow(true);
+ jb.setPrefersBatteryNotLow(false);
}
if (i > 2) {
jb.setRequiresCharging(true);
+ jb.setPrefersCharging(false);
}
jobs[i] = createJobStatus("", jb);
flexTracker.add(jobs[i]);
@@ -547,53 +553,55 @@ public class FlexibilityControllerTest {
synchronized (mFlexibilityController.mLock) {
ArrayList<ArraySet<JobStatus>> trackedJobs = flexTracker.getArrayList();
assertEquals(1, trackedJobs.get(0).size());
- assertEquals(0, trackedJobs.get(1).size());
- assertEquals(0, trackedJobs.get(2).size());
- assertEquals(3, trackedJobs.get(3).size());
+ assertEquals(1, trackedJobs.get(1).size());
+ assertEquals(1, trackedJobs.get(2).size());
+ assertEquals(1, trackedJobs.get(3).size());
assertEquals(0, trackedJobs.get(4).size());
flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
assertEquals(1, trackedJobs.get(0).size());
- assertEquals(0, trackedJobs.get(1).size());
- assertEquals(1, trackedJobs.get(2).size());
- assertEquals(2, trackedJobs.get(3).size());
+ assertEquals(1, trackedJobs.get(1).size());
+ assertEquals(2, trackedJobs.get(2).size());
+ assertEquals(0, trackedJobs.get(3).size());
assertEquals(0, trackedJobs.get(4).size());
flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
assertEquals(1, trackedJobs.get(0).size());
- assertEquals(1, trackedJobs.get(1).size());
- assertEquals(0, trackedJobs.get(2).size());
- assertEquals(2, trackedJobs.get(3).size());
+ assertEquals(2, trackedJobs.get(1).size());
+ assertEquals(1, trackedJobs.get(2).size());
+ assertEquals(0, trackedJobs.get(3).size());
assertEquals(0, trackedJobs.get(4).size());
flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
assertEquals(2, trackedJobs.get(0).size());
- assertEquals(0, trackedJobs.get(1).size());
- assertEquals(0, trackedJobs.get(2).size());
- assertEquals(2, trackedJobs.get(3).size());
+ assertEquals(1, trackedJobs.get(1).size());
+ assertEquals(1, trackedJobs.get(2).size());
+ assertEquals(0, trackedJobs.get(3).size());
assertEquals(0, trackedJobs.get(4).size());
flexTracker.remove(jobs[1]);
assertEquals(2, trackedJobs.get(0).size());
- assertEquals(0, trackedJobs.get(1).size());
+ assertEquals(1, trackedJobs.get(1).size());
assertEquals(0, trackedJobs.get(2).size());
- assertEquals(1, trackedJobs.get(3).size());
+ assertEquals(0, trackedJobs.get(3).size());
assertEquals(0, trackedJobs.get(4).size());
flexTracker.resetJobNumDroppedConstraints(jobs[0], FROZEN_TIME);
assertEquals(1, trackedJobs.get(0).size());
- assertEquals(0, trackedJobs.get(1).size());
+ assertEquals(1, trackedJobs.get(1).size());
assertEquals(0, trackedJobs.get(2).size());
- assertEquals(2, trackedJobs.get(3).size());
+ assertEquals(1, trackedJobs.get(3).size());
assertEquals(0, trackedJobs.get(4).size());
flexTracker.adjustJobsRequiredConstraints(jobs[0], -2, FROZEN_TIME);
assertEquals(1, trackedJobs.get(0).size());
- assertEquals(1, trackedJobs.get(1).size());
+ assertEquals(2, trackedJobs.get(1).size());
assertEquals(0, trackedJobs.get(2).size());
- assertEquals(1, trackedJobs.get(3).size());
+ assertEquals(0, trackedJobs.get(3).size());
assertEquals(0, trackedJobs.get(4).size());
+ // Over halfway through the flex window. The job that prefers all flex constraints
+ // should have its first flex constraint dropped.
final long nowElapsed = ((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2)
+ HOUR_IN_MILLIS);
JobSchedulerService.sElapsedRealtimeClock =
@@ -601,9 +609,9 @@ public class FlexibilityControllerTest {
flexTracker.resetJobNumDroppedConstraints(jobs[0], nowElapsed);
assertEquals(1, trackedJobs.get(0).size());
- assertEquals(0, trackedJobs.get(1).size());
+ assertEquals(1, trackedJobs.get(1).size());
assertEquals(1, trackedJobs.get(2).size());
- assertEquals(1, trackedJobs.get(3).size());
+ assertEquals(0, trackedJobs.get(3).size());
assertEquals(0, trackedJobs.get(4).size());
}
}
@@ -618,8 +626,13 @@ public class FlexibilityControllerTest {
@Test
public void testExceptions_UserInitiated() {
- JobInfo.Builder jb = createJob(0);
- jb.setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ JobInfo.Builder jb = createJob(0)
+ .setUserInitiated(true)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ // Attempt to add flex constraints to the job. For now, we will ignore them.
+ .setPrefersBatteryNotLow(true)
+ .setPrefersCharging(true)
+ .setPrefersDeviceIdle(false);
JobStatus js = createJobStatus("testExceptions_UserInitiated", jb);
assertFalse(js.hasFlexibilityConstraint());
}
@@ -635,10 +648,10 @@ public class FlexibilityControllerTest {
@Test
public void testExceptions_NoFlexibleConstraints() {
- JobInfo.Builder jb = createJob(0);
- jb.setRequiresDeviceIdle(true);
- jb.setRequiresCharging(true);
- jb.setRequiresBatteryNotLow(true);
+ JobInfo.Builder jb = createJob(0)
+ .setPrefersBatteryNotLow(false)
+ .setPrefersCharging(false)
+ .setPrefersDeviceIdle(false);
JobStatus js = createJobStatus("testExceptions_NoFlexibleConstraints", jb);
assertFalse(js.hasFlexibilityConstraint());
}
@@ -697,15 +710,50 @@ public class FlexibilityControllerTest {
JobStatus js = createJobStatus("testTopAppBypass", jb);
synchronized (mFlexibilityController.mLock) {
js.setHasAccessToUnmetered(false);
- assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js));
+ assertEquals(0, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
js.setHasAccessToUnmetered(true);
- assertEquals(1, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js));
+ assertEquals(1, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
js.setHasAccessToUnmetered(false);
- assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js));
+ assertEquals(0, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
}
}
@Test
+ public void testGetNumSatisfiedFlexibleConstraints() {
+ long nowElapsed = FROZEN_TIME;
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, true, nowElapsed);
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING, true, nowElapsed);
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, true, nowElapsed);
+ JobInfo.Builder jb = createJob(0)
+ .setPrefersBatteryNotLow(false)
+ .setPrefersCharging(false)
+ .setPrefersDeviceIdle(false);
+ JobStatus js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb);
+ assertEquals(0, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
+
+ jb = createJob(0)
+ .setPrefersBatteryNotLow(true)
+ .setPrefersCharging(false)
+ .setPrefersDeviceIdle(false);
+ js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb);
+ assertEquals(1, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
+
+ jb = createJob(0)
+ .setPrefersBatteryNotLow(true)
+ .setPrefersCharging(false)
+ .setPrefersDeviceIdle(true);
+ js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb);
+ assertEquals(2, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
+
+ jb = createJob(0)
+ .setPrefersBatteryNotLow(true)
+ .setPrefersCharging(true)
+ .setPrefersDeviceIdle(true);
+ js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb);
+ assertEquals(3, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
+ }
+
+ @Test
public void testSetConstraintSatisfied_Constraints() {
mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME);
assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
@@ -736,8 +784,11 @@ public class FlexibilityControllerTest {
jb = createJob(i);
constraints = constraintCombinations[i];
jb.setRequiresDeviceIdle((constraints & CONSTRAINT_IDLE) != 0);
+ jb.setPrefersDeviceIdle((constraints & CONSTRAINT_IDLE) == 0);
jb.setRequiresBatteryNotLow((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0);
+ jb.setPrefersBatteryNotLow((constraints & CONSTRAINT_BATTERY_NOT_LOW) == 0);
jb.setRequiresCharging((constraints & CONSTRAINT_CHARGING) != 0);
+ jb.setPrefersCharging((constraints & CONSTRAINT_CHARGING) == 0);
synchronized (mFlexibilityController.mLock) {
mFlexibilityController.maybeStartTrackingJobLocked(
createJobStatus(String.valueOf(i), jb), null);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 5dc8ed58994d..b076ab495d0c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -1039,7 +1039,9 @@ public class JobStatusTest {
@Test
public void testReadinessStatusWithConstraint_FlexibilityConstraint() {
final JobStatus job = createJobStatus(
- new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
+ new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setPrefersCharging(true)
+ .build());
job.setConstraintSatisfied(CONSTRAINT_FLEXIBLE, sElapsedRealtimeClock.millis(), false);
markImplicitConstraintsSatisfied(job, true);
assertTrue(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 5560b41b164f..236c74fc29e6 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -728,6 +728,66 @@ public class JobStoreTest {
}
@Test
+ public void testPersistedPreferredBatteryNotLowConstraint() throws Exception {
+ JobInfo.Builder b = new Builder(8, mComponent)
+ .setPrefersBatteryNotLow(true)
+ .setPersisted(true);
+ JobStatus taskStatus =
+ JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null);
+
+ mTaskStoreUnderTest.add(taskStatus);
+ waitForPendingIo();
+
+ final JobSet jobStatusSet = new JobSet();
+ mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+ assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+ JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+ assertEquals("Battery-not-low constraint not persisted correctly.",
+ taskStatus.getJob().isPreferBatteryNotLow(),
+ loaded.getJob().isPreferBatteryNotLow());
+ }
+
+ @Test
+ public void testPersistedPreferredChargingConstraint() throws Exception {
+ JobInfo.Builder b = new Builder(8, mComponent)
+ .setPrefersCharging(true)
+ .setPersisted(true);
+ JobStatus taskStatus =
+ JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null);
+
+ mTaskStoreUnderTest.add(taskStatus);
+ waitForPendingIo();
+
+ final JobSet jobStatusSet = new JobSet();
+ mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+ assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+ JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+ assertEquals("Charging constraint not persisted correctly.",
+ taskStatus.getJob().isPreferCharging(),
+ loaded.getJob().isPreferCharging());
+ }
+
+ @Test
+ public void testPersistedPreferredDeviceIdleConstraint() throws Exception {
+ JobInfo.Builder b = new Builder(8, mComponent)
+ .setPrefersDeviceIdle(true)
+ .setPersisted(true);
+ JobStatus taskStatus =
+ JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null);
+
+ mTaskStoreUnderTest.add(taskStatus);
+ waitForPendingIo();
+
+ final JobSet jobStatusSet = new JobSet();
+ mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+ assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+ JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+ assertEquals("Idle constraint not persisted correctly.",
+ taskStatus.getJob().isPreferDeviceIdle(),
+ loaded.getJob().isPreferDeviceIdle());
+ }
+
+ @Test
public void testJobWorkItems() throws Exception {
JobWorkItem item1 = new JobWorkItem.Builder().build();
item1.bumpDeliveryCount();
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 9570ff6323d2..86878c5384fd 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -160,6 +160,9 @@ public class AppStandbyControllerTests {
private static final String ADMIN_PKG2 = "com.android.admin2";
private static final String ADMIN_PKG3 = "com.android.admin3";
+ private static final String ADMIN_PROTECTED_PKG = "com.android.admin.protected";
+ private static final String ADMIN_PROTECTED_PKG2 = "com.android.admin.protected2";
+
private static final long MINUTE_MS = 60 * 1000;
private static final long HOUR_MS = 60 * MINUTE_MS;
private static final long DAY_MS = 24 * HOUR_MS;
@@ -1758,6 +1761,19 @@ public class AppStandbyControllerTests {
}
@Test
+ public void testSetAdminProtectedPackages() {
+ assertAdminProtectedPackagesForTest(USER_ID, (String[]) null);
+ assertAdminProtectedPackagesForTest(USER_ID2, (String[]) null);
+
+ setAdminProtectedPackages(USER_ID, ADMIN_PROTECTED_PKG, ADMIN_PROTECTED_PKG2);
+ assertAdminProtectedPackagesForTest(USER_ID, ADMIN_PROTECTED_PKG, ADMIN_PROTECTED_PKG2);
+ assertAdminProtectedPackagesForTest(USER_ID2, (String[]) null);
+
+ setAdminProtectedPackages(USER_ID, (String[]) null);
+ assertAdminProtectedPackagesForTest(USER_ID, (String[]) null);
+ }
+
+ @Test
@FlakyTest(bugId = 185169504)
public void testUserInteraction_CrossProfile() throws Exception {
mInjector.mRunningUsers = new int[] {USER_ID, USER_ID2, USER_ID3};
@@ -2195,6 +2211,28 @@ public class AppStandbyControllerTests {
mController.setActiveAdminApps(new ArraySet<>(Arrays.asList(admins)), userId);
}
+ private void setAdminProtectedPackages(int userId, String... packageNames) {
+ Set<String> adminProtectedPackages = packageNames != null ? new ArraySet<>(
+ Arrays.asList(packageNames)) : null;
+ mController.setAdminProtectedPackages(adminProtectedPackages, userId);
+ }
+
+ private void assertAdminProtectedPackagesForTest(int userId, String... packageNames) {
+ final Set<String> actualAdminProtectedPackages =
+ mController.getAdminProtectedPackagesForTest(userId);
+ if (packageNames == null) {
+ if (actualAdminProtectedPackages != null && !actualAdminProtectedPackages.isEmpty()) {
+ fail("Admin protected packages should be null; " + getAdminAppsStr(userId,
+ actualAdminProtectedPackages));
+ }
+ return;
+ }
+ assertEquals(packageNames.length, actualAdminProtectedPackages.size());
+ for (String adminProtectedPackage : packageNames) {
+ assertTrue(actualAdminProtectedPackages.contains(adminProtectedPackage));
+ }
+ }
+
private void setAndAssertBucket(String pkg, int user, int bucket, int reason) throws Exception {
rearmLatch(pkg);
mController.setAppStandbyBucket(pkg, user, bucket, reason);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 7ff5b4a28f1b..8948e494d851 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -3129,6 +3129,11 @@ public class UsageStatsService extends SystemService implements
}
@Override
+ public void setAdminProtectedPackages(Set<String> packageNames, int userId) {
+ mAppStandby.setAdminProtectedPackages(packageNames, userId);
+ }
+
+ @Override
public void onAdminDataAvailable() {
mAppStandby.onAdminDataAvailable();
}
diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp
index a0cc446cd42d..954b816a52bf 100644
--- a/tools/locked_region_code_injection/Android.bp
+++ b/tools/locked_region_code_injection/Android.bp
@@ -19,3 +19,20 @@ java_binary_host {
"ow2-asm-tree",
],
}
+
+java_library_host {
+ name: "lockedregioncodeinjection_input",
+ manifest: "test/manifest.txt",
+ srcs: ["test/*/*.java"],
+ static_libs: [
+ "guava",
+ "ow2-asm",
+ "ow2-asm-analysis",
+ "ow2-asm-commons",
+ "ow2-asm-tree",
+ "hamcrest-library",
+ "hamcrest",
+ "platform-test-annotations",
+ "junit",
+ ],
+}
diff --git a/tools/locked_region_code_injection/OWNERS b/tools/locked_region_code_injection/OWNERS
new file mode 100644
index 000000000000..bd43f1736ca5
--- /dev/null
+++ b/tools/locked_region_code_injection/OWNERS
@@ -0,0 +1,4 @@
+# Everyone in frameworks/base is included by default
+shayba@google.com
+shombert@google.com
+timmurray@google.com
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
index 81a077324e6c..2067bb4ef2fe 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
@@ -13,37 +13,51 @@
*/
package lockedregioncodeinjection;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
+import static com.google.common.base.Preconditions.checkElementIndex;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.TryCatchBlockSorter;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LabelNode;
+import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.Frame;
-import static com.google.common.base.Preconditions.checkElementIndex;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
/**
- * This visitor does two things:
+ * This visitor operates on two kinds of targets. For a legacy target, it does the following:
*
- * 1. Finds all the MONITOR_ENTER / MONITOR_EXIT in the byte code and insert the corresponding pre
+ * 1. Finds all the MONITOR_ENTER / MONITOR_EXIT in the byte code and inserts the corresponding pre
* and post methods calls should it matches one of the given target type in the Configuration.
*
* 2. Find all methods that are synchronized and insert pre method calls in the beginning and post
* method calls just before all return instructions.
+ *
+ * For a scoped target, it does the following:
+ *
+ * 1. Finds all the MONITOR_ENTER instructions in the byte code. If the target of the opcode is
+ * named in a --scope switch, then the pre method is invoked ON THE TARGET immediately after
+ * MONITOR_ENTER opcode completes.
+ *
+ * 2. Finds all the MONITOR_EXIT instructions in the byte code. If the target of the opcode is
+ * named in a --scope switch, then the post method is invoked ON THE TARGET immediately before
+ * MONITOR_EXIT opcode completes.
*/
class LockFindingClassVisitor extends ClassVisitor {
private String className = null;
@@ -73,12 +87,16 @@ class LockFindingClassVisitor extends ClassVisitor {
class LockFindingMethodVisitor extends MethodVisitor {
private String owner;
private MethodVisitor chain;
+ private final String className;
+ private final String methodName;
public LockFindingMethodVisitor(String owner, MethodNode mn, MethodVisitor chain) {
super(Utils.ASM_VERSION, mn);
assert owner != null;
this.owner = owner;
this.chain = chain;
+ className = owner;
+ methodName = mn.name;
}
@SuppressWarnings("unchecked")
@@ -93,6 +111,12 @@ class LockFindingClassVisitor extends ClassVisitor {
for (LockTarget t : targets) {
if (t.getTargetDesc().equals("L" + owner + ";")) {
ownerMonitor = t;
+ if (ownerMonitor.getScoped()) {
+ final String emsg = String.format(
+ "scoped targets do not support synchronized methods in %s.%s()",
+ className, methodName);
+ throw new RuntimeException(emsg);
+ }
}
}
}
@@ -118,9 +142,11 @@ class LockFindingClassVisitor extends ClassVisitor {
AbstractInsnNode s = instructions.getFirst();
MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
ownerMonitor.getPreOwner(), ownerMonitor.getPreMethod(), "()V", false);
- insertMethodCallBefore(mn, frameMap, handlersMap, s, 0, call);
+ insertMethodCallBeforeSync(mn, frameMap, handlersMap, s, 0, call);
}
+ boolean anyDup = false;
+
for (int i = 0; i < instructions.size(); i++) {
AbstractInsnNode s = instructions.get(i);
@@ -131,9 +157,15 @@ class LockFindingClassVisitor extends ClassVisitor {
LockTargetState state = (LockTargetState) operand;
for (int j = 0; j < state.getTargets().size(); j++) {
LockTarget target = state.getTargets().get(j);
- MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
- target.getPreOwner(), target.getPreMethod(), "()V", false);
- insertMethodCallAfter(mn, frameMap, handlersMap, s, i, call);
+ MethodInsnNode call = methodCall(target, true);
+ if (target.getScoped()) {
+ TypeInsnNode cast = typeCast(target);
+ i += insertInvokeAcquire(mn, frameMap, handlersMap, s, i,
+ call, cast);
+ anyDup = true;
+ } else {
+ i += insertMethodCallBefore(mn, frameMap, handlersMap, s, i, call);
+ }
}
}
}
@@ -144,8 +176,9 @@ class LockFindingClassVisitor extends ClassVisitor {
if (operand instanceof LockTargetState) {
LockTargetState state = (LockTargetState) operand;
for (int j = 0; j < state.getTargets().size(); j++) {
- // The instruction after a monitor_exit should be a label for the end of the implicit
- // catch block that surrounds the synchronized block to call monitor_exit when an exception
+ // The instruction after a monitor_exit should be a label for
+ // the end of the implicit catch block that surrounds the
+ // synchronized block to call monitor_exit when an exception
// occurs.
checkState(instructions.get(i + 1).getType() == AbstractInsnNode.LABEL,
"Expected to find label after monitor exit");
@@ -161,9 +194,16 @@ class LockFindingClassVisitor extends ClassVisitor {
"Expected label to be the end of monitor exit's try block");
LockTarget target = state.getTargets().get(j);
- MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
- target.getPostOwner(), target.getPostMethod(), "()V", false);
- insertMethodCallAfter(mn, frameMap, handlersMap, label, labelIndex, call);
+ MethodInsnNode call = methodCall(target, false);
+ if (target.getScoped()) {
+ TypeInsnNode cast = typeCast(target);
+ i += insertInvokeRelease(mn, frameMap, handlersMap, s, i,
+ call, cast);
+ anyDup = true;
+ } else {
+ insertMethodCallAfter(mn, frameMap, handlersMap, label,
+ labelIndex, call);
+ }
}
}
}
@@ -174,16 +214,116 @@ class LockFindingClassVisitor extends ClassVisitor {
MethodInsnNode call =
new MethodInsnNode(Opcodes.INVOKESTATIC, ownerMonitor.getPostOwner(),
ownerMonitor.getPostMethod(), "()V", false);
- insertMethodCallBefore(mn, frameMap, handlersMap, s, i, call);
+ insertMethodCallBeforeSync(mn, frameMap, handlersMap, s, i, call);
i++; // Skip ahead. Otherwise, we will revisit this instruction again.
}
}
+
+ if (anyDup) {
+ mn.maxStack++;
+ }
+
super.visitEnd();
mn.accept(chain);
}
+
+ // Insert a call to a monitor pre handler. The node and the index identify the
+ // monitorenter call itself. Insert DUP immediately prior to the MONITORENTER.
+ // Insert the typecast and call (in that order) after the MONITORENTER.
+ public int insertInvokeAcquire(MethodNode mn, List<Frame> frameMap,
+ List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
+ MethodInsnNode call, TypeInsnNode cast) {
+ InsnList instructions = mn.instructions;
+
+ // Insert a DUP right before MONITORENTER, to capture the object being locked.
+ // Note that the object will be typed as java.lang.Object.
+ instructions.insertBefore(node, new InsnNode(Opcodes.DUP));
+ frameMap.add(index, frameMap.get(index));
+ handlersMap.add(index, handlersMap.get(index));
+
+ // Insert the call right after the MONITORENTER. These entries are pushed after
+ // MONITORENTER so they are inserted in reverse order. MONITORENTER should be
+ // the target of a try/catch block, which means it must be immediately
+ // followed by a label (which is part of the try/catch block definition).
+ // Move forward past the label so the invocation in inside the proper block.
+ // Throw an error if the next instruction is not a label.
+ node = node.getNext();
+ if (!(node instanceof LabelNode)) {
+ throw new RuntimeException(String.format("invalid bytecode sequence in %s.%s()",
+ className, methodName));
+ }
+ node = node.getNext();
+ index = instructions.indexOf(node);
+
+ instructions.insertBefore(node, cast);
+ frameMap.add(index, frameMap.get(index));
+ handlersMap.add(index, handlersMap.get(index));
+
+ instructions.insertBefore(node, call);
+ frameMap.add(index, frameMap.get(index));
+ handlersMap.add(index, handlersMap.get(index));
+
+ return 3;
+ }
+
+ // Insert instructions completely before the current opcode. This is slightly
+ // different from insertMethodCallBefore(), which inserts the call before MONITOREXIT
+ // but inserts the start and end labels after MONITOREXIT.
+ public int insertInvokeRelease(MethodNode mn, List<Frame> frameMap,
+ List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
+ MethodInsnNode call, TypeInsnNode cast) {
+ InsnList instructions = mn.instructions;
+
+ instructions.insertBefore(node, new InsnNode(Opcodes.DUP));
+ frameMap.add(index, frameMap.get(index));
+ handlersMap.add(index, handlersMap.get(index));
+
+ instructions.insertBefore(node, cast);
+ frameMap.add(index, frameMap.get(index));
+ handlersMap.add(index, handlersMap.get(index));
+
+ instructions.insertBefore(node, call);
+ frameMap.add(index, frameMap.get(index));
+ handlersMap.add(index, handlersMap.get(index));
+
+ return 3;
+ }
+ }
+
+ public static MethodInsnNode methodCall(LockTarget target, boolean pre) {
+ String spec = "()V";
+ if (!target.getScoped()) {
+ if (pre) {
+ return new MethodInsnNode(
+ Opcodes.INVOKESTATIC, target.getPreOwner(), target.getPreMethod(), spec);
+ } else {
+ return new MethodInsnNode(
+ Opcodes.INVOKESTATIC, target.getPostOwner(), target.getPostMethod(), spec);
+ }
+ } else {
+ if (pre) {
+ return new MethodInsnNode(
+ Opcodes.INVOKEVIRTUAL, target.getPreOwner(), target.getPreMethod(), spec);
+ } else {
+ return new MethodInsnNode(
+ Opcodes.INVOKEVIRTUAL, target.getPostOwner(), target.getPostMethod(), spec);
+ }
+ }
}
- public static void insertMethodCallBefore(MethodNode mn, List<Frame> frameMap,
+ public static TypeInsnNode typeCast(LockTarget target) {
+ if (!target.getScoped()) {
+ return null;
+ } else {
+ // preOwner and postOwner return the same string for scoped targets.
+ return new TypeInsnNode(Opcodes.CHECKCAST, target.getPreOwner());
+ }
+ }
+
+ /**
+ * Insert a method call before the beginning or end of a synchronized method.
+ */
+ public static void insertMethodCallBeforeSync(MethodNode mn, List<Frame> frameMap,
List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
MethodInsnNode call) {
List<TryCatchBlockNode> handlers = handlersMap.get(index);
@@ -226,6 +366,22 @@ class LockFindingClassVisitor extends ClassVisitor {
updateCatchHandler(mn, handlers, start, end, handlersMap);
}
+ // Insert instructions completely before the current opcode. This is slightly different from
+ // insertMethodCallBeforeSync(), which inserts the call before MONITOREXIT but inserts the
+ // start and end labels after MONITOREXIT.
+ public int insertMethodCallBefore(MethodNode mn, List<Frame> frameMap,
+ List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
+ MethodInsnNode call) {
+ InsnList instructions = mn.instructions;
+
+ instructions.insertBefore(node, call);
+ frameMap.add(index, frameMap.get(index));
+ handlersMap.add(index, handlersMap.get(index));
+
+ return 1;
+ }
+
+
@SuppressWarnings("unchecked")
public static void updateCatchHandler(MethodNode mn, List<TryCatchBlockNode> handlers,
LabelNode start, LabelNode end, List<List<TryCatchBlockNode>> handlersMap) {
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java
index c5e59e3dc64d..5f6240327a7f 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java
@@ -21,14 +21,28 @@ package lockedregioncodeinjection;
public class LockTarget {
public static final LockTarget NO_TARGET = new LockTarget("", null, null);
+ // The lock which must be instrumented, in Java internal form (L<path>;).
private final String targetDesc;
+ // The methods to be called when the lock is taken (released). For non-scoped locks,
+ // these are fully qualified static methods. For scoped locks, these are the
+ // unqualified names of a member method of the target lock.
private final String pre;
private final String post;
+ // If true, the pre and post methods are virtual on the target class. The pre and post methods
+ // are both called while the lock is held. If this field is false then the pre and post methods
+ // take no parameters and the post method is called after the lock is released. This is legacy
+ // behavior.
+ private final boolean scoped;
- public LockTarget(String targetDesc, String pre, String post) {
+ public LockTarget(String targetDesc, String pre, String post, boolean scoped) {
this.targetDesc = targetDesc;
this.pre = pre;
this.post = post;
+ this.scoped = scoped;
+ }
+
+ public LockTarget(String targetDesc, String pre, String post) {
+ this(targetDesc, pre, post, false);
}
public String getTargetDesc() {
@@ -40,7 +54,11 @@ public class LockTarget {
}
public String getPreOwner() {
- return pre.substring(0, pre.lastIndexOf('.'));
+ if (scoped) {
+ return targetDesc.substring(1, targetDesc.length() - 1);
+ } else {
+ return pre.substring(0, pre.lastIndexOf('.'));
+ }
}
public String getPreMethod() {
@@ -52,10 +70,23 @@ public class LockTarget {
}
public String getPostOwner() {
- return post.substring(0, post.lastIndexOf('.'));
+ if (scoped) {
+ return targetDesc.substring(1, targetDesc.length() - 1);
+ } else {
+ return post.substring(0, post.lastIndexOf('.'));
+ }
}
public String getPostMethod() {
return post.substring(post.lastIndexOf('.') + 1);
}
+
+ public boolean getScoped() {
+ return scoped;
+ }
+
+ @Override
+ public String toString() {
+ return targetDesc + ":" + pre + ":" + post;
+ }
}
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java
index 99d841829132..5df0160abd8c 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java
@@ -13,10 +13,11 @@
*/
package lockedregioncodeinjection;
-import java.util.List;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.analysis.BasicValue;
+import java.util.List;
+
public class LockTargetState extends BasicValue {
private final List<LockTarget> lockTargets;
@@ -31,4 +32,10 @@ public class LockTargetState extends BasicValue {
public List<LockTarget> getTargets() {
return lockTargets;
}
+
+ @Override
+ public String toString() {
+ return "LockTargetState(" + getType().getDescriptor()
+ + ", " + lockTargets.size() + ")";
+ }
}
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java
index 828cce72dda9..d22ea2338ff7 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java
@@ -21,7 +21,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.Collections;
+import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
@@ -36,6 +36,7 @@ public class Main {
String legacyTargets = null;
String legacyPreMethods = null;
String legacyPostMethods = null;
+ List<LockTarget> targets = new ArrayList<>();
for (int i = 0; i < args.length; i++) {
if ("-i".equals(args[i].trim())) {
i++;
@@ -52,23 +53,25 @@ public class Main {
} else if ("--post".equals(args[i].trim())) {
i++;
legacyPostMethods = args[i].trim();
+ } else if ("--scoped".equals(args[i].trim())) {
+ i++;
+ targets.add(Utils.getScopedTarget(args[i].trim()));
}
-
}
- // TODO(acleung): Better help message than asserts.
- assert inJar != null;
- assert outJar != null;
+ if (inJar == null) {
+ throw new RuntimeException("missing input jar path");
+ }
+ if (outJar == null) {
+ throw new RuntimeException("missing output jar path");
+ }
assert legacyTargets == null || (legacyPreMethods != null && legacyPostMethods != null);
ZipFile zipSrc = new ZipFile(inJar);
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outJar));
- List<LockTarget> targets = null;
if (legacyTargets != null) {
- targets = Utils.getTargetsFromLegacyJackConfig(legacyTargets, legacyPreMethods,
- legacyPostMethods);
- } else {
- targets = Collections.emptyList();
+ targets.addAll(Utils.getTargetsFromLegacyJackConfig(legacyTargets, legacyPreMethods,
+ legacyPostMethods));
}
Enumeration<? extends ZipEntry> srcEntries = zipSrc.entries();
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
index b44e8b42f052..bfef10611e6c 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
@@ -44,4 +44,27 @@ public class Utils {
return config;
}
+
+ /**
+ * Returns a single {@link LockTarget} from a string. The target is a comma-separated list of
+ * the target class, the request method, the release method, and a boolean which is true if this
+ * is a scoped target and false if this is a legacy target. The boolean is optional and
+ * defaults to true.
+ */
+ public static LockTarget getScopedTarget(String arg) {
+ String[] c = arg.split(",");
+ if (c.length == 3) {
+ return new LockTarget(c[0], c[1], c[2], true);
+ } else if (c.length == 4) {
+ if (c[3].equals("true")) {
+ return new LockTarget(c[0], c[1], c[2], true);
+ } else if (c[3].equals("false")) {
+ return new LockTarget(c[0], c[1], c[2], false);
+ } else {
+ System.err.println("illegal target parameter \"" + c[3] + "\"");
+ }
+ }
+ // Fall through
+ throw new RuntimeException("invalid scoped target format");
+ }
}
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
index 31fa0bf63416..28f00b9c897a 100644
--- a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
@@ -17,7 +17,10 @@ import org.junit.Assert;
import org.junit.Test;
/**
- * To run the unit tests:
+ * To run the unit tests, first build the two necessary artifacts. Do this explicitly as they are
+ * not generally retained by a normal "build all". After lunching a target:
+ * m lockedregioncodeinjection
+ * m lockedregioncodeinjection_input
*
* <pre>
* <code>
@@ -29,31 +32,25 @@ import org.junit.Test;
* mkdir -p out
* rm -fr out/*
*
- * # Make booster
- * javac -cp lib/asm-7.0_BETA.jar:lib/asm-commons-7.0_BETA.jar:lib/asm-tree-7.0_BETA.jar:lib/asm-analysis-7.0_BETA.jar:lib/guava-21.0.jar src&#47;*&#47;*.java -d out/
- * pushd out
- * jar cfe lockedregioncodeinjection.jar lockedregioncodeinjection.Main *&#47;*.class
- * popd
- *
- * # Make unit tests.
- * javac -cp lib/junit-4.12.jar test&#47;*&#47;*.java -d out/
- *
- * pushd out
- * jar cfe test_input.jar lockedregioncodeinjection.Test *&#47;*.class
- * popd
+ * # Paths to the build artifacts. These assume linux-x86; YMMV.
+ * ROOT=$TOP/out/host/linux-x86
+ * EXE=$ROOT/bin/lockedregioncodeinjection
+ * INPUT=$ROOT/frameworkd/lockedregioncodeinjection_input.jar
*
* # Run tool on unit tests.
- * java -ea -cp lib/asm-7.0_BETA.jar:lib/asm-commons-7.0_BETA.jar:lib/asm-tree-7.0_BETA.jar:lib/asm-analysis-7.0_BETA.jar:lib/guava-21.0.jar:out/lockedregioncodeinjection.jar \
- * lockedregioncodeinjection.Main \
- * -i out/test_input.jar -o out/test_output.jar \
+ * $EXE -i $INPUT -o out/test_output.jar \
* --targets 'Llockedregioncodeinjection/TestTarget;' \
* --pre 'lockedregioncodeinjection/TestTarget.boost' \
* --post 'lockedregioncodeinjection/TestTarget.unboost'
*
* # Run unit tests.
- * java -ea -cp lib/hamcrest-core-1.3.jar:lib/junit-4.12.jar:out/test_output.jar \
+ * java -ea -cp out/test_output.jar \
* org.junit.runner.JUnitCore lockedregioncodeinjection.TestMain
* </code>
+ * OR
+ * <code>
+ * bash test/unit-test.sh
+ * </code>
* </pre>
*/
public class TestMain {
@@ -64,7 +61,9 @@ public class TestMain {
Assert.assertEquals(TestTarget.boostCount, 0);
Assert.assertEquals(TestTarget.unboostCount, 0);
- Assert.assertEquals(TestTarget.unboostCount, 0);
+ Assert.assertEquals(TestTarget.invokeCount, 0);
+ Assert.assertEquals(TestTarget.boostCountLocked, 0);
+ Assert.assertEquals(TestTarget.unboostCountLocked, 0);
synchronized (t) {
Assert.assertEquals(TestTarget.boostCount, 1);
@@ -75,6 +74,8 @@ public class TestMain {
Assert.assertEquals(TestTarget.boostCount, 1);
Assert.assertEquals(TestTarget.unboostCount, 1);
Assert.assertEquals(TestTarget.invokeCount, 1);
+ Assert.assertEquals(TestTarget.boostCountLocked, 0);
+ Assert.assertEquals(TestTarget.unboostCountLocked, 0);
}
@Test
@@ -84,12 +85,16 @@ public class TestMain {
Assert.assertEquals(TestTarget.boostCount, 0);
Assert.assertEquals(TestTarget.unboostCount, 0);
+ Assert.assertEquals(TestTarget.boostCountLocked, 0);
+ Assert.assertEquals(TestTarget.unboostCountLocked, 0);
t.synchronizedCall();
Assert.assertEquals(TestTarget.boostCount, 1);
Assert.assertEquals(TestTarget.unboostCount, 1);
Assert.assertEquals(TestTarget.invokeCount, 1);
+ Assert.assertEquals(TestTarget.boostCountLocked, 0);
+ Assert.assertEquals(TestTarget.unboostCountLocked, 0);
}
@Test
@@ -99,12 +104,16 @@ public class TestMain {
Assert.assertEquals(TestTarget.boostCount, 0);
Assert.assertEquals(TestTarget.unboostCount, 0);
+ Assert.assertEquals(TestTarget.boostCountLocked, 0);
+ Assert.assertEquals(TestTarget.unboostCountLocked, 0);
t.synchronizedCallReturnInt();
Assert.assertEquals(TestTarget.boostCount, 1);
Assert.assertEquals(TestTarget.unboostCount, 1);
Assert.assertEquals(TestTarget.invokeCount, 1);
+ Assert.assertEquals(TestTarget.boostCountLocked, 0);
+ Assert.assertEquals(TestTarget.unboostCountLocked, 0);
}
@Test
@@ -253,4 +262,125 @@ public class TestMain {
Assert.assertEquals(TestTarget.invokeCount, 1);
}
+ @Test
+ public void testScopedTarget() {
+ TestScopedTarget target = new TestScopedTarget();
+ Assert.assertEquals(0, target.scopedLock().mLevel);
+
+ synchronized (target.scopedLock()) {
+ Assert.assertEquals(1, target.scopedLock().mLevel);
+ }
+ Assert.assertEquals(0, target.scopedLock().mLevel);
+
+ synchronized (target.scopedLock()) {
+ synchronized (target.scopedLock()) {
+ Assert.assertEquals(2, target.scopedLock().mLevel);
+ }
+ }
+ Assert.assertEquals(0, target.scopedLock().mLevel);
+ }
+
+ @Test
+ public void testScopedExceptionHandling() {
+ TestScopedTarget target = new TestScopedTarget();
+ Assert.assertEquals(0, target.scopedLock().mLevel);
+
+ boolean handled;
+
+ // 1: an exception inside the block properly releases the lock.
+ handled = false;
+ try {
+ synchronized (target.scopedLock()) {
+ Assert.assertEquals(true, Thread.holdsLock(target.scopedLock()));
+ Assert.assertEquals(1, target.scopedLock().mLevel);
+ throw new RuntimeException();
+ }
+ } catch (RuntimeException e) {
+ Assert.assertEquals(0, target.scopedLock().mLevel);
+ handled = true;
+ }
+ Assert.assertEquals(0, target.scopedLock().mLevel);
+ Assert.assertEquals(true, handled);
+ // Just verify that the lock can still be taken
+ Assert.assertEquals(false, Thread.holdsLock(target.scopedLock()));
+
+ // 2: An exception inside the monitor enter function
+ handled = false;
+ target.throwOnEnter(true);
+ try {
+ synchronized (target.scopedLock()) {
+ // The exception was thrown inside monitorEnter(), so the code should
+ // never reach this point.
+ Assert.assertEquals(0, 1);
+ }
+ } catch (RuntimeException e) {
+ Assert.assertEquals(0, target.scopedLock().mLevel);
+ handled = true;
+ }
+ Assert.assertEquals(0, target.scopedLock().mLevel);
+ Assert.assertEquals(true, handled);
+ // Just verify that the lock can still be taken
+ Assert.assertEquals(false, Thread.holdsLock(target.scopedLock()));
+
+ // 3: An exception inside the monitor exit function
+ handled = false;
+ target.throwOnEnter(true);
+ try {
+ synchronized (target.scopedLock()) {
+ Assert.assertEquals(true, Thread.holdsLock(target.scopedLock()));
+ Assert.assertEquals(1, target.scopedLock().mLevel);
+ }
+ } catch (RuntimeException e) {
+ Assert.assertEquals(0, target.scopedLock().mLevel);
+ handled = true;
+ }
+ Assert.assertEquals(0, target.scopedLock().mLevel);
+ Assert.assertEquals(true, handled);
+ // Just verify that the lock can still be taken
+ Assert.assertEquals(false, Thread.holdsLock(target.scopedLock()));
+ }
+
+ // Provide an in-class type conversion for the scoped target.
+ private Object untypedLock(TestScopedTarget target) {
+ return target.scopedLock();
+ }
+
+ @Test
+ public void testScopedLockTyping() {
+ TestScopedTarget target = new TestScopedTarget();
+ Assert.assertEquals(target.scopedLock().mLevel, 0);
+
+ // Scoped lock injection works on the static type of an object. In general, it is
+ // a very bad idea to do type conversion on scoped locks, but the general rule is
+ // that conversions within a single method are recognized by the lock injection
+ // tool and injection occurs. Conversions outside a single method are not
+ // recognized and injection does not occur.
+
+ // 1. Conversion occurs outside the class. The visible type of the lock is Object
+ // in this block, so no injection takes place on 'untypedLock', even though the
+ // dynamic type is TestScopedLock.
+ synchronized (target.untypedLock()) {
+ Assert.assertEquals(0, target.scopedLock().mLevel);
+ Assert.assertEquals(true, target.untypedLock() instanceof TestScopedLock);
+ Assert.assertEquals(true, Thread.holdsLock(target.scopedLock()));
+ }
+
+ // 2. Conversion occurs inside the class but in another method. The visible type
+ // of the lock is Object in this block, so no injection takes place on
+ // 'untypedLock', even though the dynamic type is TestScopedLock.
+ synchronized (untypedLock(target)) {
+ Assert.assertEquals(0, target.scopedLock().mLevel);
+ Assert.assertEquals(true, target.untypedLock() instanceof TestScopedLock);
+ Assert.assertEquals(true, Thread.holdsLock(target.scopedLock()));
+ }
+
+ // 3. Conversion occurs inside the method. The compiler can determine the type of
+ // the lock within a single function, so injection does take place here.
+ Object untypedLock = target.scopedLock();
+ synchronized (untypedLock) {
+ Assert.assertEquals(1, target.scopedLock().mLevel);
+ Assert.assertEquals(true, untypedLock instanceof TestScopedLock);
+ Assert.assertEquals(true, Thread.holdsLock(target.scopedLock()));
+ }
+ }
}
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedLock.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedLock.java
new file mode 100644
index 000000000000..7441d2b1da48
--- /dev/null
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedLock.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 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 lockedregioncodeinjection;
+
+public class TestScopedLock {
+ public int mEntered = 0;
+ public int mExited = 0;
+
+ public int mLevel = 0;
+ private final TestScopedTarget mTarget;
+
+ TestScopedLock(TestScopedTarget target) {
+ mTarget = target;
+ }
+
+ void monitorEnter() {
+ mLevel++;
+ mEntered++;
+ mTarget.enter(mLevel);
+ }
+
+ void monitorExit() {
+ mLevel--;
+ mExited++;
+ mTarget.exit(mLevel);
+ }
+}
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedTarget.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedTarget.java
new file mode 100644
index 000000000000..c80975e9c6b3
--- /dev/null
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedTarget.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 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 lockedregioncodeinjection;
+
+public class TestScopedTarget {
+
+ public final TestScopedLock mLock;;
+
+ private boolean mNextEnterThrows = false;
+ private boolean mNextExitThrows = false;
+
+ TestScopedTarget() {
+ mLock = new TestScopedLock(this);
+ }
+
+ TestScopedLock scopedLock() {
+ return mLock;
+ }
+
+ Object untypedLock() {
+ return mLock;
+ }
+
+ void enter(int level) {
+ if (mNextEnterThrows) {
+ mNextEnterThrows = false;
+ throw new RuntimeException();
+ }
+ }
+
+ void exit(int level) {
+ if (mNextExitThrows) {
+ mNextExitThrows = false;
+ throw new RuntimeException();
+ }
+ }
+
+ void throwOnEnter(boolean b) {
+ mNextEnterThrows = b;
+ }
+
+ void throwOnExit(boolean b) {
+ mNextExitThrows = b;
+ }
+}
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
index d1c8f340a598..e3ba6a77e3b7 100644
--- a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
@@ -19,8 +19,17 @@ public class TestTarget {
public static int invokeCount = 0;
public static boolean nextUnboostThrows = false;
+ // If this is not null, then this is the lock under test. The lock must not be held when boost()
+ // or unboost() are called.
+ public static Object mLock = null;
+ public static int boostCountLocked = 0;
+ public static int unboostCountLocked = 0;
+
public static void boost() {
boostCount++;
+ if (mLock != null && Thread.currentThread().holdsLock(mLock)) {
+ boostCountLocked++;
+ }
}
public static void unboost() {
@@ -29,6 +38,9 @@ public class TestTarget {
throw new RuntimeException();
}
unboostCount++;
+ if (mLock != null && Thread.currentThread().holdsLock(mLock)) {
+ unboostCountLocked++;
+ }
}
public static void invoke() {
diff --git a/tools/locked_region_code_injection/test/manifest.txt b/tools/locked_region_code_injection/test/manifest.txt
new file mode 100644
index 000000000000..2314c188721c
--- /dev/null
+++ b/tools/locked_region_code_injection/test/manifest.txt
@@ -0,0 +1 @@
+Main-Class: org.junit.runner.JUnitCore
diff --git a/tools/locked_region_code_injection/test/unit-test.sh b/tools/locked_region_code_injection/test/unit-test.sh
new file mode 100755
index 000000000000..9fa6f39af14a
--- /dev/null
+++ b/tools/locked_region_code_injection/test/unit-test.sh
@@ -0,0 +1,98 @@
+#! /bin/bash
+#
+
+# 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 script runs the tests for the lockedregioninjectioncode. See
+# TestMain.java for the invocation. The script expects that a full build has
+# already been done and artifacts are in $TOP/out.
+
+# Compute the default top of the workspace. The following code copies the
+# strategy of croot. (croot cannot be usd directly because it is a function and
+# functions are not carried over into subshells.) This gives the correct answer
+# if run from inside a workspace. If run from outside a workspace, supply TOP
+# on the command line.
+TOPFILE=build/make/core/envsetup.mk
+TOP=$(dirname $(realpath $0))
+while [[ ! $TOP = / && ! -f $TOP/$TOPFILE ]]; do
+ TOP=$(dirname $TOP)
+done
+# TOP is "/" if this script is located outside a workspace.
+
+# If the user supplied a top directory, use it instead
+if [[ -n $1 ]]; then
+ TOP=$1
+ shift
+fi
+if [[ -z $TOP || $TOP = / ]]; then
+ echo "usage: $0 <workspace-root>"
+ exit 1
+elif [[ ! -d $TOP ]]; then
+ echo "$TOP is not a directory"
+ exit 1
+elif [[ ! -d $TOP/prebuilts/misc/common ]]; then
+ echo "$TOP does not look like w workspace"
+ exit 1
+fi
+echo "Using workspace $TOP"
+
+# Pick up the current java compiler. The lunch target is not very important,
+# since most, if not all, will use the same host binaries.
+pushd $TOP > /dev/null
+. build/envsetup.sh > /dev/null 2>&1
+lunch redfin-userdebug > /dev/null 2>&1
+popd > /dev/null
+
+# Bail on any error
+set -o pipefail
+trap 'exit 1' ERR
+
+# Create the two sources
+pushd $TOP > /dev/null
+m lockedregioncodeinjection
+m lockedregioncodeinjection_input
+popd > /dev/null
+
+# Create a temporary directory outside of the workspace.
+OUT=$TOP/out/host/test/lockedregioncodeinjection
+echo
+
+# Clean the directory
+if [[ -d $OUT ]]; then rm -r $OUT; fi
+mkdir -p $OUT
+
+ROOT=$TOP/out/host/linux-x86
+EXE=$ROOT/bin/lockedregioncodeinjection
+INP=$ROOT/framework/lockedregioncodeinjection_input.jar
+
+# Run tool on unit tests.
+$EXE \
+ -i $INP -o $OUT/test_output.jar \
+ --targets 'Llockedregioncodeinjection/TestTarget;' \
+ --pre 'lockedregioncodeinjection/TestTarget.boost' \
+ --post 'lockedregioncodeinjection/TestTarget.unboost' \
+ --scoped 'Llockedregioncodeinjection/TestScopedLock;,monitorEnter,monitorExit'
+
+# Run unit tests.
+java -ea -cp $OUT/test_output.jar \
+ org.junit.runner.JUnitCore lockedregioncodeinjection.TestMain
+
+# Extract the class files and decompile them for possible post-analysis.
+pushd $OUT > /dev/null
+jar -x --file test_output.jar lockedregioncodeinjection
+for class in lockedregioncodeinjection/*.class; do
+ javap -c -v $class > ${class%.class}.asm
+done
+popd > /dev/null
+
+echo "artifacts are in $OUT"