summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp7
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobInfo.java53
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobStore.java2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java81
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java11
-rw-r--r--cmds/uinput/README.md3
-rw-r--r--cmds/uinput/jni/com_android_commands_uinput_Device.cpp18
-rw-r--r--cmds/uinput/jni/com_android_commands_uinput_Device.h12
-rw-r--r--cmds/uinput/src/com/android/commands/uinput/Device.java107
-rw-r--r--cmds/uinput/src/com/android/commands/uinput/EvemuParser.java28
-rw-r--r--cmds/uinput/src/com/android/commands/uinput/Event.java30
-rw-r--r--cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java3
-rw-r--r--cmds/uinput/src/com/android/commands/uinput/Uinput.java4
-rw-r--r--cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java30
-rw-r--r--core/api/current.txt74
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/java/android/app/admin/DeviceAdminInfo.java17
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java33
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig7
-rw-r--r--core/res/res/values/config_telephony.xml5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java14
-rw-r--r--media/java/android/media/metrics/EditingEndedEvent.java151
-rw-r--r--media/java/android/media/metrics/MediaItemInfo.java565
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java40
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt101
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt25
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java4
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java4
-rw-r--r--services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java328
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java9
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java111
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java74
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java109
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java18
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java63
-rw-r--r--services/usb/Android.bp12
-rw-r--r--services/usb/java/com/android/server/usb/UsbHandlerManager.java2
-rw-r--r--services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java83
-rw-r--r--services/usb/java/com/android/server/usb/UsbSettingsManager.java4
-rw-r--r--services/usb/java/com/android/server/usb/UsbUserSettingsManager.java4
-rw-r--r--services/usb/java/com/android/server/usb/flags/usb_flags.aconfig8
-rw-r--r--telephony/java/android/telephony/DomainSelectionService.java2
-rw-r--r--tests/UsbManagerTests/Android.bp9
-rw-r--r--tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java195
52 files changed, 2261 insertions, 353 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 1c6df75a4f02..79b4fd6ee9ae 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -855,6 +855,13 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "device_policy_aconfig_flags_lib_host",
+ aconfig_declarations: "device_policy_aconfig_flags",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
cc_aconfig_library {
name: "device_policy_aconfig_flags_c_lib",
aconfig_declarations: "device_policy_aconfig_flags",
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 7de67993bc62..52c0ac1d981d 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -124,6 +124,15 @@ public class JobInfo implements Parcelable {
@Overridable // Aid in testing
public static final long ENFORCE_MINIMUM_TIME_WINDOWS = 311402873L;
+ /**
+ * Require that minimum latencies and override deadlines are nonnegative.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long REJECT_NEGATIVE_DELAYS_AND_DEADLINES = 323349338L;
+
/** @hide */
@IntDef(prefix = { "NETWORK_TYPE_" }, value = {
NETWORK_TYPE_NONE,
@@ -692,14 +701,14 @@ public class JobInfo implements Parcelable {
* @see JobInfo.Builder#setMinimumLatency(long)
*/
public long getMinLatencyMillis() {
- return minLatencyMillis;
+ return Math.max(0, minLatencyMillis);
}
/**
* @see JobInfo.Builder#setOverrideDeadline(long)
*/
public long getMaxExecutionDelayMillis() {
- return maxExecutionDelayMillis;
+ return Math.max(0, maxExecutionDelayMillis);
}
/**
@@ -818,7 +827,7 @@ public class JobInfo implements Parcelable {
* @hide
*/
public boolean hasEarlyConstraint() {
- return hasEarlyConstraint;
+ return hasEarlyConstraint && minLatencyMillis > 0;
}
/**
@@ -827,7 +836,7 @@ public class JobInfo implements Parcelable {
* @hide
*/
public boolean hasLateConstraint() {
- return hasLateConstraint;
+ return hasLateConstraint && maxExecutionDelayMillis >= 0;
}
@Override
@@ -1869,6 +1878,13 @@ public class JobInfo implements Parcelable {
* Because it doesn't make sense setting this property on a periodic job, doing so will
* throw an {@link java.lang.IllegalArgumentException} when
* {@link android.app.job.JobInfo.Builder#build()} is called.
+ *
+ * Negative latencies also don't make sense for a job and are indicative of an error,
+ * so starting in Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
+ * setting a negative deadline will result in
+ * {@link android.app.job.JobInfo.Builder#build()} throwing an
+ * {@link java.lang.IllegalArgumentException}.
+ *
* @param minLatencyMillis Milliseconds before which this job will not be considered for
* execution.
* @see JobInfo#getMinLatencyMillis()
@@ -1892,6 +1908,13 @@ public class JobInfo implements Parcelable {
* throw an {@link java.lang.IllegalArgumentException} when
* {@link android.app.job.JobInfo.Builder#build()} is called.
*
+ * <p>
+ * Negative deadlines also don't make sense for a job and are indicative of an error,
+ * so starting in Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
+ * setting a negative deadline will result in
+ * {@link android.app.job.JobInfo.Builder#build()} throwing an
+ * {@link java.lang.IllegalArgumentException}.
+ *
* <p class="note">
* Since a job will run once the deadline has passed regardless of the status of other
* constraints, setting a deadline of 0 (or a {@link #setMinimumLatency(long) delay} equal
@@ -2189,13 +2212,15 @@ public class JobInfo implements Parcelable {
public JobInfo build() {
return build(Compatibility.isChangeEnabled(DISALLOW_DEADLINES_FOR_PREFETCH_JOBS),
Compatibility.isChangeEnabled(REJECT_NEGATIVE_NETWORK_ESTIMATES),
- Compatibility.isChangeEnabled(ENFORCE_MINIMUM_TIME_WINDOWS));
+ Compatibility.isChangeEnabled(ENFORCE_MINIMUM_TIME_WINDOWS),
+ Compatibility.isChangeEnabled(REJECT_NEGATIVE_DELAYS_AND_DEADLINES));
}
/** @hide */
public JobInfo build(boolean disallowPrefetchDeadlines,
boolean rejectNegativeNetworkEstimates,
- boolean enforceMinimumTimeWindows) {
+ boolean enforceMinimumTimeWindows,
+ boolean rejectNegativeDelaysAndDeadlines) {
// This check doesn't need to be inside enforceValidity. It's an unnecessary legacy
// check that would ideally be phased out instead.
if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
@@ -2205,7 +2230,7 @@ public class JobInfo implements Parcelable {
}
JobInfo jobInfo = new JobInfo(this);
jobInfo.enforceValidity(disallowPrefetchDeadlines, rejectNegativeNetworkEstimates,
- enforceMinimumTimeWindows);
+ enforceMinimumTimeWindows, rejectNegativeDelaysAndDeadlines);
return jobInfo;
}
@@ -2225,7 +2250,8 @@ public class JobInfo implements Parcelable {
*/
public final void enforceValidity(boolean disallowPrefetchDeadlines,
boolean rejectNegativeNetworkEstimates,
- boolean enforceMinimumTimeWindows) {
+ boolean enforceMinimumTimeWindows,
+ boolean rejectNegativeDelaysAndDeadlines) {
// Check that network estimates require network type and are reasonable values.
if ((networkDownloadBytes > 0 || networkUploadBytes > 0 || minimumNetworkChunkBytes > 0)
&& networkRequest == null) {
@@ -2259,6 +2285,17 @@ public class JobInfo implements Parcelable {
throw new IllegalArgumentException("Minimum chunk size must be positive");
}
+ if (rejectNegativeDelaysAndDeadlines) {
+ if (minLatencyMillis < 0) {
+ throw new IllegalArgumentException(
+ "Minimum latency is negative: " + minLatencyMillis);
+ }
+ if (maxExecutionDelayMillis < 0) {
+ throw new IllegalArgumentException(
+ "Override deadline is negative: " + maxExecutionDelayMillis);
+ }
+ }
+
final boolean hasDeadline = maxExecutionDelayMillis != 0L;
// Check that a deadline was not set on a periodic job.
if (isPeriodic) {
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 a83c099b764d..f819f15b430f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -4850,7 +4850,7 @@ public class JobSchedulerService extends com.android.server.SystemService
Slog.w(TAG, "Uid " + uid + " set bias on its job");
return new JobInfo.Builder(job)
.setBias(JobInfo.BIAS_DEFAULT)
- .build(false, false, false);
+ .build(false, false, false, false);
}
}
@@ -4874,7 +4874,9 @@ public class JobSchedulerService extends com.android.server.SystemService
JobInfo.DISALLOW_DEADLINES_FOR_PREFETCH_JOBS, callingUid),
rejectNegativeNetworkEstimates,
CompatChanges.isChangeEnabled(
- JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS, callingUid));
+ JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS, callingUid),
+ CompatChanges.isChangeEnabled(
+ JobInfo.REJECT_NEGATIVE_DELAYS_AND_DEADLINES, callingUid));
if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
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 53b14d616ecc..d8934d8f83b8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -1495,7 +1495,7 @@ public final class JobStore {
// return value), the deadline is dropped. Periodic jobs require all constraints
// to be met, so there's no issue with their deadlines.
// The same logic applies for other target SDK-based validation checks.
- builtJob = jobBuilder.build(false, false, false);
+ builtJob = jobBuilder.build(false, false, false, false);
} catch (Exception e) {
Slog.w(TAG, "Unable to build job from XML, ignoring: " + jobBuilder.summarize(), e);
return null;
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 6883d18cd937..aec464d0e820 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
@@ -241,6 +241,8 @@ public final class FlexibilityController extends StateController {
private static final long MAX_TIME_WINDOW_MS = 24 * HOUR_IN_MILLIS;
private final JobScoreBucket[] mScoreBuckets = new JobScoreBucket[NUM_SCORE_BUCKETS];
private int mScoreBucketIndex = 0;
+ private long mCachedScoreExpirationTimeElapsed;
+ private int mCachedScore;
public void addScore(int add, long nowElapsed) {
JobScoreBucket bucket = mScoreBuckets[mScoreBucketIndex];
@@ -248,10 +250,17 @@ public final class FlexibilityController extends StateController {
bucket = new JobScoreBucket();
bucket.startTimeElapsed = nowElapsed;
mScoreBuckets[mScoreBucketIndex] = bucket;
+ // Brand new bucket, there's nothing to remove from the score,
+ // so just update the expiration time if needed.
+ mCachedScoreExpirationTimeElapsed = Math.min(mCachedScoreExpirationTimeElapsed,
+ nowElapsed + MAX_TIME_WINDOW_MS);
} else if (bucket.startTimeElapsed < nowElapsed - MAX_TIME_WINDOW_MS) {
// The bucket is too old.
bucket.reset();
bucket.startTimeElapsed = nowElapsed;
+ // Force a recalculation of the cached score instead of just updating the cached
+ // value and time in case there are multiple stale buckets.
+ mCachedScoreExpirationTimeElapsed = nowElapsed;
} else if (bucket.startTimeElapsed
< nowElapsed - MAX_TIME_WINDOW_MS / NUM_SCORE_BUCKETS) {
// The current bucket's duration has completed. Move on to the next bucket.
@@ -261,16 +270,26 @@ public final class FlexibilityController extends StateController {
}
bucket.score += add;
+ mCachedScore += add;
}
public int getScore(long nowElapsed) {
+ if (nowElapsed < mCachedScoreExpirationTimeElapsed) {
+ return mCachedScore;
+ }
int score = 0;
final long earliestElapsed = nowElapsed - MAX_TIME_WINDOW_MS;
+ long earliestValidBucketTimeElapsed = Long.MAX_VALUE;
for (JobScoreBucket bucket : mScoreBuckets) {
if (bucket != null && bucket.startTimeElapsed >= earliestElapsed) {
score += bucket.score;
+ if (earliestValidBucketTimeElapsed > bucket.startTimeElapsed) {
+ earliestValidBucketTimeElapsed = bucket.startTimeElapsed;
+ }
}
}
+ mCachedScore = score;
+ mCachedScoreExpirationTimeElapsed = earliestValidBucketTimeElapsed + MAX_TIME_WINDOW_MS;
return score;
}
@@ -378,10 +397,16 @@ public final class FlexibilityController extends StateController {
@Override
public void prepareForExecutionLocked(JobStatus jobStatus) {
+ if (jobStatus.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
+ // Don't include jobs for the TOP app in the score calculation.
+ return;
+ }
// Use the job's requested priority to determine its score since that is what the developer
// selected and it will be stable across job runs.
- final int score = mFallbackFlexibilityDeadlineScores
- .get(jobStatus.getJob().getPriority(), jobStatus.getJob().getPriority() / 100);
+ final int priority = jobStatus.getJob().getPriority();
+ final int score = mFallbackFlexibilityDeadlineScores.get(priority,
+ FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES
+ .get(priority, priority / 100));
JobScoreTracker jobScoreTracker =
mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName());
if (jobScoreTracker == null) {
@@ -394,6 +419,10 @@ public final class FlexibilityController extends StateController {
@Override
public void unprepareFromExecutionLocked(JobStatus jobStatus) {
+ if (jobStatus.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
+ // Jobs for the TOP app are excluded from the score calculation.
+ return;
+ }
// The job didn't actually start. Undo the score increase.
JobScoreTracker jobScoreTracker =
mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName());
@@ -401,8 +430,10 @@ public final class FlexibilityController extends StateController {
Slog.e(TAG, "Unprepared a job that didn't result in a score change");
return;
}
- final int score = mFallbackFlexibilityDeadlineScores
- .get(jobStatus.getJob().getPriority(), jobStatus.getJob().getPriority() / 100);
+ final int priority = jobStatus.getJob().getPriority();
+ final int score = mFallbackFlexibilityDeadlineScores.get(priority,
+ FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES
+ .get(priority, priority / 100));
jobScoreTracker.addScore(-score, sElapsedRealtimeClock.millis());
}
@@ -649,21 +680,24 @@ public final class FlexibilityController extends StateController {
(long) Math.scalb(mRescheduledJobDeadline, js.getNumPreviousAttempts() - 2),
mMaxRescheduledDeadline);
}
+
+ // Intentionally use the effective priority here. If a job's priority was effectively
+ // lowered, it will be less likely to run quickly given other policies in JobScheduler.
+ // Thus, there's no need to further delay the job based on flex policy.
+ final int jobPriority = js.getEffectivePriority();
+ final int jobScore =
+ getScoreLocked(js.getSourceUid(), js.getSourcePackageName(), nowElapsed);
+ // Set an upper limit on the fallback deadline so that the delay doesn't become extreme.
+ final long fallbackDurationMs = Math.min(3 * mFallbackFlexibilityDeadlineMs,
+ mFallbackFlexibilityDeadlines.get(jobPriority, mFallbackFlexibilityDeadlineMs)
+ + mFallbackFlexibilityAdditionalScoreTimeFactors
+ .get(jobPriority, MINUTE_IN_MILLIS) * jobScore);
+ final long fallbackDeadlineMs = earliest + fallbackDurationMs;
+
if (js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME) {
- // Intentionally use the effective priority here. If a job's priority was effectively
- // lowered, it will be less likely to run quickly given other policies in JobScheduler.
- // Thus, there's no need to further delay the job based on flex policy.
- final int jobPriority = js.getEffectivePriority();
- final int jobScore =
- getScoreLocked(js.getSourceUid(), js.getSourcePackageName(), nowElapsed);
- // Set an upper limit on the fallback deadline so that the delay doesn't become extreme.
- final long fallbackDeadlineMs = Math.min(3 * mFallbackFlexibilityDeadlineMs,
- mFallbackFlexibilityDeadlines.get(jobPriority, mFallbackFlexibilityDeadlineMs)
- + mFallbackFlexibilityAdditionalScoreTimeFactors
- .get(jobPriority, MINUTE_IN_MILLIS) * jobScore);
- return earliest + fallbackDeadlineMs;
+ return fallbackDeadlineMs;
}
- return js.getLatestRunTimeElapsed();
+ return Math.max(fallbackDeadlineMs, js.getLatestRunTimeElapsed());
}
@VisibleForTesting
@@ -976,7 +1010,8 @@ public final class FlexibilityController extends StateController {
// Something has gone horribly wrong. This has only occurred on incorrectly
// configured tests, but add a check here for safety.
Slog.wtf(TAG, "Got invalid latest when scheduling alarm."
- + " Prefetch=" + js.getJob().isPrefetch());
+ + " prefetch=" + js.getJob().isPrefetch()
+ + " periodic=" + js.getJob().isPeriodic());
// Since things have gone wrong, the safest and most reliable thing to do is
// stop applying flex policy to the job.
mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
@@ -991,7 +1026,7 @@ public final class FlexibilityController extends StateController {
if (DEBUG) {
Slog.d(TAG, "scheduleDropNumConstraintsAlarm: "
- + js.getSourcePackageName() + " " + js.getSourceUserId()
+ + js.toShortString()
+ " numApplied: " + js.getNumAppliedFlexibleConstraints()
+ " numRequired: " + js.getNumRequiredFlexibleConstraints()
+ " numSatisfied: " + Integer.bitCount(
@@ -1199,11 +1234,11 @@ public final class FlexibilityController extends StateController {
DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
.put(PRIORITY_MAX, 0);
DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
- .put(PRIORITY_HIGH, 4 * MINUTE_IN_MILLIS);
+ .put(PRIORITY_HIGH, 3 * MINUTE_IN_MILLIS);
DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
- .put(PRIORITY_DEFAULT, 3 * MINUTE_IN_MILLIS);
+ .put(PRIORITY_DEFAULT, 2 * MINUTE_IN_MILLIS);
DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
- .put(PRIORITY_LOW, 2 * MINUTE_IN_MILLIS);
+ .put(PRIORITY_LOW, 1 * MINUTE_IN_MILLIS);
DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
.put(PRIORITY_MIN, 1 * MINUTE_IN_MILLIS);
DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
@@ -1220,7 +1255,7 @@ public final class FlexibilityController extends StateController {
private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
private static final long DEFAULT_RESCHEDULED_JOB_DEADLINE_MS = HOUR_IN_MILLIS;
- private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = 5 * DAY_IN_MILLIS;
+ private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = DAY_IN_MILLIS;
@VisibleForTesting
static final long DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = 3 * DAY_IN_MILLIS;
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 a3a686fdc5c8..edd86e3454a5 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,8 +16,6 @@
package com.android.server.job.controllers;
-import static android.text.format.DateUtils.HOUR_IN_MILLIS;
-
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
@@ -430,9 +428,6 @@ public final class JobStatus {
*/
public static final int INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ = 1 << 2;
- /** Minimum difference between start and end time to have flexible constraint */
- @VisibleForTesting
- static final long MIN_WINDOW_FOR_FLEXIBILITY_MS = HOUR_IN_MILLIS;
/**
* Versatile, persistable flags for a job that's updated within the system server,
* as opposed to {@link JobInfo#flags} that's set by callers.
@@ -657,7 +652,7 @@ public final class JobStatus {
.build());
// Don't perform validation checks at this point since we've already passed the
// initial validation check.
- job = builder.build(false, false, false);
+ job = builder.build(false, false, false, false);
}
this.job = job;
@@ -708,14 +703,10 @@ public final class JobStatus {
final boolean lacksSomeFlexibleConstraints =
((~requiredConstraints) & SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS) != 0
|| mCanApplyTransportAffinities;
- 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()
- && satisfiesMinWindowException
&& (numFailures + numSystemStops) != 1
&& lacksSomeFlexibleConstraints) {
requiredConstraints |= CONSTRAINT_FLEXIBLE;
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
index f1775864aca0..b6e4e0de8cf7 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -154,8 +154,7 @@ will be unregistered. There is no explicit command for unregistering a device.
#### `delay`
-Add a delay between the processing of commands. The delay will be timed from when the last delay
-ended, rather than from the current time, to allow for more precise timings to be produced.
+Add a delay to command processing
| Field | Type | Description |
|:-------------:|:-------------:|:-------------------------- |
diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
index bd61000186e5..a78a46504684 100644
--- a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
+++ b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
@@ -166,14 +166,14 @@ UinputDevice::~UinputDevice() {
::ioctl(mFd, UI_DEV_DESTROY);
}
-void UinputDevice::injectEvent(std::chrono::microseconds timestamp, uint16_t type, uint16_t code,
- int32_t value) {
+void UinputDevice::injectEvent(uint16_t type, uint16_t code, int32_t value) {
struct input_event event = {};
event.type = type;
event.code = code;
event.value = value;
- event.time.tv_sec = timestamp.count() / 1'000'000;
- event.time.tv_usec = timestamp.count() % 1'000'000;
+ timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ TIMESPEC_TO_TIMEVAL(&event.time, &ts);
if (::write(mFd, &event, sizeof(input_event)) < 0) {
ALOGE("Could not write event %" PRIu16 " %" PRIu16 " with value %" PRId32 " : %s", type,
@@ -268,12 +268,12 @@ static void closeUinputDevice(JNIEnv* /* env */, jclass /* clazz */, jlong ptr)
}
}
-static void injectEvent(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jlong timestampMicros,
- jint type, jint code, jint value) {
+static void injectEvent(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint type, jint code,
+ jint value) {
uinput::UinputDevice* d = reinterpret_cast<uinput::UinputDevice*>(ptr);
if (d != nullptr) {
- d->injectEvent(std::chrono::microseconds(timestampMicros), static_cast<uint16_t>(type),
- static_cast<uint16_t>(code), static_cast<int32_t>(value));
+ d->injectEvent(static_cast<uint16_t>(type), static_cast<uint16_t>(code),
+ static_cast<int32_t>(value));
} else {
ALOGE("Could not inject event, Device* is null!");
}
@@ -330,7 +330,7 @@ static JNINativeMethod sMethods[] = {
"(Ljava/lang/String;IIIIIILjava/lang/String;"
"Lcom/android/commands/uinput/Device$DeviceCallback;)J",
reinterpret_cast<void*>(openUinputDevice)},
- {"nativeInjectEvent", "(JJIII)V", reinterpret_cast<void*>(injectEvent)},
+ {"nativeInjectEvent", "(JIII)V", reinterpret_cast<void*>(injectEvent)},
{"nativeConfigure", "(II[I)V", reinterpret_cast<void*>(configure)},
{"nativeSetAbsInfo", "(IILandroid/os/Parcel;)V", reinterpret_cast<void*>(setAbsInfo)},
{"nativeCloseUinputDevice", "(J)V", reinterpret_cast<void*>(closeUinputDevice)},
diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.h b/cmds/uinput/jni/com_android_commands_uinput_Device.h
index 72c8647ae743..9769a75bd9ef 100644
--- a/cmds/uinput/jni/com_android_commands_uinput_Device.h
+++ b/cmds/uinput/jni/com_android_commands_uinput_Device.h
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-#include <android-base/unique_fd.h>
-#include <jni.h>
-#include <linux/input.h>
-
-#include <chrono>
#include <memory>
#include <vector>
+#include <jni.h>
+#include <linux/input.h>
+
+#include <android-base/unique_fd.h>
#include "src/com/android/commands/uinput/InputAbsInfo.h"
namespace android {
@@ -54,8 +53,7 @@ public:
virtual ~UinputDevice();
- void injectEvent(std::chrono::microseconds timestamp, uint16_t type, uint16_t code,
- int32_t value);
+ void injectEvent(uint16_t type, uint16_t code, int32_t value);
int handleEvents(int events);
private:
diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java
index 76ab475adc0a..25d3a341bf6e 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Device.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Device.java
@@ -55,7 +55,7 @@ public class Device {
private final SparseArray<InputAbsInfo> mAbsInfo;
private final OutputStream mOutputStream;
private final Object mCond = new Object();
- private long mTimeToSendNanos;
+ private long mTimeToSend;
static {
System.loadLibrary("uinputcommand_jni");
@@ -65,8 +65,7 @@ public class Device {
int productId, int versionId, int bus, int ffEffectsMax, String port,
DeviceCallback callback);
private static native void nativeCloseUinputDevice(long ptr);
- private static native void nativeInjectEvent(long ptr, long timestampMicros, int type, int code,
- int value);
+ private static native void nativeInjectEvent(long ptr, int type, int code, int value);
private static native void nativeConfigure(int handle, int code, int[] configs);
private static native void nativeSetAbsInfo(int handle, int axisCode, Parcel axisParcel);
private static native int nativeGetEvdevEventTypeByLabel(String label);
@@ -102,54 +101,27 @@ public class Device {
}
mHandler.obtainMessage(MSG_OPEN_UINPUT_DEVICE, args).sendToTarget();
- mTimeToSendNanos = SystemClock.uptimeNanos();
- }
-
- private long getTimeToSendMillis() {
- // Since we can only specify delays in milliseconds but evemu timestamps are in
- // microseconds, we have to round up the delays to avoid setting event timestamps
- // which are in the future (which the kernel would silently reject and replace with
- // the current time).
- //
- // This should be the same as (long) Math.ceil(mTimeToSendNanos / 1_000_000.0), except
- // without the precision loss that comes from converting from long to double and back.
- return mTimeToSendNanos / 1_000_000 + ((mTimeToSendNanos % 1_000_000 > 0) ? 1 : 0);
+ mTimeToSend = SystemClock.uptimeMillis();
}
/**
* Inject uinput events to device
*
* @param events Array of raw uinput events.
- * @param offsetMicros The difference in microseconds between the timestamps of the previous
- * batch of events injected and this batch. If set to -1, the current
- * timestamp will be used.
*/
- public void injectEvent(int[] events, long offsetMicros) {
+ public void injectEvent(int[] events) {
// if two messages are sent at identical time, they will be processed in order received
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = events;
- args.argl1 = offsetMicros;
- args.argl2 = mTimeToSendNanos;
- Message msg = mHandler.obtainMessage(MSG_INJECT_EVENT, args);
- mHandler.sendMessageAtTime(msg, getTimeToSendMillis());
+ Message msg = mHandler.obtainMessage(MSG_INJECT_EVENT, events);
+ mHandler.sendMessageAtTime(msg, mTimeToSend);
}
/**
- * Delay subsequent device activity by the specified amount of time.
+ * Impose a delay to the device for execution.
*
- * <p>Note that although the delay is specified in nanoseconds, due to limitations of {@link
- * Handler}'s API, scheduling only occurs with millisecond precision. When scheduling an
- * injection or sync, the time at which it is scheduled will be rounded up to the nearest
- * millisecond. While this means that a particular injection cannot be scheduled precisely,
- * rounding errors will not accumulate over time. For example, if five injections are scheduled
- * with a delay of 1,200,000ns before each one, the total delay will be 6ms, as opposed to the
- * 10ms it would have been if each individual delay had been rounded up (as {@link EvemuParser}
- * would otherwise have to do to avoid sending timestamps that are in the future).
- *
- * @param delayNanos Time to delay in unit of nanoseconds.
+ * @param delay Time to delay in unit of milliseconds.
*/
- public void addDelayNanos(long delayNanos) {
- mTimeToSendNanos += delayNanos;
+ public void addDelay(int delay) {
+ mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay;
}
/**
@@ -159,8 +131,7 @@ public class Device {
* @param syncToken The token for this sync command.
*/
public void syncEvent(String syncToken) {
- mHandler.sendMessageAtTime(
- mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), getTimeToSendMillis());
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), mTimeToSend);
}
/**
@@ -169,8 +140,7 @@ public class Device {
*/
public void close() {
Message msg = mHandler.obtainMessage(MSG_CLOSE_UINPUT_DEVICE);
- mHandler.sendMessageAtTime(
- msg, Math.max(SystemClock.uptimeMillis(), getTimeToSendMillis()) + 1);
+ mHandler.sendMessageAtTime(msg, Math.max(SystemClock.uptimeMillis(), mTimeToSend) + 1);
try {
synchronized (mCond) {
mCond.wait();
@@ -181,7 +151,6 @@ public class Device {
private class DeviceHandler extends Handler {
private long mPtr;
- private long mLastInjectTimestampMicros = -1;
private int mBarrierToken;
DeviceHandler(Looper looper) {
@@ -191,7 +160,7 @@ public class Device {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_OPEN_UINPUT_DEVICE: {
+ case MSG_OPEN_UINPUT_DEVICE:
SomeArgs args = (SomeArgs) msg.obj;
String name = (String) args.arg1;
mPtr = nativeOpenUinputDevice(name, args.argi1 /* id */,
@@ -208,44 +177,15 @@ public class Device {
}
args.recycle();
break;
- }
- case MSG_INJECT_EVENT: {
- SomeArgs args = (SomeArgs) msg.obj;
- if (mPtr == 0) {
- args.recycle();
- break;
- }
- long offsetMicros = args.argl1;
- if (mLastInjectTimestampMicros == -1 || offsetMicros == -1) {
- // There's often a delay of a few milliseconds between the time specified to
- // Handler.sendMessageAtTime and the handler actually being called, due to
- // the way threads are scheduled. We don't take this into account when
- // calling addDelayNanos between the first batch of event injections (when
- // we set the "base timestamp" from which all others will be offset) and the
- // second batch, meaning that the actual time between the handler calls for
- // those batches may be less than the offset between their timestamps. When
- // that happens, we would pass a timestamp for the second batch that's
- // actually in the future. The kernel's uinput API rejects timestamps that
- // are in the future and uses the current time instead, making the reported
- // timestamps inconsistent with the recording we're replaying.
- //
- // To prevent this, we need to use the time we scheduled this first batch
- // for (in microseconds, to avoid potential rounding up from
- // getTimeToSendMillis), rather than the actual current time.
- mLastInjectTimestampMicros = args.argl2 / 1000;
- } else {
- mLastInjectTimestampMicros += offsetMicros;
- }
-
- int[] events = (int[]) args.arg1;
- for (int pos = 0; pos + 2 < events.length; pos += 3) {
- nativeInjectEvent(mPtr, mLastInjectTimestampMicros, events[pos],
- events[pos + 1], events[pos + 2]);
+ case MSG_INJECT_EVENT:
+ if (mPtr != 0) {
+ int[] events = (int[]) msg.obj;
+ for (int pos = 0; pos + 2 < events.length; pos += 3) {
+ nativeInjectEvent(mPtr, events[pos], events[pos + 1], events[pos + 2]);
+ }
}
- args.recycle();
break;
- }
- case MSG_CLOSE_UINPUT_DEVICE: {
+ case MSG_CLOSE_UINPUT_DEVICE:
if (mPtr != 0) {
nativeCloseUinputDevice(mPtr);
getLooper().quitSafely();
@@ -258,14 +198,11 @@ public class Device {
mCond.notify();
}
break;
- }
- case MSG_SYNC_EVENT: {
+ case MSG_SYNC_EVENT:
handleSyncEvent((String) msg.obj);
break;
- }
- default: {
+ default:
throw new IllegalArgumentException("Unknown device message");
- }
}
}
diff --git a/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
index da991624e685..7652f2403f6e 100644
--- a/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
+++ b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
@@ -44,7 +44,7 @@ public class EvemuParser implements EventParser {
* recordings, this will always be the same.
*/
private static final int DEVICE_ID = 1;
- private static final int REGISTRATION_DELAY_NANOS = 500_000_000;
+ private static final int REGISTRATION_DELAY_MILLIS = 500;
private static class CommentAwareReader {
private final LineNumberReader mReader;
@@ -152,7 +152,7 @@ public class EvemuParser implements EventParser {
final Event.Builder delayEb = new Event.Builder();
delayEb.setId(DEVICE_ID);
delayEb.setCommand(Event.Command.DELAY);
- delayEb.setDurationNanos(REGISTRATION_DELAY_NANOS);
+ delayEb.setDurationMillis(REGISTRATION_DELAY_MILLIS);
mQueuedEvents.add(delayEb.build());
}
@@ -175,6 +175,7 @@ public class EvemuParser implements EventParser {
throw new ParsingException(
"Invalid timestamp '" + parts[0] + "' (should contain a single '.')", mReader);
}
+ // TODO(b/310958309): use timeMicros to set the timestamp on the event being sent.
final long timeMicros =
parseLong(timeParts[0], 10) * 1_000_000 + parseInt(timeParts[1], 10);
final Event.Builder eb = new Event.Builder();
@@ -191,18 +192,21 @@ public class EvemuParser implements EventParser {
return eb.build();
} else {
final long delayMicros = timeMicros - mLastEventTimeMicros;
- eb.setTimestampOffsetMicros(delayMicros);
- if (delayMicros == 0) {
+ // The shortest delay supported by Handler.sendMessageAtTime (used for timings by the
+ // Device class) is 1ms, so ignore time differences smaller than that.
+ if (delayMicros < 1000) {
+ mLastEventTimeMicros = timeMicros;
return eb.build();
+ } else {
+ // Send a delay now, and queue the actual event for the next call.
+ mQueuedEvents.add(eb.build());
+ mLastEventTimeMicros = timeMicros;
+ final Event.Builder delayEb = new Event.Builder();
+ delayEb.setId(DEVICE_ID);
+ delayEb.setCommand(Event.Command.DELAY);
+ delayEb.setDurationMillis((int) (delayMicros / 1000));
+ return delayEb.build();
}
- // Send a delay now, and queue the actual event for the next call.
- mQueuedEvents.add(eb.build());
- mLastEventTimeMicros = timeMicros;
- final Event.Builder delayEb = new Event.Builder();
- delayEb.setId(DEVICE_ID);
- delayEb.setCommand(Event.Command.DELAY);
- delayEb.setDurationNanos(delayMicros * 1000);
- return delayEb.build();
}
}
diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java
index 9e7ee0937efe..0f16a27aac1d 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Event.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Event.java
@@ -99,9 +99,8 @@ public class Event {
private int mVersionId;
private int mBusId;
private int[] mInjections;
- private long mTimestampOffsetMicros = -1;
private SparseArray<int[]> mConfiguration;
- private long mDurationNanos;
+ private int mDurationMillis;
private int mFfEffectsMax = 0;
private String mInputPort;
private SparseArray<InputAbsInfo> mAbsInfo;
@@ -140,28 +139,19 @@ public class Event {
}
/**
- * Returns the number of microseconds that should be added to the previous {@code INJECT}
- * event's timestamp to produce the timestamp for this {@code INJECT} event. A value of -1
- * indicates that the current timestamp should be used instead.
- */
- public long getTimestampOffsetMicros() {
- return mTimestampOffsetMicros;
- }
-
- /**
* Returns a {@link SparseArray} describing the event codes that should be registered for the
* device. The keys are uinput ioctl codes (such as those returned from {@link
* UinputControlCode#getValue()}, while the values are arrays of event codes to be enabled with
* those ioctls. For example, key 101 (corresponding to {@link UinputControlCode#UI_SET_KEYBIT})
- * could have values 0x110 ({@code BTN_LEFT}), 0x111 ({@code BTN_RIGHT}), and 0x112
+ * could have values 0x110 ({@code BTN_LEFT}, 0x111 ({@code BTN_RIGHT}), and 0x112
* ({@code BTN_MIDDLE}).
*/
public SparseArray<int[]> getConfiguration() {
return mConfiguration;
}
- public long getDurationNanos() {
- return mDurationNanos;
+ public int getDurationMillis() {
+ return mDurationMillis;
}
public int getFfEffectsMax() {
@@ -192,7 +182,7 @@ public class Event {
+ ", busId=" + mBusId
+ ", events=" + Arrays.toString(mInjections)
+ ", configuration=" + mConfiguration
- + ", duration=" + mDurationNanos + "ns"
+ + ", duration=" + mDurationMillis + "ms"
+ ", ff_effects_max=" + mFfEffectsMax
+ ", port=" + mInputPort
+ "}";
@@ -221,10 +211,6 @@ public class Event {
mEvent.mInjections = events;
}
- public void setTimestampOffsetMicros(long offsetMicros) {
- mEvent.mTimestampOffsetMicros = offsetMicros;
- }
-
/**
* Sets the event codes that should be registered with a {@code register} command.
*
@@ -251,8 +237,8 @@ public class Event {
mEvent.mBusId = busId;
}
- public void setDurationNanos(long durationNanos) {
- mEvent.mDurationNanos = durationNanos;
+ public void setDurationMillis(int durationMillis) {
+ mEvent.mDurationMillis = durationMillis;
}
public void setFfEffectsMax(int ffEffectsMax) {
@@ -285,7 +271,7 @@ public class Event {
}
}
case DELAY -> {
- if (mEvent.mDurationNanos <= 0) {
+ if (mEvent.mDurationMillis <= 0) {
throw new IllegalStateException("Delay has missing or invalid duration");
}
}
diff --git a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
index 6994f0cb0e4b..ed3ff33f7e52 100644
--- a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
+++ b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
@@ -71,8 +71,7 @@ public class JsonStyleParser implements EventParser {
case "configuration" -> eb.setConfiguration(readConfiguration());
case "ff_effects_max" -> eb.setFfEffectsMax(readInt());
case "abs_info" -> eb.setAbsInfo(readAbsInfoArray());
- // Duration is specified in milliseconds in the JSON-style format.
- case "duration" -> eb.setDurationNanos(readInt() * 1_000_000L);
+ case "duration" -> eb.setDurationMillis(readInt());
case "port" -> eb.setInputPort(mReader.nextString());
case "syncToken" -> eb.setSyncToken(mReader.nextString());
default -> mReader.skipValue();
diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
index 760e981c8465..04df27987d58 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
@@ -134,8 +134,8 @@ public class Uinput {
switch (Objects.requireNonNull(e.getCommand())) {
case REGISTER ->
error("Device id=" + e.getId() + " is already registered. Ignoring event.");
- case INJECT -> d.injectEvent(e.getInjections(), e.getTimestampOffsetMicros());
- case DELAY -> d.addDelayNanos(e.getDurationNanos());
+ case INJECT -> d.injectEvent(e.getInjections());
+ case DELAY -> d.addDelay(e.getDurationMillis());
case SYNC -> d.syncEvent(e.getSyncToken());
}
}
diff --git a/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java b/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java
index 4dc4b68eba60..a05cc676ddfa 100644
--- a/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java
+++ b/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java
@@ -183,22 +183,16 @@ public class EvemuParserTest {
}
private void assertInjectEvent(Event event, int eventType, int eventCode, int value) {
- assertInjectEvent(event, eventType, eventCode, value, 0);
- }
-
- private void assertInjectEvent(Event event, int eventType, int eventCode, int value,
- long timestampOffsetMicros) {
assertThat(event).isNotNull();
assertThat(event.getCommand()).isEqualTo(Event.Command.INJECT);
assertThat(event.getInjections()).asList()
.containsExactly(eventType, eventCode, value).inOrder();
- assertThat(event.getTimestampOffsetMicros()).isEqualTo(timestampOffsetMicros);
}
- private void assertDelayEvent(Event event, int durationNanos) {
+ private void assertDelayEvent(Event event, int durationMillis) {
assertThat(event).isNotNull();
assertThat(event.getCommand()).isEqualTo(Event.Command.DELAY);
- assertThat(event.getDurationNanos()).isEqualTo(durationNanos);
+ assertThat(event.getDurationMillis()).isEqualTo(durationMillis);
}
@Test
@@ -213,7 +207,7 @@ public class EvemuParserTest {
EvemuParser parser = new EvemuParser(reader);
assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER);
assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
- assertInjectEvent(parser.getNextEvent(), 0x2, 0x0, 1, -1);
+ assertInjectEvent(parser.getNextEvent(), 0x2, 0x0, 1);
assertInjectEvent(parser.getNextEvent(), 0x2, 0x1, -2);
assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
}
@@ -234,17 +228,17 @@ public class EvemuParserTest {
assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER);
assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
- assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1, -1);
+ assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1);
assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
- assertDelayEvent(parser.getNextEvent(), 10_000_000);
+ assertDelayEvent(parser.getNextEvent(), 10);
- assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 0, 10_000);
+ assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 0);
assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
- assertDelayEvent(parser.getNextEvent(), 1_000_000_000);
+ assertDelayEvent(parser.getNextEvent(), 1000);
- assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1, 1_000_000);
+ assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1);
assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
}
@@ -455,7 +449,7 @@ public class EvemuParserTest {
assertThat(regEvent.getBus()).isEqualTo(0x001d);
assertThat(regEvent.getVendorId()).isEqualTo(0x6cb);
assertThat(regEvent.getProductId()).isEqualTo(0x0000);
- // TODO(b/302297266): check version ID once it's supported
+ assertThat(regEvent.getVersionId()).isEqualTo(0x0000);
assertThat(regEvent.getConfiguration().get(UinputControlCode.UI_SET_PROPBIT.getValue()))
.asList().containsExactly(0, 2);
@@ -483,7 +477,7 @@ public class EvemuParserTest {
assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
- assertInjectEvent(parser.getNextEvent(), 0x3, 0x39, 0, -1);
+ assertInjectEvent(parser.getNextEvent(), 0x3, 0x39, 0);
assertInjectEvent(parser.getNextEvent(), 0x3, 0x35, 891);
assertInjectEvent(parser.getNextEvent(), 0x3, 0x36, 333);
assertInjectEvent(parser.getNextEvent(), 0x3, 0x3a, 56);
@@ -496,8 +490,8 @@ public class EvemuParserTest {
assertInjectEvent(parser.getNextEvent(), 0x3, 0x18, 56);
assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
- assertDelayEvent(parser.getNextEvent(), 6_080_000);
+ assertDelayEvent(parser.getNextEvent(), 6);
- assertInjectEvent(parser.getNextEvent(), 0x3, 0x0035, 888, 6_080);
+ assertInjectEvent(parser.getNextEvent(), 0x3, 0x0035, 888);
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 0e42f807c34b..18153a5d4d34 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7840,6 +7840,7 @@ package android.app.admin {
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DeviceAdminInfo> CREATOR;
field public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1; // 0x1
+ field @FlaggedApi("android.app.admin.flags.headless_device_owner_single_user_enabled") public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2; // 0x2
field public static final int HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED = 0; // 0x0
field public static final int USES_ENCRYPTED_STORAGE = 7; // 0x7
field public static final int USES_POLICY_DISABLE_CAMERA = 8; // 0x8
@@ -25869,6 +25870,9 @@ package android.media.metrics {
method public int describeContents();
method public int getErrorCode();
method public int getFinalState();
+ method @NonNull public java.util.List<android.media.metrics.MediaItemInfo> getInputMediaItemInfos();
+ method public long getOperationTypes();
+ method @Nullable public android.media.metrics.MediaItemInfo getOutputMediaItemInfo();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.EditingEndedEvent> CREATOR;
field public static final int ERROR_CODE_AUDIO_PROCESSING_FAILED = 18; // 0x12
@@ -25893,14 +25897,25 @@ package android.media.metrics {
field public static final int FINAL_STATE_CANCELED = 2; // 0x2
field public static final int FINAL_STATE_ERROR = 3; // 0x3
field public static final int FINAL_STATE_SUCCEEDED = 1; // 0x1
+ field public static final long OPERATION_TYPE_AUDIO_EDIT = 8L; // 0x8L
+ field public static final long OPERATION_TYPE_AUDIO_TRANSCODE = 2L; // 0x2L
+ field public static final long OPERATION_TYPE_AUDIO_TRANSMUX = 32L; // 0x20L
+ field public static final long OPERATION_TYPE_PAUSED = 64L; // 0x40L
+ field public static final long OPERATION_TYPE_RESUMED = 128L; // 0x80L
+ field public static final long OPERATION_TYPE_VIDEO_EDIT = 4L; // 0x4L
+ field public static final long OPERATION_TYPE_VIDEO_TRANSCODE = 1L; // 0x1L
+ field public static final long OPERATION_TYPE_VIDEO_TRANSMUX = 16L; // 0x10L
field public static final int TIME_SINCE_CREATED_UNKNOWN = -1; // 0xffffffff
}
@FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public static final class EditingEndedEvent.Builder {
ctor public EditingEndedEvent.Builder(int);
+ method @NonNull public android.media.metrics.EditingEndedEvent.Builder addInputMediaItemInfo(@NonNull android.media.metrics.MediaItemInfo);
+ method @NonNull public android.media.metrics.EditingEndedEvent.Builder addOperationType(long);
method @NonNull public android.media.metrics.EditingEndedEvent build();
method @NonNull public android.media.metrics.EditingEndedEvent.Builder setErrorCode(int);
method @NonNull public android.media.metrics.EditingEndedEvent.Builder setMetricsBundle(@NonNull android.os.Bundle);
+ method @NonNull public android.media.metrics.EditingEndedEvent.Builder setOutputMediaItemInfo(@NonNull android.media.metrics.MediaItemInfo);
method @NonNull public android.media.metrics.EditingEndedEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=android.media.metrics.EditingEndedEvent.TIME_SINCE_CREATED_UNKNOWN) long);
}
@@ -25920,6 +25935,65 @@ package android.media.metrics {
field @NonNull public static final android.media.metrics.LogSessionId LOG_SESSION_ID_NONE;
}
+ @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public final class MediaItemInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAudioChannelCount();
+ method public long getAudioSampleCount();
+ method public int getAudioSampleRateHz();
+ method public long getClipDurationMillis();
+ method @NonNull public java.util.List<java.lang.String> getCodecNames();
+ method @Nullable public String getContainerMimeType();
+ method public long getDataTypes();
+ method public long getDurationMillis();
+ method @NonNull public java.util.List<java.lang.String> getSampleMimeTypes();
+ method public int getSourceType();
+ method public int getVideoDataSpace();
+ method public float getVideoFrameRate();
+ method public long getVideoSampleCount();
+ method @NonNull public android.util.Size getVideoSize();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.MediaItemInfo> CREATOR;
+ field public static final long DATA_TYPE_AUDIO = 4L; // 0x4L
+ field public static final long DATA_TYPE_CUE_POINTS = 128L; // 0x80L
+ field public static final long DATA_TYPE_DEPTH = 16L; // 0x10L
+ field public static final long DATA_TYPE_GAIN_MAP = 32L; // 0x20L
+ field public static final long DATA_TYPE_GAPLESS = 256L; // 0x100L
+ field public static final long DATA_TYPE_HIGH_DYNAMIC_RANGE_VIDEO = 1024L; // 0x400L
+ field public static final long DATA_TYPE_HIGH_FRAME_RATE = 64L; // 0x40L
+ field public static final long DATA_TYPE_IMAGE = 1L; // 0x1L
+ field public static final long DATA_TYPE_METADATA = 8L; // 0x8L
+ field public static final long DATA_TYPE_SPATIAL_AUDIO = 512L; // 0x200L
+ field public static final long DATA_TYPE_VIDEO = 2L; // 0x2L
+ field public static final int SOURCE_TYPE_CAMERA = 2; // 0x2
+ field public static final int SOURCE_TYPE_EDITING_SESSION = 3; // 0x3
+ field public static final int SOURCE_TYPE_GALLERY = 1; // 0x1
+ field public static final int SOURCE_TYPE_GENERATED = 7; // 0x7
+ field public static final int SOURCE_TYPE_LOCAL_FILE = 4; // 0x4
+ field public static final int SOURCE_TYPE_REMOTE_FILE = 5; // 0x5
+ field public static final int SOURCE_TYPE_REMOTE_LIVE_STREAM = 6; // 0x6
+ field public static final int SOURCE_TYPE_UNSPECIFIED = 0; // 0x0
+ field public static final int VALUE_UNSPECIFIED = -1; // 0xffffffff
+ }
+
+ @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public static final class MediaItemInfo.Builder {
+ ctor public MediaItemInfo.Builder();
+ method @NonNull public android.media.metrics.MediaItemInfo.Builder addCodecName(@NonNull String);
+ method @NonNull public android.media.metrics.MediaItemInfo.Builder addDataType(long);
+ method @NonNull public android.media.metrics.MediaItemInfo.Builder addSampleMimeType(@NonNull String);
+ method @NonNull public android.media.metrics.MediaItemInfo build();
+ method @NonNull public android.media.metrics.MediaItemInfo.Builder setAudioChannelCount(@IntRange(from=0) int);
+ method @NonNull public android.media.metrics.MediaItemInfo.Builder setAudioSampleCount(@IntRange(from=0) long);
+ method @NonNull public android.media.metrics.MediaItemInfo.Builder setAudioSampleRateHz(@IntRange(from=0) int);
+ method @NonNull public android.media.metrics.MediaItemInfo.Builder setClipDurationMillis(long);
+ method @NonNull public android.media.metrics.MediaItemInfo.Builder setContainerMimeType(@NonNull String);
+ method @NonNull public android.media.metrics.MediaItemInfo.Builder setDurationMillis(long);
+ method @NonNull public android.media.metrics.MediaItemInfo.Builder setSourceType(int);
+ method @NonNull public android.media.metrics.MediaItemInfo.Builder setVideoDataSpace(int);
+ method @NonNull public android.media.metrics.MediaItemInfo.Builder setVideoFrameRate(@FloatRange(from=0) float);
+ method @NonNull public android.media.metrics.MediaItemInfo.Builder setVideoSampleCount(@IntRange(from=0) long);
+ method @NonNull public android.media.metrics.MediaItemInfo.Builder setVideoSize(@NonNull android.util.Size);
+ }
+
public final class MediaMetricsManager {
method @NonNull public android.media.metrics.BundleSession createBundleSession();
method @NonNull public android.media.metrics.EditingSession createEditingSession();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ecbdeaa5fdb8..748845284cbc 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1389,6 +1389,7 @@ package android.app.admin {
field public static final int STATUS_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
field public static final int STATUS_HAS_DEVICE_OWNER = 1; // 0x1
field public static final int STATUS_HAS_PAIRED = 8; // 0x8
+ field @FlaggedApi("android.app.admin.flags.headless_device_owner_single_user_enabled") public static final int STATUS_HEADLESS_ONLY_SYSTEM_USER = 17; // 0x11
field public static final int STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED = 16; // 0x10
field public static final int STATUS_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
field public static final int STATUS_NONSYSTEM_USER_EXISTS = 5; // 0x5
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 14462b853c02..7d5d5c162271 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -16,8 +16,10 @@
package android.app.admin;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.app.admin.flags.Flags;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -176,7 +178,18 @@ public final class DeviceAdminInfo implements Parcelable {
*/
public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1;
- @IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED})
+ /**
+ * Value for {@link #getHeadlessDeviceOwnerMode} which indicates that this DPC should be
+ * provisioned into the first secondary user when on a Headless System User Mode device.
+ *
+ * <p>This mode only allows a single secondary user on the device blocking the creation of
+ * additional secondary users.
+ */
+ @FlaggedApi(Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
+ public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
+
+ @IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED,
+ HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER})
@Retention(RetentionPolicy.SOURCE)
private @interface HeadlessDeviceOwnerMode {}
@@ -373,6 +386,8 @@ public final class DeviceAdminInfo implements Parcelable {
mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
} else if (deviceOwnerModeStringValue.equalsIgnoreCase("affiliated")) {
mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
+ } else if (deviceOwnerModeStringValue.equalsIgnoreCase("single_user")) {
+ mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
} else {
throw new XmlPullParserException("headless-system-user mode must be valid");
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c8762c69eeca..c649e622dfc5 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -84,6 +84,7 @@ import android.app.Activity;
import android.app.IServiceConnection;
import android.app.KeyguardManager;
import android.app.admin.SecurityLog.SecurityEvent;
+import android.app.admin.flags.Flags;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -2863,6 +2864,19 @@ public class DevicePolicyManager {
public static final int STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED = 16;
/**
+ * Result code for {@link #checkProvisioningPrecondition}.
+ *
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when provisioning a DPC into the
+ * {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER} mode but only the system
+ * user exists on the device.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
+ public static final int STATUS_HEADLESS_ONLY_SYSTEM_USER = 17;
+
+ /**
* Result codes for {@link #checkProvisioningPrecondition} indicating all the provisioning pre
* conditions.
*
@@ -2876,7 +2890,7 @@ public class DevicePolicyManager {
STATUS_CANNOT_ADD_MANAGED_PROFILE, STATUS_DEVICE_ADMIN_NOT_SUPPORTED,
STATUS_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER,
STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS,
- STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED
+ STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED, STATUS_HEADLESS_ONLY_SYSTEM_USER
})
public @interface ProvisioningPrecondition {}
@@ -9178,9 +9192,11 @@ public class DevicePolicyManager {
* <p>Calling this after the setup phase of the device owner user has completed is allowed only
* if the caller is the {@link Process#SHELL_UID Shell UID}, and there are no additional users
* (except when the device runs on headless system user mode, in which case it could have exact
- * one extra user, which is the current user - the device owner will be set in the
- * {@link UserHandle#SYSTEM system} user and a profile owner will be set in the current user)
- * and no accounts.
+ * one extra user, which is the current user.
+ *
+ * <p>On a headless devices, if it is in affiliated mode the device owner will be set in the
+ * {@link UserHandle#SYSTEM system} user. If the device is in single user mode, the device owner
+ * will be set in the first secondary user.
*
* @param who the component name to be registered as device owner.
* @param userId ID of the user on which the device owner runs.
@@ -11371,7 +11387,9 @@ public class DevicePolicyManager {
* @see UserHandle
* @return the {@link android.os.UserHandle} object for the created user, or {@code null} if the
* user could not be created.
- * @throws SecurityException if {@code admin} is not a device owner.
+ * @throws SecurityException if headless device is in
+ * {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER} mode.
+ * @throws SecurityException if {@code admin} is not a device owner
* @throws UserOperationException if the user could not be created and the calling app is
* targeting {@link android.os.Build.VERSION_CODES#P} and running on
* {@link android.os.Build.VERSION_CODES#P}.
@@ -16612,7 +16630,10 @@ public class DevicePolicyManager {
* before calling this method.
*
* <p>Holders of {@link android.Manifest.permission#PROVISION_DEMO_DEVICE} can call this API
- * only if {@link FullyManagedDeviceProvisioningParams#isDemoDevice()} is {@code true}.</p>
+ * only if {@link FullyManagedDeviceProvisioningParams#isDemoDevice()} is {@code true}.
+ *
+ * <p>If headless device is in {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER}
+ * mode then it sets the device owner on the first secondary user.</p>
*
* @param provisioningParams Params required to provision a fully managed device,
* see {@link FullyManagedDeviceProvisioningParams}.
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 6cc8af8c4b51..3c98ef9cfb00 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -76,3 +76,10 @@ flag {
description: "Enable APIs to provision and manage eSIMs"
bug: "295301164"
}
+
+flag {
+ name: "headless_device_owner_single_user_enabled"
+ namespace: "enterprise"
+ description: "Add Headless DO support."
+ bug: "289515470"
+}
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index c14fe572aba1..dbe7196d8e6a 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -212,6 +212,11 @@
<integer name="config_emergency_call_wait_for_connection_timeout_millis">20000</integer>
<java-symbol type="integer" name="config_emergency_call_wait_for_connection_timeout_millis" />
+ <!-- Indicates the data limit in bytes that can be used for bootstrap sim until factory reset.
+ -1 means unlimited. -->
+ <integer name="config_esim_bootstrap_data_limit_bytes">-1</integer>
+ <java-symbol type="integer" name="config_esim_bootstrap_data_limit_bytes" />
+
<!-- Telephony config for the PLMNs of all satellite providers. This is used by satellite modem
to identify providers that should be ignored if the carrier config
carrier_supported_satellite_services_per_provider_bundle does not support them.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e5045aea189b..70b2f211d943 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -2959,13 +2959,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
public void goToFullscreenFromSplit() {
- boolean leftOrTop;
- if (mSideStage.isFocused()) {
- leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+ // If main stage is focused, toEnd = true if
+ // mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT. Otherwise toEnd = false
+ // If side stage is focused, toEnd = true if
+ // mSideStagePosition = SPLIT_POSITION_TOP_OR_LEFT. Otherwise toEnd = false
+ final boolean toEnd;
+ if (mMainStage.isFocused()) {
+ toEnd = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
} else {
- leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ toEnd = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
}
- mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
+ mSplitLayout.flingDividerToDismiss(toEnd, EXIT_REASON_FULLSCREEN_SHORTCUT);
}
/** Move the specified task to fullscreen, regardless of focus state. */
diff --git a/media/java/android/media/metrics/EditingEndedEvent.java b/media/java/android/media/metrics/EditingEndedEvent.java
index 5ed8d40af63e..f1c5c9d0c748 100644
--- a/media/java/android/media/metrics/EditingEndedEvent.java
+++ b/media/java/android/media/metrics/EditingEndedEvent.java
@@ -20,6 +20,7 @@ import static com.android.media.editing.flags.Flags.FLAG_ADD_MEDIA_METRICS_EDITI
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
+import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
@@ -27,6 +28,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/** Event for an editing operation having ended. */
@@ -156,14 +159,66 @@ public final class EditingEndedEvent extends Event implements Parcelable {
@SuppressWarnings("HidingField") // Hiding field from superclass as for playback events.
private final long mTimeSinceCreatedMillis;
+ private final ArrayList<MediaItemInfo> mInputMediaItemInfos;
+ @Nullable private final MediaItemInfo mOutputMediaItemInfo;
+
+ /** @hide */
+ @LongDef(
+ prefix = {"OPERATION_TYPE_"},
+ flag = true,
+ value = {
+ OPERATION_TYPE_VIDEO_TRANSCODE,
+ OPERATION_TYPE_AUDIO_TRANSCODE,
+ OPERATION_TYPE_VIDEO_EDIT,
+ OPERATION_TYPE_AUDIO_EDIT,
+ OPERATION_TYPE_VIDEO_TRANSMUX,
+ OPERATION_TYPE_AUDIO_TRANSMUX,
+ OPERATION_TYPE_PAUSED,
+ OPERATION_TYPE_RESUMED,
+ })
+ @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ public @interface OperationType {}
+
+ /** Input video was decoded and re-encoded. */
+ public static final long OPERATION_TYPE_VIDEO_TRANSCODE = 1;
+
+ /** Input audio was decoded and re-encoded. */
+ public static final long OPERATION_TYPE_AUDIO_TRANSCODE = 1L << 1;
+
+ /** Input video was edited. */
+ public static final long OPERATION_TYPE_VIDEO_EDIT = 1L << 2;
+
+ /** Input audio was edited. */
+ public static final long OPERATION_TYPE_AUDIO_EDIT = 1L << 3;
+
+ /** Input video samples were writted (muxed) directly to the output file without transcoding. */
+ public static final long OPERATION_TYPE_VIDEO_TRANSMUX = 1L << 4;
+
+ /** Input audio samples were written (muxed) directly to the output file without transcoding. */
+ public static final long OPERATION_TYPE_AUDIO_TRANSMUX = 1L << 5;
+
+ /** The editing operation was paused before it completed. */
+ public static final long OPERATION_TYPE_PAUSED = 1L << 6;
+
+ /** The editing operation resumed a previous (paused) operation. */
+ public static final long OPERATION_TYPE_RESUMED = 1L << 7;
+
+ private final @OperationType long mOperationTypes;
+
private EditingEndedEvent(
@FinalState int finalState,
@ErrorCode int errorCode,
long timeSinceCreatedMillis,
+ ArrayList<MediaItemInfo> inputMediaItemInfos,
+ @Nullable MediaItemInfo outputMediaItemInfo,
+ @OperationType long operationTypes,
@NonNull Bundle extras) {
mFinalState = finalState;
mErrorCode = errorCode;
mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+ mInputMediaItemInfos = inputMediaItemInfos;
+ mOutputMediaItemInfo = outputMediaItemInfo;
+ mOperationTypes = operationTypes;
mMetricsBundle = extras.deepCopy();
}
@@ -194,6 +249,23 @@ public final class EditingEndedEvent extends Event implements Parcelable {
return mTimeSinceCreatedMillis;
}
+ /** Gets information about the input media items, or an empty list if unspecified. */
+ @NonNull
+ public List<MediaItemInfo> getInputMediaItemInfos() {
+ return new ArrayList<>(mInputMediaItemInfos);
+ }
+
+ /** Gets information about the output media item, or {@code null} if unspecified. */
+ @Nullable
+ public MediaItemInfo getOutputMediaItemInfo() {
+ return mOutputMediaItemInfo;
+ }
+
+ /** Gets a set of flags describing the types of operations performed. */
+ public @OperationType long getOperationTypes() {
+ return mOperationTypes;
+ }
+
/**
* Gets metrics-related information that is not supported by dedicated methods.
*
@@ -208,7 +280,7 @@ public final class EditingEndedEvent extends Event implements Parcelable {
@Override
@NonNull
public String toString() {
- return "PlaybackErrorEvent { "
+ return "EditingEndedEvent { "
+ "finalState = "
+ mFinalState
+ ", "
@@ -217,6 +289,15 @@ public final class EditingEndedEvent extends Event implements Parcelable {
+ ", "
+ "timeSinceCreatedMillis = "
+ mTimeSinceCreatedMillis
+ + ", "
+ + "inputMediaItemInfos = "
+ + mInputMediaItemInfos
+ + ", "
+ + "outputMediaItemInfo = "
+ + mOutputMediaItemInfo
+ + ", "
+ + "operationTypes = "
+ + mOperationTypes
+ " }";
}
@@ -227,12 +308,21 @@ public final class EditingEndedEvent extends Event implements Parcelable {
EditingEndedEvent that = (EditingEndedEvent) o;
return mFinalState == that.mFinalState
&& mErrorCode == that.mErrorCode
+ && Objects.equals(mInputMediaItemInfos, that.mInputMediaItemInfos)
+ && Objects.equals(mOutputMediaItemInfo, that.mOutputMediaItemInfo)
+ && mOperationTypes == that.mOperationTypes
&& mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis;
}
@Override
public int hashCode() {
- return Objects.hash(mFinalState, mErrorCode, mTimeSinceCreatedMillis);
+ return Objects.hash(
+ mFinalState,
+ mErrorCode,
+ mInputMediaItemInfos,
+ mOutputMediaItemInfo,
+ mOperationTypes,
+ mTimeSinceCreatedMillis);
}
@Override
@@ -240,6 +330,9 @@ public final class EditingEndedEvent extends Event implements Parcelable {
dest.writeInt(mFinalState);
dest.writeInt(mErrorCode);
dest.writeLong(mTimeSinceCreatedMillis);
+ dest.writeTypedList(mInputMediaItemInfos);
+ dest.writeTypedObject(mOutputMediaItemInfo, /* parcelableFlags= */ 0);
+ dest.writeLong(mOperationTypes);
dest.writeBundle(mMetricsBundle);
}
@@ -249,15 +342,14 @@ public final class EditingEndedEvent extends Event implements Parcelable {
}
private EditingEndedEvent(@NonNull Parcel in) {
- int finalState = in.readInt();
- int errorCode = in.readInt();
- long timeSinceCreatedMillis = in.readLong();
- Bundle metricsBundle = in.readBundle();
-
- mFinalState = finalState;
- mErrorCode = errorCode;
- mTimeSinceCreatedMillis = timeSinceCreatedMillis;
- mMetricsBundle = metricsBundle;
+ mFinalState = in.readInt();
+ mErrorCode = in.readInt();
+ mTimeSinceCreatedMillis = in.readLong();
+ mInputMediaItemInfos = new ArrayList<>();
+ in.readTypedList(mInputMediaItemInfos, MediaItemInfo.CREATOR);
+ mOutputMediaItemInfo = in.readTypedObject(MediaItemInfo.CREATOR);
+ mOperationTypes = in.readLong();
+ mMetricsBundle = in.readBundle();
}
public static final @NonNull Creator<EditingEndedEvent> CREATOR =
@@ -277,8 +369,11 @@ public final class EditingEndedEvent extends Event implements Parcelable {
@FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
public static final class Builder {
private final @FinalState int mFinalState;
+ private final ArrayList<MediaItemInfo> mInputMediaItemInfos;
private @ErrorCode int mErrorCode;
private long mTimeSinceCreatedMillis;
+ @Nullable private MediaItemInfo mOutputMediaItemInfo;
+ private @OperationType long mOperationTypes;
private Bundle mMetricsBundle;
/**
@@ -290,6 +385,7 @@ public final class EditingEndedEvent extends Event implements Parcelable {
mFinalState = finalState;
mErrorCode = ERROR_CODE_NONE;
mTimeSinceCreatedMillis = TIME_SINCE_CREATED_UNKNOWN;
+ mInputMediaItemInfos = new ArrayList<>();
mMetricsBundle = new Bundle();
}
@@ -312,20 +408,49 @@ public final class EditingEndedEvent extends Event implements Parcelable {
return this;
}
+ /** Adds information about a media item that was input to the editing operation. */
+ public @NonNull Builder addInputMediaItemInfo(@NonNull MediaItemInfo mediaItemInfo) {
+ mInputMediaItemInfos.add(Objects.requireNonNull(mediaItemInfo));
+ return this;
+ }
+
+ /** Sets information about the output media item. */
+ public @NonNull Builder setOutputMediaItemInfo(@NonNull MediaItemInfo mediaItemInfo) {
+ mOutputMediaItemInfo = Objects.requireNonNull(mediaItemInfo);
+ return this;
+ }
+
+ /**
+ * Adds an operation type to the set of operations performed.
+ *
+ * @param operationType A type of operation performed as part of this editing operation.
+ */
+ public @NonNull Builder addOperationType(@OperationType long operationType) {
+ mOperationTypes |= operationType;
+ return this;
+ }
+
/**
* Sets metrics-related information that is not supported by dedicated methods.
*
* <p>Used for backwards compatibility by the metrics infrastructure.
*/
public @NonNull Builder setMetricsBundle(@NonNull Bundle metricsBundle) {
- mMetricsBundle = metricsBundle;
+ mMetricsBundle = Objects.requireNonNull(metricsBundle);
return this;
}
/** Builds an instance. */
public @NonNull EditingEndedEvent build() {
return new EditingEndedEvent(
- mFinalState, mErrorCode, mTimeSinceCreatedMillis, mMetricsBundle);
+ mFinalState,
+ mErrorCode,
+ mTimeSinceCreatedMillis,
+ mInputMediaItemInfos,
+ mOutputMediaItemInfo,
+ mOperationTypes,
+ mMetricsBundle);
}
}
+
}
diff --git a/media/java/android/media/metrics/MediaItemInfo.java b/media/java/android/media/metrics/MediaItemInfo.java
new file mode 100644
index 000000000000..63dd3ccd3b33
--- /dev/null
+++ b/media/java/android/media/metrics/MediaItemInfo.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.metrics;
+
+import static com.android.media.editing.flags.Flags.FLAG_ADD_MEDIA_METRICS_EDITING;
+
+import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.LongDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.hardware.DataSpace;
+import android.media.MediaCodec;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Size;
+
+import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** Represents information about a piece of media (for example, an audio or video file). */
+@FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+public final class MediaItemInfo implements Parcelable {
+
+ /** @hide */
+ @IntDef(
+ prefix = {"SOURCE_TYPE_"},
+ value = {
+ SOURCE_TYPE_UNSPECIFIED,
+ SOURCE_TYPE_GALLERY,
+ SOURCE_TYPE_CAMERA,
+ SOURCE_TYPE_EDITING_SESSION,
+ SOURCE_TYPE_LOCAL_FILE,
+ SOURCE_TYPE_REMOTE_FILE,
+ SOURCE_TYPE_REMOTE_LIVE_STREAM,
+ SOURCE_TYPE_GENERATED,
+ })
+ @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ public @interface SourceType {}
+
+ /** The media item's source is not known. */
+ public static final int SOURCE_TYPE_UNSPECIFIED = 0;
+
+ /** The media item came from the device gallery. */
+ public static final int SOURCE_TYPE_GALLERY = 1;
+
+ /** The media item came directly from camera capture. */
+ public static final int SOURCE_TYPE_CAMERA = 2;
+
+ /** The media item was output by a previous editing session. */
+ public static final int SOURCE_TYPE_EDITING_SESSION = 3;
+
+ /** The media item is stored on the local device's file system. */
+ public static final int SOURCE_TYPE_LOCAL_FILE = 4;
+
+ /** The media item is a remote file (for example, it's loaded from an HTTP server). */
+ public static final int SOURCE_TYPE_REMOTE_FILE = 5;
+
+ /** The media item is a remotely-served live stream. */
+ public static final int SOURCE_TYPE_REMOTE_LIVE_STREAM = 6;
+
+ /** The media item was generated by another system. */
+ public static final int SOURCE_TYPE_GENERATED = 7;
+
+ /** @hide */
+ @LongDef(
+ prefix = {"DATA_TYPE_"},
+ flag = true,
+ value = {
+ DATA_TYPE_IMAGE,
+ DATA_TYPE_VIDEO,
+ DATA_TYPE_AUDIO,
+ DATA_TYPE_METADATA,
+ DATA_TYPE_DEPTH,
+ DATA_TYPE_GAIN_MAP,
+ DATA_TYPE_HIGH_FRAME_RATE,
+ DATA_TYPE_CUE_POINTS,
+ DATA_TYPE_GAPLESS,
+ DATA_TYPE_SPATIAL_AUDIO,
+ DATA_TYPE_HIGH_DYNAMIC_RANGE_VIDEO,
+ })
+ @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ public @interface DataType {}
+
+ /** The media item includes image data. */
+ public static final long DATA_TYPE_IMAGE = 1L;
+
+ /** The media item includes video data. */
+ public static final long DATA_TYPE_VIDEO = 1L << 1;
+
+ /** The media item includes audio data. */
+ public static final long DATA_TYPE_AUDIO = 1L << 2;
+
+ /** The media item includes metadata. */
+ public static final long DATA_TYPE_METADATA = 1L << 3;
+
+ /** The media item includes depth (z-distance) information. */
+ public static final long DATA_TYPE_DEPTH = 1L << 4;
+
+ /** The media item includes gain map information (for example, an Ultra HDR gain map). */
+ public static final long DATA_TYPE_GAIN_MAP = 1L << 5;
+
+ /** The media item includes high frame rate video data. */
+ public static final long DATA_TYPE_HIGH_FRAME_RATE = 1L << 6;
+
+ /** The media item includes time-dependent speed setting metadata. */
+ public static final long DATA_TYPE_CUE_POINTS = 1L << 7;
+
+ /** The media item includes gapless audio metadata. */
+ public static final long DATA_TYPE_GAPLESS = 1L << 8;
+
+ /** The media item includes spatial audio data. */
+ public static final long DATA_TYPE_SPATIAL_AUDIO = 1L << 9;
+
+ /** The media item includes high dynamic range (HDR) video. */
+ public static final long DATA_TYPE_HIGH_DYNAMIC_RANGE_VIDEO = 1L << 10;
+
+ /** Special value for numerical fields where the value was not specified. */
+ public static final int VALUE_UNSPECIFIED = -1;
+
+ private final @SourceType int mSourceType;
+ private final @DataType long mDataTypes;
+ private final long mDurationMillis;
+ private final long mClipDurationMillis;
+ @Nullable private final String mContainerMimeType;
+ private final List<String> mSampleMimeTypes;
+ private final List<String> mCodecNames;
+ private final int mAudioSampleRateHz;
+ private final int mAudioChannelCount;
+ private final long mAudioSampleCount;
+ private final Size mVideoSize;
+ private final int mVideoDataSpace;
+ private final float mVideoFrameRate;
+ private final long mVideoSampleCount;
+
+ private MediaItemInfo(
+ @SourceType int sourceType,
+ @DataType long dataTypes,
+ long durationMillis,
+ long clipDurationMillis,
+ @Nullable String containerMimeType,
+ List<String> sampleMimeTypes,
+ List<String> codecNames,
+ int audioSampleRateHz,
+ int audioChannelCount,
+ long audioSampleCount,
+ Size videoSize,
+ int videoDataSpace,
+ float videoFrameRate,
+ long videoSampleCount) {
+ mSourceType = sourceType;
+ mDataTypes = dataTypes;
+ mDurationMillis = durationMillis;
+ mClipDurationMillis = clipDurationMillis;
+ mContainerMimeType = containerMimeType;
+ mSampleMimeTypes = sampleMimeTypes;
+ mCodecNames = codecNames;
+ mAudioSampleRateHz = audioSampleRateHz;
+ mAudioChannelCount = audioChannelCount;
+ mAudioSampleCount = audioSampleCount;
+ mVideoSize = videoSize;
+ mVideoDataSpace = videoDataSpace;
+ mVideoFrameRate = videoFrameRate;
+ mVideoSampleCount = videoSampleCount;
+ }
+
+ /**
+ * Returns where the media item came from, or {@link #SOURCE_TYPE_UNSPECIFIED} if not specified.
+ */
+ public @SourceType int getSourceType() {
+ return mSourceType;
+ }
+
+ /** Returns the data types that are present in the media item. */
+ public @DataType long getDataTypes() {
+ return mDataTypes;
+ }
+
+ /**
+ * Returns the duration of the media item, in milliseconds, or {@link #VALUE_UNSPECIFIED} if not
+ * specified.
+ */
+ public long getDurationMillis() {
+ return mDurationMillis;
+ }
+
+ /**
+ * Returns the duration of the clip taken from the media item, in milliseconds, or {@link
+ * #VALUE_UNSPECIFIED} if not specified.
+ */
+ public long getClipDurationMillis() {
+ return mClipDurationMillis;
+ }
+
+ /** Returns the MIME type of the media container, or {@code null} if unspecified. */
+ @Nullable
+ public String getContainerMimeType() {
+ return mContainerMimeType;
+ }
+
+ /**
+ * Returns the MIME types of samples stored in the media container, or an empty list if not
+ * known.
+ */
+ @NonNull
+ public List<String> getSampleMimeTypes() {
+ return new ArrayList<>(mSampleMimeTypes);
+ }
+
+ /**
+ * Returns the {@linkplain MediaCodec#getName() media codec names} for codecs that were used as
+ * part of encoding/decoding this media item, or an empty list if not known or not applicable.
+ */
+ @NonNull
+ public List<String> getCodecNames() {
+ return new ArrayList<>(mCodecNames);
+ }
+
+ /**
+ * Returns the sample rate of audio, in Hertz, or {@link #VALUE_UNSPECIFIED} if not specified.
+ */
+ public int getAudioSampleRateHz() {
+ return mAudioSampleRateHz;
+ }
+
+ /** Returns the number of audio channels, or {@link #VALUE_UNSPECIFIED} if not specified. */
+ public int getAudioChannelCount() {
+ return mAudioChannelCount;
+ }
+
+ /**
+ * Returns the number of audio frames in the item, after clipping (if applicable), or {@link
+ * #VALUE_UNSPECIFIED} if not specified.
+ */
+ public long getAudioSampleCount() {
+ return mAudioSampleCount;
+ }
+
+ /**
+ * Returns the video size, in pixels, or a {@link Size} with width and height set to {@link
+ * #VALUE_UNSPECIFIED} if not specified.
+ */
+ @NonNull
+ public Size getVideoSize() {
+ return mVideoSize;
+ }
+
+ /** Returns the {@linkplain DataSpace data space} for video, as a packed integer. */
+ @SuppressLint("MethodNameUnits") // Packed integer for an android.hardware.DataSpace.
+ public int getVideoDataSpace() {
+ return mVideoDataSpace;
+ }
+
+ /**
+ * Returns the average video frame rate, in frames per second, or {@link #VALUE_UNSPECIFIED} if
+ * not specified.
+ */
+ public float getVideoFrameRate() {
+ return mVideoFrameRate;
+ }
+
+ /**
+ * Returns the number of video frames, aftrer clipping (if applicable), or {@link
+ * #VALUE_UNSPECIFIED} if not specified.
+ */
+ public long getVideoSampleCount() {
+ return mVideoSampleCount;
+ }
+
+ /** Builder for {@link MediaItemInfo}. */
+ @FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+ public static final class Builder {
+
+ private @SourceType int mSourceType;
+ private @DataType long mDataTypes;
+ private long mDurationMillis;
+ private long mClipDurationMillis;
+ @Nullable private String mContainerMimeType;
+ private final ArrayList<String> mSampleMimeTypes;
+ private final ArrayList<String> mCodecNames;
+ private int mAudioSampleRateHz;
+ private int mAudioChannelCount;
+ private long mAudioSampleCount;
+ @Nullable private Size mVideoSize;
+ private int mVideoDataSpace;
+ private float mVideoFrameRate;
+ private long mVideoSampleCount;
+
+ /** Creates a new builder. */
+ public Builder() {
+ mSourceType = SOURCE_TYPE_UNSPECIFIED;
+ mDurationMillis = VALUE_UNSPECIFIED;
+ mClipDurationMillis = VALUE_UNSPECIFIED;
+ mSampleMimeTypes = new ArrayList<>();
+ mCodecNames = new ArrayList<>();
+ mAudioSampleRateHz = VALUE_UNSPECIFIED;
+ mAudioChannelCount = VALUE_UNSPECIFIED;
+ mAudioSampleCount = VALUE_UNSPECIFIED;
+ mVideoSize = new Size(VALUE_UNSPECIFIED, VALUE_UNSPECIFIED);
+ mVideoFrameRate = VALUE_UNSPECIFIED;
+ mVideoSampleCount = VALUE_UNSPECIFIED;
+ }
+
+ /** Sets where the media item came from. */
+ public @NonNull Builder setSourceType(@SourceType int sourceType) {
+ mSourceType = sourceType;
+ return this;
+ }
+
+ /** Adds an additional data type represented as part of the media item. */
+ public @NonNull Builder addDataType(@DataType long dataType) {
+ mDataTypes |= dataType;
+ return this;
+ }
+
+ /** Sets the duration of the media item, in milliseconds. */
+ public @NonNull Builder setDurationMillis(long durationMillis) {
+ mDurationMillis = durationMillis;
+ return this;
+ }
+
+ /** Sets the duration of the clip taken from the media item, in milliseconds. */
+ public @NonNull Builder setClipDurationMillis(long clipDurationMillis) {
+ mClipDurationMillis = clipDurationMillis;
+ return this;
+ }
+
+ /** Sets the MIME type of the media container. */
+ public @NonNull Builder setContainerMimeType(@NonNull String containerMimeType) {
+ mContainerMimeType = Objects.requireNonNull(containerMimeType);
+ return this;
+ }
+
+ /** Adds a sample MIME type stored in the media container. */
+ public @NonNull Builder addSampleMimeType(@NonNull String mimeType) {
+ mSampleMimeTypes.add(Objects.requireNonNull(mimeType));
+ return this;
+ }
+
+ /**
+ * Adds an {@linkplain MediaCodec#getName() media codec name} that was used as part of
+ * decoding/encoding this media item.
+ */
+ public @NonNull Builder addCodecName(@NonNull String codecName) {
+ mCodecNames.add(Objects.requireNonNull(codecName));
+ return this;
+ }
+
+ /** Sets the sample rate of audio, in Hertz. */
+ public @NonNull Builder setAudioSampleRateHz(@IntRange(from = 0) int audioSampleRateHz) {
+ mAudioSampleRateHz = audioSampleRateHz;
+ return this;
+ }
+
+ /** Sets the number of audio channels. */
+ public @NonNull Builder setAudioChannelCount(@IntRange(from = 0) int audioChannelCount) {
+ mAudioChannelCount = audioChannelCount;
+ return this;
+ }
+
+ /** Sets the number of audio frames in the item, after clipping (if applicable). */
+ public @NonNull Builder setAudioSampleCount(@IntRange(from = 0) long audioSampleCount) {
+ mAudioSampleCount = audioSampleCount;
+ return this;
+ }
+
+ /** Sets the video size, in pixels. */
+ public @NonNull Builder setVideoSize(@NonNull Size videoSize) {
+ mVideoSize = Objects.requireNonNull(videoSize);
+ return this;
+ }
+
+ /**
+ * Sets the {@link DataSpace} of video frames.
+ *
+ * @param videoDataSpace The data space, returned by {@link DataSpace#pack(int, int, int)}.
+ */
+ public @NonNull Builder setVideoDataSpace(int videoDataSpace) {
+ mVideoDataSpace = videoDataSpace;
+ return this;
+ }
+
+ /** Sets the average video frame rate, in frames per second. */
+ public @NonNull Builder setVideoFrameRate(@FloatRange(from = 0) float videoFrameRate) {
+ mVideoFrameRate = videoFrameRate;
+ return this;
+ }
+
+ /** Sets the number of video frames, after clipping (if applicable). */
+ public @NonNull Builder setVideoSampleCount(@IntRange(from = 0) long videoSampleCount) {
+ mVideoSampleCount = videoSampleCount;
+ return this;
+ }
+
+ /** Builds an instance. */
+ @NonNull
+ public MediaItemInfo build() {
+ return new MediaItemInfo(
+ mSourceType,
+ mDataTypes,
+ mDurationMillis,
+ mClipDurationMillis,
+ mContainerMimeType,
+ mSampleMimeTypes,
+ mCodecNames,
+ mAudioSampleRateHz,
+ mAudioChannelCount,
+ mAudioSampleCount,
+ mVideoSize,
+ mVideoDataSpace,
+ mVideoFrameRate,
+ mVideoSampleCount);
+ }
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return "MediaItemInfo { "
+ + "sourceType = "
+ + mSourceType
+ + ", "
+ + "dataTypes = "
+ + mDataTypes
+ + ", "
+ + "durationMillis = "
+ + mDurationMillis
+ + ", "
+ + "clipDurationMillis = "
+ + mClipDurationMillis
+ + ", "
+ + "containerMimeType = "
+ + mContainerMimeType
+ + ", "
+ + "sampleMimeTypes = "
+ + mSampleMimeTypes
+ + ", "
+ + "codecNames = "
+ + mCodecNames
+ + ", "
+ + "audioSampleRateHz = "
+ + mAudioSampleRateHz
+ + ", "
+ + "audioChannelCount = "
+ + mAudioChannelCount
+ + ", "
+ + "audioSampleCount = "
+ + mAudioSampleCount
+ + ", "
+ + "videoSize = "
+ + mVideoSize
+ + ", "
+ + "videoDataSpace = "
+ + mVideoDataSpace
+ + ", "
+ + "videoFrameRate = "
+ + mVideoFrameRate
+ + ", "
+ + "videoSampleCount = "
+ + mVideoSampleCount
+ + " }";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MediaItemInfo that = (MediaItemInfo) o;
+ return mSourceType == that.mSourceType
+ && mDataTypes == that.mDataTypes
+ && mDurationMillis == that.mDurationMillis
+ && mClipDurationMillis == that.mClipDurationMillis
+ && Objects.equals(mContainerMimeType, that.mContainerMimeType)
+ && mSampleMimeTypes.equals(that.mSampleMimeTypes)
+ && mCodecNames.equals(that.mCodecNames)
+ && mAudioSampleRateHz == that.mAudioSampleRateHz
+ && mAudioChannelCount == that.mAudioChannelCount
+ && mAudioSampleCount == that.mAudioSampleCount
+ && Objects.equals(mVideoSize, that.mVideoSize)
+ && Objects.equals(mVideoDataSpace, that.mVideoDataSpace)
+ && mVideoFrameRate == that.mVideoFrameRate
+ && mVideoSampleCount == that.mVideoSampleCount;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSourceType, mDataTypes);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSourceType);
+ dest.writeLong(mDataTypes);
+ dest.writeLong(mDurationMillis);
+ dest.writeLong(mClipDurationMillis);
+ dest.writeString(mContainerMimeType);
+ dest.writeStringList(mSampleMimeTypes);
+ dest.writeStringList(mCodecNames);
+ dest.writeInt(mAudioSampleRateHz);
+ dest.writeInt(mAudioChannelCount);
+ dest.writeLong(mAudioSampleCount);
+ dest.writeInt(mVideoSize.getWidth());
+ dest.writeInt(mVideoSize.getHeight());
+ dest.writeInt(mVideoDataSpace);
+ dest.writeFloat(mVideoFrameRate);
+ dest.writeLong(mVideoSampleCount);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private MediaItemInfo(@NonNull Parcel in) {
+ mSourceType = in.readInt();
+ mDataTypes = in.readLong();
+ mDurationMillis = in.readLong();
+ mClipDurationMillis = in.readLong();
+ mContainerMimeType = in.readString();
+ mSampleMimeTypes = new ArrayList<>();
+ in.readStringList(mSampleMimeTypes);
+ mCodecNames = new ArrayList<>();
+ in.readStringList(mCodecNames);
+ mAudioSampleRateHz = in.readInt();
+ mAudioChannelCount = in.readInt();
+ mAudioSampleCount = in.readLong();
+ int videoSizeWidth = in.readInt();
+ int videoSizeHeight = in.readInt();
+ mVideoSize = new Size(videoSizeWidth, videoSizeHeight);
+ mVideoDataSpace = in.readInt();
+ mVideoFrameRate = in.readFloat();
+ mVideoSampleCount = in.readLong();
+ }
+
+ public static final @NonNull Creator<MediaItemInfo> CREATOR =
+ new Creator<>() {
+ @Override
+ public MediaItemInfo[] newArray(int size) {
+ return new MediaItemInfo[size];
+ }
+
+ @Override
+ public MediaItemInfo createFromParcel(@NonNull Parcel in) {
+ return new MediaItemInfo(in);
+ }
+ };
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index e3012cd3e6fd..249fa7f0df77 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1621,6 +1621,7 @@ public class ApplicationsState {
}
public static class AppEntry extends SizeInfo {
+ @VisibleForTesting String mProfileType;
@Nullable public final File apkFile;
public final long id;
public String label;
@@ -1647,11 +1648,6 @@ public class ApplicationsState {
*/
public boolean isHomeApp;
- /**
- * Whether or not it's a cloned app .
- */
- public boolean isCloned;
-
public String getNormalizedLabel() {
if (normalizedLabel != null) {
return normalizedLabel;
@@ -1692,11 +1688,21 @@ public class ApplicationsState {
() -> this.ensureLabelDescriptionLocked(context));
}
UserManager um = UserManager.get(context);
- this.showInPersonalTab = shouldShowInPersonalTab(um, info.uid);
UserInfo userInfo = um.getUserInfo(UserHandle.getUserId(info.uid));
- if (userInfo != null) {
- this.isCloned = userInfo.isCloneProfile();
- }
+ mProfileType = userInfo.userType;
+ this.showInPersonalTab = shouldShowInPersonalTab(um, info.uid);
+ }
+
+ public boolean isClonedProfile() {
+ return UserManager.USER_TYPE_PROFILE_CLONE.equals(mProfileType);
+ }
+
+ public boolean isManagedProfile() {
+ return UserManager.USER_TYPE_PROFILE_MANAGED.equals(mProfileType);
+ }
+
+ public boolean isPrivateProfile() {
+ return UserManager.USER_TYPE_PROFILE_PRIVATE.equals(mProfileType);
}
/**
@@ -1890,16 +1896,24 @@ public class ApplicationsState {
};
public static final AppFilter FILTER_WORK = new AppFilter() {
- private int mCurrentUser;
@Override
- public void init() {
- mCurrentUser = ActivityManager.getCurrentUser();
+ public void init() {}
+
+ @Override
+ public boolean filterApp(AppEntry entry) {
+ return !entry.showInPersonalTab && entry.isManagedProfile();
}
+ };
+
+ public static final AppFilter FILTER_PRIVATE_PROFILE = new AppFilter() {
+
+ @Override
+ public void init() {}
@Override
public boolean filterApp(AppEntry entry) {
- return !entry.showInPersonalTab;
+ return !entry.showInPersonalTab && entry.isPrivateProfile();
}
};
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index c5598bfa9438..213a66e546ab 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -22,6 +22,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.pm.ApplicationInfo;
+import android.os.UserManager;
import org.junit.Before;
import org.junit.Test;
@@ -297,11 +298,26 @@ public class ApplicationsStateTest {
@Test
public void testPersonalAndWorkFiltersDisplaysCorrectApps() {
mEntry.showInPersonalTab = true;
+ mEntry.mProfileType = UserManager.USER_TYPE_FULL_SYSTEM;
assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isTrue();
assertThat(ApplicationsState.FILTER_WORK.filterApp(mEntry)).isFalse();
mEntry.showInPersonalTab = false;
+ mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_MANAGED;
assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
assertThat(ApplicationsState.FILTER_WORK.filterApp(mEntry)).isTrue();
}
+
+ @Test
+ public void testPrivateProfileFilterDisplaysCorrectApps() {
+ mEntry.showInPersonalTab = true;
+ mEntry.mProfileType = UserManager.USER_TYPE_FULL_SYSTEM;
+ assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isTrue();
+ assertThat(ApplicationsState.FILTER_PRIVATE_PROFILE.filterApp(mEntry)).isFalse();
+
+ mEntry.showInPersonalTab = false;
+ mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_PRIVATE;
+ assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
+ assertThat(ApplicationsState.FILTER_PRIVATE_PROFILE.filterApp(mEntry)).isTrue();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index 40a9b9c699bc..13d743f8a4e4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -141,6 +141,7 @@ constructor(
if (field != value) {
field = value
checkIfPollingNeeded()
+ _data = _data.copy(listening = value)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
index 90abec17771c..80c3551b7de0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static android.app.Flags.lifetimeExtensionRefactor;
+
import android.annotation.NonNull;
import android.app.Notification;
import android.app.RemoteInputHistoryItem;
@@ -29,6 +31,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Stream;
@@ -68,7 +71,7 @@ public class RemoteInputNotificationRebuilder {
@NonNull
public StatusBarNotification rebuildForCanceledSmartReplies(
NotificationEntry entry) {
- return rebuildWithRemoteInputInserted(entry, null /* remoteInputTest */,
+ return rebuildWithRemoteInputInserted(entry, null /* remoteInputText */,
false /* showSpinner */, null /* mimeType */, null /* uri */);
}
@@ -97,22 +100,50 @@ public class RemoteInputNotificationRebuilder {
StatusBarNotification rebuildWithRemoteInputInserted(NotificationEntry entry,
CharSequence remoteInputText, boolean showSpinner, String mimeType, Uri uri) {
StatusBarNotification sbn = entry.getSbn();
-
Notification.Builder b = Notification.Builder
.recoverBuilder(mContext, sbn.getNotification().clone());
- if (remoteInputText != null || uri != null) {
- RemoteInputHistoryItem newItem = uri != null
- ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText)
- : new RemoteInputHistoryItem(remoteInputText);
+
+ if (lifetimeExtensionRefactor()) {
+ if (entry.remoteInputs == null) {
+ entry.remoteInputs = new ArrayList<RemoteInputHistoryItem>();
+ }
+
+ // Append new remote input information to remoteInputs list
+ if (remoteInputText != null || uri != null) {
+ RemoteInputHistoryItem newItem = uri != null
+ ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText)
+ : new RemoteInputHistoryItem(remoteInputText);
+ // The list is latest-first, so new elements should be added as the first element.
+ entry.remoteInputs.add(0, newItem);
+ }
+
+ // Read the whole remoteInputs list from the entry, then append all of those to the sbn.
Parcelable[] oldHistoryItems = sbn.getNotification().extras
.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+
RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null
? Stream.concat(
- Stream.of(newItem),
- Arrays.stream(oldHistoryItems).map(p -> (RemoteInputHistoryItem) p))
+ entry.remoteInputs.stream(),
+ Arrays.stream(oldHistoryItems).map(p -> (RemoteInputHistoryItem) p))
.toArray(RemoteInputHistoryItem[]::new)
- : new RemoteInputHistoryItem[] { newItem };
+ : entry.remoteInputs.toArray(RemoteInputHistoryItem[]::new);
b.setRemoteInputHistory(newHistoryItems);
+
+ } else {
+ if (remoteInputText != null || uri != null) {
+ RemoteInputHistoryItem newItem = uri != null
+ ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText)
+ : new RemoteInputHistoryItem(remoteInputText);
+ Parcelable[] oldHistoryItems = sbn.getNotification().extras
+ .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null
+ ? Stream.concat(
+ Stream.of(newItem),
+ Arrays.stream(oldHistoryItems).map(p -> (RemoteInputHistoryItem) p))
+ .toArray(RemoteInputHistoryItem[]::new)
+ : new RemoteInputHistoryItem[]{newItem};
+ b.setRemoteInputHistory(newHistoryItems);
+ }
}
b.setShowRemoteInputSpinner(showSpinner);
b.setHideSmartReplies(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index cdacb10e1676..8678f0aad181 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -40,6 +40,7 @@ import android.app.NotificationChannel;
import android.app.NotificationManager.Policy;
import android.app.Person;
import android.app.RemoteInput;
+import android.app.RemoteInputHistoryItem;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.net.Uri;
@@ -127,6 +128,7 @@ public final class NotificationEntry extends ListEntry {
public int targetSdk;
private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
public CharSequence remoteInputText;
+ public List<RemoteInputHistoryItem> remoteInputs = null;
public String remoteInputMimeType;
public Uri remoteInputUri;
public ContentInfo remoteInputAttachment;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
index 918bf083f9fe..28fff1519032 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator
+import android.app.Flags.lifetimeExtensionRefactor
+import android.app.Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY
import android.os.Handler
import android.service.notification.NotificationListenerService.REASON_CANCEL
import android.service.notification.NotificationListenerService.REASON_CLICK
@@ -88,11 +90,21 @@ class RemoteInputCoordinator @Inject constructor(
override fun attach(pipeline: NotifPipeline) {
mNotificationRemoteInputManager.setRemoteInputListener(this)
- mRemoteInputLifetimeExtenders.forEach { pipeline.addNotificationLifetimeExtender(it) }
+ if (lifetimeExtensionRefactor()) {
+ pipeline.addNotificationLifetimeExtender(mRemoteInputActiveExtender)
+ } else {
+ mRemoteInputLifetimeExtenders.forEach {
+ pipeline.addNotificationLifetimeExtender(it)
+ }
+ }
mNotifUpdater = pipeline.getInternalNotifUpdater(TAG)
pipeline.addCollectionListener(mCollectionListener)
}
+ /*
+ * Listener that updates the appearance of the notification if it has been lifetime extended
+ * by a a direct reply or a smart reply, and cancelled.
+ */
val mCollectionListener = object : NotifCollectionListener {
override fun onEntryUpdated(entry: NotificationEntry, fromSystem: Boolean) {
if (DEBUG) {
@@ -100,9 +112,32 @@ class RemoteInputCoordinator @Inject constructor(
" fromSystem=$fromSystem)")
}
if (fromSystem) {
- // Mark smart replies as sent whenever a notification is updated by the app,
- // otherwise the smart replies are never marked as sent.
- mSmartReplyController.stopSending(entry)
+ if (lifetimeExtensionRefactor()) {
+ if ((entry.getSbn().getNotification().flags
+ and FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
+ if (mNotificationRemoteInputManager.shouldKeepForRemoteInputHistory(
+ entry)) {
+ val newSbn = mRebuilder.rebuildForRemoteInputReply(entry)
+ entry.onRemoteInputInserted()
+ mNotifUpdater.onInternalNotificationUpdate(newSbn,
+ "Extending lifetime of notification with remote input")
+ } else if (mNotificationRemoteInputManager.shouldKeepForSmartReplyHistory(
+ entry)) {
+ val newSbn = mRebuilder.rebuildForCanceledSmartReplies(entry)
+ mSmartReplyController.stopSending(entry)
+ mNotifUpdater.onInternalNotificationUpdate(newSbn,
+ "Extending lifetime of notification with smart reply")
+ }
+ } else {
+ // Notifications updated without FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY
+ // should have their remote inputs list cleared.
+ entry.remoteInputs = null
+ }
+ } else {
+ // Mark smart replies as sent whenever a notification is updated by the app,
+ // otherwise the smart replies are never marked as sent.
+ mSmartReplyController.stopSending(entry)
+ }
}
}
@@ -130,8 +165,10 @@ class RemoteInputCoordinator @Inject constructor(
// NOTE: This is some trickery! By removing the lifetime extensions when we know they should
// be immediately re-upped, we ensure that the side-effects of the lifetime extenders get to
// fire again, thus ensuring that we add subsequent replies to the notification.
- mRemoteInputHistoryExtender.endLifetimeExtension(entry.key)
- mSmartReplyHistoryExtender.endLifetimeExtension(entry.key)
+ if (!lifetimeExtensionRefactor()) {
+ mRemoteInputHistoryExtender.endLifetimeExtension(entry.key)
+ mSmartReplyHistoryExtender.endLifetimeExtension(entry.key)
+ }
// If we're extending for remote input being active, then from the apps point of
// view it is already canceled, so we'll need to cancel it on the apps behalf
@@ -160,15 +197,19 @@ class RemoteInputCoordinator @Inject constructor(
}
override fun isNotificationKeptForRemoteInputHistory(key: String) =
+ if (!lifetimeExtensionRefactor()) {
mRemoteInputHistoryExtender.isExtending(key) ||
mSmartReplyHistoryExtender.isExtending(key)
+ } else false
override fun releaseNotificationIfKeptForRemoteInputHistory(entry: NotificationEntry) {
if (DEBUG) Log.d(TAG, "releaseNotificationIfKeptForRemoteInputHistory(entry=${entry.key})")
- mRemoteInputHistoryExtender.endLifetimeExtensionAfterDelay(entry.key,
- REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
- mSmartReplyHistoryExtender.endLifetimeExtensionAfterDelay(entry.key,
- REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
+ if (!lifetimeExtensionRefactor()) {
+ mRemoteInputHistoryExtender.endLifetimeExtensionAfterDelay(entry.key,
+ REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
+ mSmartReplyHistoryExtender.endLifetimeExtensionAfterDelay(entry.key,
+ REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
+ }
mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(entry.key,
REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 45bdae83bbd9..8ac3b4a75141 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -132,9 +132,9 @@ public class PhoneStatusBarView extends FrameLayout {
if (updateDisplayParameters()) {
updateLayoutForCutout();
requestLayout();
- if (truncatedStatusBarIconsFix()) {
- updateWindowHeight();
- }
+ }
+ if (truncatedStatusBarIconsFix()) {
+ updateWindowHeight();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
index 100e579f1d93..4ec29ceb56d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
@@ -164,6 +164,18 @@ class SeekBarObserverTest : SysuiTestCase() {
}
@Test
+ fun seekbarNotListeningNotScrubbingPlaying() {
+ // WHEN playing
+ val isPlaying = true
+ val isScrubbing = false
+ val data =
+ SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, false)
+ observer.onChanged(data)
+ // THEN progress drawable is not animating
+ verify(mockSquigglyProgress).animate = false
+ }
+
+ @Test
fun seekBarPlayingScrubbing() {
// WHEN playing & scrubbing
val isPlaying = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
index 7073cc7c5707..85b8b03a1b46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
@@ -15,7 +15,13 @@
*/
package com.android.systemui.statusbar.notification.collection.coordinator
+import android.app.Flags.lifetimeExtensionRefactor
+import android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR
+import android.app.Notification
+import android.app.RemoteInputHistoryItem
import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -34,6 +40,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.captureMany
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -42,6 +49,7 @@ import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations.initMocks
@@ -57,6 +65,7 @@ class RemoteInputCoordinatorTest : SysuiTestCase() {
private lateinit var entry2: NotificationEntry
@Mock private lateinit var lifetimeExtensionCallback: OnEndLifetimeExtensionCallback
+
@Mock private lateinit var rebuilder: RemoteInputNotificationRebuilder
@Mock private lateinit var remoteInputManager: NotificationRemoteInputManager
@Mock private lateinit var mainHandler: Handler
@@ -84,9 +93,6 @@ class RemoteInputCoordinatorTest : SysuiTestCase() {
listener = withArgCaptor {
verify(remoteInputManager).setRemoteInputListener(capture())
}
- collectionListener = withArgCaptor {
- verify(pipeline).addCollectionListener(capture())
- }
entry1 = NotificationEntryBuilder().setId(1).build()
entry2 = NotificationEntryBuilder().setId(2).build()
`when`(rebuilder.rebuildForCanceledSmartReplies(any())).thenReturn(sbn)
@@ -98,16 +104,23 @@ class RemoteInputCoordinatorTest : SysuiTestCase() {
val remoteInputHistoryExtender get() = coordinator.mRemoteInputHistoryExtender
val smartReplyHistoryExtender get() = coordinator.mSmartReplyHistoryExtender
+ val collectionListeners get() = captureMany {
+ verify(pipeline, times(1)).addCollectionListener(capture())
+ }
+
@Test
fun testRemoteInputActive() {
`when`(remoteInputManager.isRemoteInputActive(entry1)).thenReturn(true)
assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isTrue()
- assertThat(remoteInputHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse()
- assertThat(smartReplyHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse()
+ if (!lifetimeExtensionRefactor()) {
+ assertThat(remoteInputHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse()
+ assertThat(smartReplyHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse()
+ }
assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isFalse()
}
@Test
+ @DisableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
fun testRemoteInputHistory() {
`when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry1)).thenReturn(true)
assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isFalse()
@@ -117,6 +130,7 @@ class RemoteInputCoordinatorTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
fun testSmartReplyHistory() {
`when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry1)).thenReturn(true)
assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isFalse()
@@ -142,4 +156,81 @@ class RemoteInputCoordinatorTest : SysuiTestCase() {
verify(lifetimeExtensionCallback).onEndLifetimeExtension(remoteInputActiveExtender, entry1)
assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isFalse()
}
+
+ @Test
+ @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
+ fun testOnlyRemoteInputActiveLifetimeExtenderExtends() {
+ `when`(remoteInputManager.isRemoteInputActive(entry1)).thenReturn(true)
+ assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isTrue()
+ assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isTrue()
+
+ listener.onPanelCollapsed()
+ assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isFalse()
+
+ // Checks that lifetimeExtensionCallback is only called the expected number of times,
+ // by the remoteInputActiveExtender.
+ // Checks that the remote input history extender and smart reply history extenders
+ // aren't attached to the pipeline.
+ verify(lifetimeExtensionCallback, times(1)).onEndLifetimeExtension(any(), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
+ fun testRemoteInputLifetimeExtensionListenerTrigger() {
+ // Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag.
+ val entry = NotificationEntryBuilder()
+ .setId(3)
+ .setTag("entry")
+ .setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true)
+ .build()
+ `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(true)
+ `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(false)
+
+ collectionListeners.forEach {
+ it.onEntryUpdated(entry, true)
+ }
+
+ verify(rebuilder, times(1)).rebuildForRemoteInputReply(entry)
+ }
+
+ @Test
+ @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
+ fun testSmartReplyLifetimeExtensionListenerTrigger() {
+ // Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag.
+ val entry = NotificationEntryBuilder()
+ .setId(3)
+ .setTag("entry")
+ .setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true)
+ .build()
+ `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(false)
+ `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(true)
+ collectionListeners.forEach {
+ it.onEntryUpdated(entry, true)
+ }
+
+
+ verify(rebuilder, times(1)).rebuildForCanceledSmartReplies(entry)
+ verify(smartReplyController, times(1)).stopSending(entry)
+ }
+
+ @Test
+ @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
+ fun testLifetimeExtensionListenerClearsRemoteInputs() {
+ // Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag.
+ val entry = NotificationEntryBuilder()
+ .setId(3)
+ .setTag("entry")
+ .setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, false)
+ .build()
+ entry.remoteInputs = ArrayList<RemoteInputHistoryItem>()
+ entry.remoteInputs.add(RemoteInputHistoryItem("Test Text"))
+ `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(false)
+ `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(false)
+
+ collectionListeners.forEach {
+ it.onEntryUpdated(entry, true)
+ }
+
+ assertThat(entry.remoteInputs).isNull()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index 6eb1c1a9f12c..4293a27095c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -42,6 +42,7 @@ import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@SmallTest
@@ -145,6 +146,18 @@ class PhoneStatusBarViewTest : SysuiTestCase() {
}
@Test
+ fun onConfigurationChanged_multipleCalls_flagEnabled_updatesWindowHeightMultipleTimes() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
+
+ view.onConfigurationChanged(Configuration())
+ view.onConfigurationChanged(Configuration())
+ view.onConfigurationChanged(Configuration())
+ view.onConfigurationChanged(Configuration())
+
+ verify(windowController, times(4)).refreshStatusBarHeight()
+ }
+
+ @Test
fun onConfigurationChanged_flagDisabled_doesNotUpdateWindowHeight() {
mSetFlagsRule.disableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
@@ -154,6 +167,18 @@ class PhoneStatusBarViewTest : SysuiTestCase() {
}
@Test
+ fun onConfigurationChanged_multipleCalls_flagDisabled_doesNotUpdateWindowHeight() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
+
+ view.onConfigurationChanged(Configuration())
+ view.onConfigurationChanged(Configuration())
+ view.onConfigurationChanged(Configuration())
+ view.onConfigurationChanged(Configuration())
+
+ verify(windowController, never()).refreshStatusBarHeight()
+ }
+
+ @Test
fun onAttachedToWindow_updatesLeftTopRightPaddingsBasedOnInsets() {
val insets = Insets.of(/* left = */ 10, /* top = */ 20, /* right = */ 30, /* bottom = */ 40)
whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index cec7a79cdada..5d415c2f636f 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -200,7 +200,9 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
Slog.d(TAG, this + ": Starting");
}
mRunning = true;
- updateBinding();
+ if (!Flags.enablePreventionOfKeepAliveRouteProviders()) {
+ updateBinding();
+ }
}
if (rebindIfDisconnected && mActiveConnection == null && shouldBind()) {
unbind();
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index 233a3ab4a4ea..fcca94b0611a 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -150,7 +150,9 @@ final class MediaRoute2ProviderWatcher {
mCallback.onAddProviderService(proxy);
} else if (sourceIndex >= targetIndex) {
MediaRoute2ProviderServiceProxy proxy = mProxies.get(sourceIndex);
- proxy.start(/* rebindIfDisconnected= */ true); // restart the proxy if needed
+ proxy.start(
+ /* rebindIfDisconnected= */
+ !Flags.enablePreventionOfKeepAliveRouteProviders());
Collections.swap(mProxies, sourceIndex, targetIndex++);
}
}
diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index bbe6d3a0c8fa..2cd8fe0ac43e 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -18,10 +18,13 @@ package com.android.server.media.metrics;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.hardware.DataSpace;
import android.media.MediaMetrics;
+import android.media.codec.Enums;
import android.media.metrics.BundleSession;
import android.media.metrics.EditingEndedEvent;
import android.media.metrics.IMediaMetricsManager;
+import android.media.metrics.MediaItemInfo;
import android.media.metrics.NetworkEvent;
import android.media.metrics.PlaybackErrorEvent;
import android.media.metrics.PlaybackMetrics;
@@ -31,7 +34,9 @@ import android.os.Binder;
import android.os.PersistableBundle;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
+import android.text.TextUtils;
import android.util.Base64;
+import android.util.Size;
import android.util.Slog;
import android.util.StatsEvent;
import android.util.StatsLog;
@@ -72,7 +77,14 @@ public final class MediaMetricsManagerService extends SystemService {
private static final String mMetricsId = MediaMetrics.Name.METRICS_MANAGER;
private static final String FAILED_TO_GET = "failed_to_get";
+
+ private static final MediaItemInfo EMPTY_MEDIA_ITEM_INFO = new MediaItemInfo.Builder().build();
+ private static final int DURATION_BUCKETS_BELOW_ONE_MINUTE = 8;
+ private static final int DURATION_BUCKETS_COUNT = 13;
+ private static final String AUDIO_MIME_TYPE_PREFIX = "audio/";
+ private static final String VIDEO_MIME_TYPE_PREFIX = "video/";
private final SecureRandom mSecureRandom;
+
@GuardedBy("mLock")
private Integer mMode = null;
@GuardedBy("mLock")
@@ -353,6 +365,51 @@ public final class MediaMetricsManagerService extends SystemService {
if (level == LOGGING_LEVEL_BLOCKED) {
return;
}
+ MediaItemInfo inputMediaItemInfo =
+ event.getInputMediaItemInfos().isEmpty()
+ ? EMPTY_MEDIA_ITEM_INFO
+ : event.getInputMediaItemInfos().get(0);
+ String inputAudioSampleMimeType =
+ getFilteredFirstMimeType(
+ inputMediaItemInfo.getSampleMimeTypes(), AUDIO_MIME_TYPE_PREFIX);
+ String inputVideoSampleMimeType =
+ getFilteredFirstMimeType(
+ inputMediaItemInfo.getSampleMimeTypes(), VIDEO_MIME_TYPE_PREFIX);
+ Size inputVideoSize = inputMediaItemInfo.getVideoSize();
+ int inputVideoResolution = getVideoResolutionEnum(inputVideoSize);
+ if (inputVideoResolution == Enums.RESOLUTION_UNKNOWN) {
+ // Try swapping width/height in case it's a portrait video.
+ inputVideoResolution =
+ getVideoResolutionEnum(
+ new Size(inputVideoSize.getHeight(), inputVideoSize.getWidth()));
+ }
+ List<String> inputCodecNames = inputMediaItemInfo.getCodecNames();
+ String inputFirstCodecName = !inputCodecNames.isEmpty() ? inputCodecNames.get(0) : "";
+ String inputSecondCodecName = inputCodecNames.size() > 1 ? inputCodecNames.get(1) : "";
+
+ MediaItemInfo outputMediaItemInfo =
+ event.getOutputMediaItemInfo() == null
+ ? EMPTY_MEDIA_ITEM_INFO
+ : event.getOutputMediaItemInfo();
+ String outputAudioSampleMimeType =
+ getFilteredFirstMimeType(
+ outputMediaItemInfo.getSampleMimeTypes(), AUDIO_MIME_TYPE_PREFIX);
+ String outputVideoSampleMimeType =
+ getFilteredFirstMimeType(
+ outputMediaItemInfo.getSampleMimeTypes(), VIDEO_MIME_TYPE_PREFIX);
+ Size outputVideoSize = outputMediaItemInfo.getVideoSize();
+ int outputVideoResolution = getVideoResolutionEnum(outputVideoSize);
+ if (outputVideoResolution == Enums.RESOLUTION_UNKNOWN) {
+ // Try swapping width/height in case it's a portrait video.
+ outputVideoResolution =
+ getVideoResolutionEnum(
+ new Size(outputVideoSize.getHeight(), outputVideoSize.getWidth()));
+ }
+ List<String> outputCodecNames = outputMediaItemInfo.getCodecNames();
+ String outputFirstCodecName =
+ !outputCodecNames.isEmpty() ? outputCodecNames.get(0) : "";
+ String outputSecondCodecName =
+ outputCodecNames.size() > 1 ? outputCodecNames.get(1) : "";
StatsEvent statsEvent =
StatsEvent.newBuilder()
.setAtomId(798)
@@ -360,6 +417,66 @@ public final class MediaMetricsManagerService extends SystemService {
.writeInt(event.getFinalState())
.writeInt(event.getErrorCode())
.writeLong(event.getTimeSinceCreatedMillis())
+ .writeInt(getThroughputFps(event))
+ .writeInt(event.getInputMediaItemInfos().size())
+ .writeInt(inputMediaItemInfo.getSourceType())
+ .writeLong(
+ getBucketedDurationMillis(
+ inputMediaItemInfo.getDurationMillis()))
+ .writeLong(
+ getBucketedDurationMillis(
+ inputMediaItemInfo.getClipDurationMillis()))
+ .writeString(
+ getFilteredMimeType(inputMediaItemInfo.getContainerMimeType()))
+ .writeString(inputAudioSampleMimeType)
+ .writeString(inputVideoSampleMimeType)
+ .writeInt(getCodecEnum(inputVideoSampleMimeType))
+ .writeInt(
+ getFilteredAudioSampleRateHz(
+ inputMediaItemInfo.getAudioSampleRateHz()))
+ .writeInt(inputMediaItemInfo.getAudioChannelCount())
+ .writeInt(inputVideoSize.getWidth())
+ .writeInt(inputVideoSize.getHeight())
+ .writeInt(inputVideoResolution)
+ .writeInt(getVideoResolutionAspectRatioEnum(inputVideoSize))
+ .writeInt(inputMediaItemInfo.getVideoDataSpace())
+ .writeInt(
+ getVideoHdrFormatEnum(
+ inputMediaItemInfo.getVideoDataSpace(),
+ inputVideoSampleMimeType))
+ .writeInt(Math.round(inputMediaItemInfo.getVideoFrameRate()))
+ .writeInt(getVideoFrameRateEnum(inputMediaItemInfo.getVideoFrameRate()))
+ .writeString(inputFirstCodecName)
+ .writeString(inputSecondCodecName)
+ .writeLong(
+ getBucketedDurationMillis(
+ outputMediaItemInfo.getDurationMillis()))
+ .writeLong(
+ getBucketedDurationMillis(
+ outputMediaItemInfo.getClipDurationMillis()))
+ .writeString(
+ getFilteredMimeType(outputMediaItemInfo.getContainerMimeType()))
+ .writeString(outputAudioSampleMimeType)
+ .writeString(outputVideoSampleMimeType)
+ .writeInt(getCodecEnum(outputVideoSampleMimeType))
+ .writeInt(
+ getFilteredAudioSampleRateHz(
+ outputMediaItemInfo.getAudioSampleRateHz()))
+ .writeInt(outputMediaItemInfo.getAudioChannelCount())
+ .writeInt(outputVideoSize.getWidth())
+ .writeInt(outputVideoSize.getHeight())
+ .writeInt(outputVideoResolution)
+ .writeInt(getVideoResolutionAspectRatioEnum(outputVideoSize))
+ .writeInt(outputMediaItemInfo.getVideoDataSpace())
+ .writeInt(
+ getVideoHdrFormatEnum(
+ outputMediaItemInfo.getVideoDataSpace(),
+ outputVideoSampleMimeType))
+ .writeInt(Math.round(outputMediaItemInfo.getVideoFrameRate()))
+ .writeInt(
+ getVideoFrameRateEnum(outputMediaItemInfo.getVideoFrameRate()))
+ .writeString(outputFirstCodecName)
+ .writeString(outputSecondCodecName)
.usePooledBuffer()
.build();
StatsLog.write(statsEvent);
@@ -511,4 +628,215 @@ public final class MediaMetricsManagerService extends SystemService {
}
}
}
+
+ private static int getThroughputFps(EditingEndedEvent event) {
+ MediaItemInfo outputMediaItemInfo = event.getOutputMediaItemInfo();
+ if (outputMediaItemInfo == null) {
+ return -1;
+ }
+ long videoSampleCount = outputMediaItemInfo.getVideoSampleCount();
+ if (videoSampleCount == MediaItemInfo.VALUE_UNSPECIFIED) {
+ return -1;
+ }
+ long elapsedTimeMs = event.getTimeSinceCreatedMillis();
+ if (elapsedTimeMs == EditingEndedEvent.TIME_SINCE_CREATED_UNKNOWN) {
+ return -1;
+ }
+ return (int)
+ Math.min(Integer.MAX_VALUE, Math.round(1000.0 * videoSampleCount / elapsedTimeMs));
+ }
+
+ private static long getBucketedDurationMillis(long durationMillis) {
+ if (durationMillis == MediaItemInfo.VALUE_UNSPECIFIED || durationMillis <= 0) {
+ return -1;
+ }
+ // Bucket values in an exponential distribution to reduce the precision that's stored:
+ // bucket index -> range -> bucketed duration
+ // 1 -> [0, 469 ms) -> 235 ms
+ // 2 -> [469 ms, 938 ms) -> 469 ms
+ // 3 -> [938 ms, 1875 ms) -> 938 ms
+ // 4 -> [1875 ms, 3750 ms) -> 1875 ms
+ // 5 -> [3750 ms, 7500 ms) -> 3750 ms
+ // [...]
+ // 13 -> [960000 ms, max) -> 960000 ms
+ int bucketIndex =
+ (int)
+ Math.floor(
+ DURATION_BUCKETS_BELOW_ONE_MINUTE
+ + Math.log((durationMillis + 1) / 60_000.0) / Math.log(2));
+ // Clamp to range [0, DURATION_BUCKETS_COUNT].
+ bucketIndex = Math.min(DURATION_BUCKETS_COUNT, Math.max(0, bucketIndex));
+ // Map back onto the representative value for the bucket.
+ return (long)
+ Math.ceil(Math.pow(2, bucketIndex - DURATION_BUCKETS_BELOW_ONE_MINUTE) * 60_000.0);
+ }
+
+ /**
+ * Returns the first entry in {@code mimeTypes} with the given prefix, if it matches the
+ * filtering allowlist. If no entries match the prefix or if the first matching entry is not on
+ * the allowlist, returns an empty string.
+ */
+ private static String getFilteredFirstMimeType(List<String> mimeTypes, String prefix) {
+ int size = mimeTypes.size();
+ for (int i = 0; i < size; i++) {
+ String mimeType = mimeTypes.get(i);
+ if (mimeType.startsWith(prefix)) {
+ return getFilteredMimeType(mimeType);
+ }
+ }
+ return "";
+ }
+
+ private static String getFilteredMimeType(String mimeType) {
+ if (TextUtils.isEmpty(mimeType)) {
+ return "";
+ }
+ // Discard all inputs that aren't allowlisted MIME types.
+ return switch (mimeType) {
+ case "video/mp4",
+ "video/x-matroska",
+ "video/webm",
+ "video/3gpp",
+ "video/avc",
+ "video/hevc",
+ "video/x-vnd.on2.vp8",
+ "video/x-vnd.on2.vp9",
+ "video/av01",
+ "video/mp2t",
+ "video/mp4v-es",
+ "video/mpeg",
+ "video/x-flv",
+ "video/dolby-vision",
+ "video/raw",
+ "audio/mp4",
+ "audio/mp4a-latm",
+ "audio/x-matroska",
+ "audio/webm",
+ "audio/mpeg",
+ "audio/mpeg-L1",
+ "audio/mpeg-L2",
+ "audio/ac3",
+ "audio/eac3",
+ "audio/eac3-joc",
+ "audio/av4",
+ "audio/true-hd",
+ "audio/vnd.dts",
+ "audio/vnd.dts.hd",
+ "audio/vorbis",
+ "audio/opus",
+ "audio/flac",
+ "audio/ogg",
+ "audio/wav",
+ "audio/midi",
+ "audio/raw",
+ "application/mp4",
+ "application/webm",
+ "application/x-matroska",
+ "application/dash+xml",
+ "application/x-mpegURL",
+ "application/vnd.ms-sstr+xml" ->
+ mimeType;
+ default -> "";
+ };
+ }
+
+ private static int getCodecEnum(String mimeType) {
+ if (TextUtils.isEmpty(mimeType)) {
+ return Enums.CODEC_UNKNOWN;
+ }
+ return switch (mimeType) {
+ case "video/avc" -> Enums.CODEC_AVC;
+ case "video/hevc" -> Enums.CODEC_HEVC;
+ case "video/x-vnd.on2.vp8" -> Enums.CODEC_VP8;
+ case "video/x-vnd.on2.vp9" -> Enums.CODEC_VP9;
+ case "video/av01" -> Enums.CODEC_AV1;
+ default -> Enums.CODEC_UNKNOWN;
+ };
+ }
+
+ private static int getFilteredAudioSampleRateHz(int sampleRateHz) {
+ return switch (sampleRateHz) {
+ case 8000, 11025, 16000, 22050, 44100, 48000, 96000, 192000 -> sampleRateHz;
+ default -> -1;
+ };
+ }
+
+ private static int getVideoResolutionEnum(Size size) {
+ int width = size.getWidth();
+ int height = size.getHeight();
+ if (width == 352 && height == 640) {
+ return Enums.RESOLUTION_352X640;
+ } else if (width == 360 && height == 640) {
+ return Enums.RESOLUTION_360X640;
+ } else if (width == 480 && height == 640) {
+ return Enums.RESOLUTION_480X640;
+ } else if (width == 480 && height == 854) {
+ return Enums.RESOLUTION_480X854;
+ } else if (width == 540 && height == 960) {
+ return Enums.RESOLUTION_540X960;
+ } else if (width == 576 && height == 1024) {
+ return Enums.RESOLUTION_576X1024;
+ } else if (width == 1280 && height == 720) {
+ return Enums.RESOLUTION_720P_HD;
+ } else if (width == 1920 && height == 1080) {
+ return Enums.RESOLUTION_1080P_FHD;
+ } else if (width == 1440 && height == 2560) {
+ return Enums.RESOLUTION_1440X2560;
+ } else if (width == 3840 && height == 2160) {
+ return Enums.RESOLUTION_4K_UHD;
+ } else if (width == 7680 && height == 4320) {
+ return Enums.RESOLUTION_8K_UHD;
+ } else {
+ return Enums.RESOLUTION_UNKNOWN;
+ }
+ }
+
+ private static int getVideoResolutionAspectRatioEnum(Size size) {
+ int width = size.getWidth();
+ int height = size.getHeight();
+ if (width <= 0 || height <= 0) {
+ return android.media.editing.Enums.RESOLUTION_ASPECT_RATIO_UNSPECIFIED;
+ } else if (width < height) {
+ return android.media.editing.Enums.RESOLUTION_ASPECT_RATIO_PORTRAIT;
+ } else if (height < width) {
+ return android.media.editing.Enums.RESOLUTION_ASPECT_RATIO_LANDSCAPE;
+ } else {
+ return android.media.editing.Enums.RESOLUTION_ASPECT_RATIO_SQUARE;
+ }
+ }
+
+ private static int getVideoHdrFormatEnum(int dataSpace, String mimeType) {
+ if (dataSpace == DataSpace.DATASPACE_UNKNOWN) {
+ return Enums.HDR_FORMAT_UNKNOWN;
+ }
+ if (mimeType.equals("video/dolby-vision")) {
+ return Enums.HDR_FORMAT_DOLBY_VISION;
+ }
+ int standard = DataSpace.getStandard(dataSpace);
+ int transfer = DataSpace.getTransfer(dataSpace);
+ if (standard == DataSpace.STANDARD_BT2020 && transfer == DataSpace.TRANSFER_HLG) {
+ return Enums.HDR_FORMAT_HLG;
+ }
+ if (standard == DataSpace.STANDARD_BT2020 && transfer == DataSpace.TRANSFER_ST2084) {
+ // We don't currently distinguish HDR10+ from HDR10.
+ return Enums.HDR_FORMAT_HDR10;
+ }
+ return Enums.HDR_FORMAT_NONE;
+ }
+
+ private static int getVideoFrameRateEnum(float frameRate) {
+ int frameRateInt = Math.round(frameRate);
+ return switch (frameRateInt) {
+ case 24 -> Enums.FRAMERATE_24;
+ case 25 -> Enums.FRAMERATE_25;
+ case 30 -> Enums.FRAMERATE_30;
+ case 50 -> Enums.FRAMERATE_50;
+ case 60 -> Enums.FRAMERATE_60;
+ case 120 -> Enums.FRAMERATE_120;
+ case 240 -> Enums.FRAMERATE_240;
+ case 480 -> Enums.FRAMERATE_480;
+ case 960 -> Enums.FRAMERATE_960;
+ default -> Enums.FRAMERATE_UNKNOWN;
+ };
+ }
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index d0c054307d0c..f645eaa28632 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
+import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT;
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_FOREGROUND_SERVICE;
@@ -24,6 +25,7 @@ import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -1802,6 +1804,8 @@ abstract public class ManagedServices {
public ComponentName component;
public int userid;
public boolean isSystem;
+ @FlaggedApi(FLAG_LIFETIME_EXTENSION_REFACTOR)
+ public boolean isSystemUi;
public ServiceConnection connection;
public int targetSdkVersion;
public Pair<ComponentName, Integer> mKey;
@@ -1836,6 +1840,11 @@ abstract public class ManagedServices {
return isSystem;
}
+ @FlaggedApi(FLAG_LIFETIME_EXTENSION_REFACTOR)
+ public boolean isSystemUi() {
+ return isSystemUi;
+ }
+
@Override
public String toString() {
return new StringBuilder("ManagedServiceInfo[")
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e7ad99a8cf20..3507d2d56cbd 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -71,6 +71,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
import static android.app.Flags.lifetimeExtensionRefactor;
import static android.app.NotificationManager.zenModeFromInterruptionFilter;
import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
@@ -159,6 +160,7 @@ import android.Manifest;
import android.Manifest.permission;
import android.annotation.DurationMillisLong;
import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1852,6 +1854,7 @@ public class NotificationManagerService extends SystemService {
}
if (ACTION_NOTIFICATION_TIMEOUT.equals(action)) {
final NotificationRecord record;
+ // TODO: b/323013410 - Record should be cloned instead of used directly.
synchronized (mNotificationLock) {
record = findNotificationByKeyLocked(intent.getStringExtra(EXTRA_KEY));
}
@@ -1864,6 +1867,14 @@ public class NotificationManagerService extends SystemService {
FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB
| FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY,
true, record.getUserId(), REASON_TIMEOUT, null);
+ // If cancellation will be prevented due to lifetime extension, we send an
+ // update to system UI.
+ synchronized (mNotificationLock) {
+ maybeNotifySystemUiListenerLifetimeExtendedLocked(record,
+ record.getSbn().getPackageName(),
+ mActivityManager.getPackageImportance(
+ record.getSbn().getPackageName()));
+ }
} else {
cancelNotification(record.getSbn().getUid(),
record.getSbn().getInitialPid(),
@@ -3825,7 +3836,17 @@ public class NotificationManagerService extends SystemService {
int mustNotHaveFlags = isCallingUidSystem() ? 0 :
(FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_AUTOGROUP_SUMMARY);
if (lifetimeExtensionRefactor()) {
+ // Also don't allow client apps to cancel lifetime extended notifs.
mustNotHaveFlags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+ // If cancellation will be prevented due to lifetime extension, we send an update to
+ // system UI.
+ NotificationRecord record = null;
+ final int packageImportance = mActivityManager.getPackageImportance(pkg);
+ synchronized (mNotificationLock) {
+ record = findNotificationLocked(pkg, tag, id, userId);
+ maybeNotifySystemUiListenerLifetimeExtendedLocked(record, pkg,
+ packageImportance);
+ }
}
cancelNotificationInternal(pkg, opPkg, Binder.getCallingUid(), Binder.getCallingPid(),
@@ -3845,6 +3866,16 @@ public class NotificationManagerService extends SystemService {
pkg, null, 0, FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB
| FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY,
userId, REASON_APP_CANCEL_ALL);
+ // If cancellation will be prevented due to lifetime extension, we send updates
+ // to system UI.
+ // In this case, we need to hold the lock to access these lists.
+ final int packageImportance = mActivityManager.getPackageImportance(pkg);
+ synchronized (mNotificationLock) {
+ notifySystemUiListenerLifetimeExtendedListLocked(mNotificationList,
+ packageImportance);
+ notifySystemUiListenerLifetimeExtendedListLocked(mEnqueuedNotifications,
+ packageImportance);
+ }
} else {
cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
pkg, null, 0, FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB,
@@ -4891,11 +4922,19 @@ public class NotificationManagerService extends SystemService {
final long identity = Binder.clearCallingIdentity();
boolean notificationsRapidlyCleared = false;
final String pkg;
+ final int packageImportance;
+ final ManagedServiceInfo info;
try {
synchronized (mNotificationLock) {
- final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+ info = mListeners.checkServiceTokenLocked(token);
pkg = info.component.getPackageName();
-
+ }
+ if (lifetimeExtensionRefactor()) {
+ packageImportance = mActivityManager.getPackageImportance(pkg);
+ } else {
+ packageImportance = IMPORTANCE_NONE;
+ }
+ synchronized (mNotificationLock) {
// Cancellation reason. If the token comes from assistant, label the
// cancellation as coming from the assistant; default to LISTENER_CANCEL.
int reason = REASON_LISTENER_CANCEL;
@@ -4917,7 +4956,7 @@ public class NotificationManagerService extends SystemService {
|| isNotificationRecent(r.getUpdateTimeMs());
cancelNotificationFromListenerLocked(info, callingUid, callingPid,
r.getSbn().getPackageName(), r.getSbn().getTag(),
- r.getSbn().getId(), userId, reason);
+ r.getSbn().getId(), userId, reason, packageImportance);
}
} else {
for (NotificationRecord notificationRecord : mNotificationList) {
@@ -4931,6 +4970,12 @@ public class NotificationManagerService extends SystemService {
REASON_LISTENER_CANCEL_ALL, info, info.supportsProfiles(),
FLAG_ONGOING_EVENT | FLAG_NO_CLEAR
| FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+ // If cancellation will be prevented due to lifetime extension, we send
+ // an update to system UI.
+ notifySystemUiListenerLifetimeExtendedListLocked(mNotificationList,
+ packageImportance);
+ notifySystemUiListenerLifetimeExtendedListLocked(mEnqueuedNotifications,
+ packageImportance);
} else {
cancelAllLocked(callingUid, callingPid, info.userid,
REASON_LISTENER_CANCEL_ALL, info, info.supportsProfiles(),
@@ -5051,10 +5096,14 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
private void cancelNotificationFromListenerLocked(ManagedServiceInfo info,
int callingUid, int callingPid, String pkg, String tag, int id, int userId,
- int reason) {
+ int reason, int packageImportance) {
int mustNotHaveFlags = FLAG_ONGOING_EVENT;
if (lifetimeExtensionRefactor()) {
mustNotHaveFlags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+ // If cancellation will be prevented due to lifetime extension, we send an update
+ // to system UI.
+ NotificationRecord record = findNotificationLocked(pkg, tag, id, userId);
+ maybeNotifySystemUiListenerLifetimeExtendedLocked(record, pkg, packageImportance);
}
cancelNotification(callingUid, callingPid, pkg, tag, id, 0 /* mustHaveFlags */,
mustNotHaveFlags,
@@ -5197,7 +5246,13 @@ public class NotificationManagerService extends SystemService {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final long identity = Binder.clearCallingIdentity();
+ final int packageImportance;
try {
+ if (lifetimeExtensionRefactor()) {
+ packageImportance = mActivityManager.getPackageImportance(pkg);
+ } else {
+ packageImportance = IMPORTANCE_NONE;
+ }
synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
int cancelReason = REASON_LISTENER_CANCEL;
@@ -5210,7 +5265,7 @@ public class NotificationManagerService extends SystemService {
+ " use cancelNotification(key) instead.");
} else {
cancelNotificationFromListenerLocked(info, callingUid, callingPid,
- pkg, tag, id, info.userid, cancelReason);
+ pkg, tag, id, info.userid, cancelReason, packageImportance);
}
}
} finally {
@@ -11654,6 +11709,30 @@ public class NotificationManagerService extends SystemService {
});
}
+ @FlaggedApi(FLAG_LIFETIME_EXTENSION_REFACTOR)
+ @GuardedBy("mNotificationLock")
+ private void notifySystemUiListenerLifetimeExtendedListLocked(
+ List<NotificationRecord> notificationList, int packageImportance) {
+ for (int i = notificationList.size() - 1; i >= 0; --i) {
+ NotificationRecord record = notificationList.get(i);
+ maybeNotifySystemUiListenerLifetimeExtendedLocked(record,
+ record.getSbn().getPackageName(), packageImportance);
+ }
+ }
+
+ @FlaggedApi(FLAG_LIFETIME_EXTENSION_REFACTOR)
+ @GuardedBy("mNotificationLock")
+ private void maybeNotifySystemUiListenerLifetimeExtendedLocked(NotificationRecord record,
+ String pkg, int packageImportance) {
+ if (record != null && (record.getSbn().getNotification().flags
+ & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
+ boolean isAppForeground = pkg != null && packageImportance == IMPORTANCE_FOREGROUND;
+ mHandler.post(new EnqueueNotificationRunnable(record.getUser().getIdentifier(),
+ record, isAppForeground,
+ mPostNotificationTrackerFactory.newTracker(null)));
+ }
+ }
+
public class NotificationListeners extends ManagedServices {
static final String TAG_ENABLED_NOTIFICATION_LISTENERS = "enabled_listeners";
static final String TAG_REQUESTED_LISTENERS = "request_listeners";
@@ -11777,6 +11856,11 @@ public class NotificationManagerService extends SystemService {
@Override
public void onServiceAdded(ManagedServiceInfo info) {
+ if (lifetimeExtensionRefactor()) {
+ // Only System or System UI can call registerSystemService, so if the caller is not
+ // system, we know it's system UI.
+ info.isSystemUi = !isCallerSystemOrPhone();
+ }
final INotificationListener listener = (INotificationListener) info.service;
final NotificationRankingUpdate update;
synchronized (mNotificationLock) {
@@ -12141,6 +12225,23 @@ public class NotificationManagerService extends SystemService {
continue;
}
+ if (lifetimeExtensionRefactor()) {
+ // Checks if this is a request to notify system UI about a notification that
+ // has been lifetime extended.
+ // (We only need to check old for the flag, because in both cancellation and
+ // update cases, old should have the flag.)
+ // If it is such a request, and this is system UI, we send the post request
+ // only to System UI, and break as we don't need to continue checking other
+ // Managed Services.
+ if (info.isSystemUi() && old != null && old.getNotification() != null
+ && (old.getNotification().flags
+ & Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
+ final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
+ listenerCalls.add(() -> notifyPosted(info, oldSbn, update));
+ break;
+ }
+ }
+
// If we shouldn't notify all listeners, this means the hidden state of
// a notification was changed. Don't notifyPosted listeners targeting >= P.
// Instead, those listeners will receive notifyRankingUpdate.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 05d1c49b3508..85eac29599f5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -98,6 +98,7 @@ import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_SUSPENSION;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
+import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
import static android.app.admin.DeviceAdminInfo.USES_POLICY_FORCE_LOCK;
import static android.app.admin.DeviceAdminInfo.USES_POLICY_WIPE_DATA;
import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
@@ -189,6 +190,7 @@ import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_SYSTEM_USER_
import static android.app.admin.DevicePolicyManager.STATUS_MANAGED_USERS_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.STATUS_NONSYSTEM_USER_EXISTS;
import static android.app.admin.DevicePolicyManager.STATUS_NOT_SYSTEM_USER;
+import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_ONLY_SYSTEM_USER;
import static android.app.admin.DevicePolicyManager.STATUS_OK;
import static android.app.admin.DevicePolicyManager.STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
import static android.app.admin.DevicePolicyManager.STATUS_SYSTEM_USER;
@@ -225,6 +227,7 @@ import static android.app.admin.ProvisioningException.ERROR_SET_DEVICE_OWNER_FAI
import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED;
import static android.app.admin.flags.Flags.backupServiceSecurityLogEventEnabled;
import static android.app.admin.flags.Flags.dumpsysPolicyEngineMigrationEnabled;
+import static android.app.admin.flags.Flags.headlessDeviceOwnerSingleUserEnabled;
import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE;
import static android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
@@ -9460,7 +9463,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
if (setProfileOwnerOnCurrentUserIfNecessary
- && mInjector.userManagerIsHeadlessSystemUserMode()) {
+ && mInjector.userManagerIsHeadlessSystemUserMode()
+ && getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED) {
int currentForegroundUser;
synchronized (getLockObject()) {
currentForegroundUser = getCurrentForegroundUserId();
@@ -9476,6 +9480,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return true;
}
+ private int getHeadlessDeviceOwnerMode() {
+ synchronized (getLockObject()) {
+ return getDeviceOwnerAdminLocked().info.getHeadlessDeviceOwnerMode();
+ }
+ }
+
/**
* This API is cached: invalidate with invalidateBinderCaches().
*/
@@ -12226,6 +12236,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
+ admin + " are not in the same package");
}
final CallerIdentity caller = getCallerIdentity(admin);
+
+ if (headlessDeviceOwnerSingleUserEnabled()) {
+ // Block this method if the device is in headless main user mode
+ Preconditions.checkCallAuthorization(
+ getHeadlessDeviceOwnerMode() != HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER,
+ "createAndManageUser was called while in headless single user mode");
+ }
// Only allow the system user to use this method
Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
"createAndManageUser was called from non-system user");
@@ -16636,29 +16653,51 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return STATUS_USER_NOT_RUNNING;
}
+ DeviceAdminInfo adminInfo = null;
+
+ boolean isHeadlessModeAffiliated = false;
+
+ boolean isHeadlessModeSingleUser = false;
+
boolean isHeadlessSystemUserMode = mInjector.userManagerIsHeadlessSystemUserMode();
+ int ensureSetUpUser = UserHandle.USER_SYSTEM;
if (isHeadlessSystemUserMode) {
- if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
- Slogf.e(LOG_TAG, "In headless system user mode, "
- + "device owner can only be set on headless system user.");
- return STATUS_NOT_SYSTEM_USER;
- }
-
if (owner != null) {
- DeviceAdminInfo adminInfo = findAdmin(
- owner, deviceOwnerUserId, /* throwForMissingPermission= */ false);
+ adminInfo = findAdmin(owner,
+ deviceOwnerUserId, /* throwForMissingPermission= */ false);
- if (adminInfo.getHeadlessDeviceOwnerMode()
- != HEADLESS_DEVICE_OWNER_MODE_AFFILIATED) {
+ isHeadlessModeAffiliated =
+ adminInfo.getHeadlessDeviceOwnerMode()
+ == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
+
+ isHeadlessModeSingleUser =
+ adminInfo.getHeadlessDeviceOwnerMode()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
+
+ if (!isHeadlessModeAffiliated && !isHeadlessModeSingleUser) {
return STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
}
+
+ if (headlessDeviceOwnerSingleUserEnabled() && isHeadlessModeSingleUser) {
+ ensureSetUpUser = mUserManagerInternal.getMainUserId();
+ if (ensureSetUpUser == UserHandle.USER_NULL) {
+ return STATUS_HEADLESS_ONLY_SYSTEM_USER;
+ }
+ }
+ }
+
+ if (isHeadlessModeAffiliated && deviceOwnerUserId != UserHandle.USER_SYSTEM) {
+ Slogf.e(LOG_TAG, "In headless system user mode, "
+ + "device owner can only be set on headless system user.");
+ return STATUS_NOT_SYSTEM_USER;
}
+
}
if (isAdb) {
// If shell command runs after user setup completed check device status. Otherwise, OK.
- if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
+ if (hasUserSetupCompleted(ensureSetUpUser)) {
// DO can be setup only if there are no users which are neither created by default
// nor marked as FOR_TESTING
@@ -16681,11 +16720,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return STATUS_OK;
} else {
// DO has to be user 0
- if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
+ if ((!isHeadlessSystemUserMode || isHeadlessModeAffiliated)
+ && deviceOwnerUserId != UserHandle.USER_SYSTEM) {
return STATUS_NOT_SYSTEM_USER;
}
// Only provision DO before setup wizard completes
- if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
+ if (hasUserSetupCompleted(ensureSetUpUser)) {
return STATUS_USER_SETUP_COMPLETED;
}
return STATUS_OK;
@@ -21260,7 +21300,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
- int deviceOwnerUserId = UserHandle.USER_SYSTEM;
+ int deviceOwnerUserId = headlessDeviceOwnerSingleUserEnabled()
+ && getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER
+ ? mUserManagerInternal.getMainUserId()
+ : UserHandle.USER_SYSTEM;
+
if (!removeNonRequiredAppsForManagedDevice(
deviceOwnerUserId,
provisioningParams.isLeaveAllSystemAppsEnabled(),
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 28471b37d2a0..6bcd778c234b 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
@@ -49,7 +49,6 @@ import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVI
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONTENT_TRIGGER;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
-import static com.android.server.job.controllers.JobStatus.MIN_WINDOW_FOR_FLEXIBILITY_MS;
import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME;
import static org.junit.Assert.assertArrayEquals;
@@ -410,10 +409,12 @@ public class FlexibilityControllerTest {
@Test
public void testOnConstantsUpdated_PercentsToDropConstraints() {
+ final long fallbackDuration = 12 * HOUR_IN_MILLIS;
JobInfo.Builder jb = createJob(0)
- .setOverrideDeadline(MIN_WINDOW_FOR_FLEXIBILITY_MS);
+ .setOverrideDeadline(HOUR_IN_MILLIS);
JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
- assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
+ // Even though the override deadline is 1 hour, the fallback duration is still used.
+ assertEquals(FROZEN_TIME + fallbackDuration / 10 * 5,
mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
"500=1|2|3|4"
@@ -441,13 +442,13 @@ public class FlexibilityControllerTest {
mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
.get(JobInfo.PRIORITY_MIN),
new int[]{54, 55, 56, 57});
- assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10,
+ assertEquals(FROZEN_TIME + fallbackDuration / 10,
mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
js.setNumDroppedFlexibleConstraints(1);
- assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 2,
+ assertEquals(FROZEN_TIME + fallbackDuration / 10 * 2,
mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
js.setNumDroppedFlexibleConstraints(2);
- assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 3,
+ assertEquals(FROZEN_TIME + fallbackDuration / 10 * 3,
mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
}
@@ -504,37 +505,38 @@ public class FlexibilityControllerTest {
@Test
public void testGetNextConstraintDropTimeElapsedLocked() {
+ final long fallbackDuration = 50 * HOUR_IN_MILLIS;
setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 200 * HOUR_IN_MILLIS);
setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
"500=" + HOUR_IN_MILLIS
+ ",400=" + 25 * HOUR_IN_MILLIS
- + ",300=" + 50 * HOUR_IN_MILLIS
+ + ",300=" + fallbackDuration
+ ",200=" + 100 * HOUR_IN_MILLIS
+ ",100=" + 200 * HOUR_IN_MILLIS);
long nextTimeToDropNumConstraints;
// no delay, deadline
- JobInfo.Builder jb = createJob(0).setOverrideDeadline(MIN_WINDOW_FOR_FLEXIBILITY_MS);
+ JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
JobStatus js = createJobStatus("time", jb);
assertEquals(JobStatus.NO_EARLIEST_RUNTIME, js.getEarliestRunTime());
- assertEquals(MIN_WINDOW_FOR_FLEXIBILITY_MS + FROZEN_TIME, js.getLatestRunTimeElapsed());
+ assertEquals(HOUR_IN_MILLIS + FROZEN_TIME, js.getLatestRunTimeElapsed());
assertEquals(FROZEN_TIME, js.enqueueTime);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
+ assertEquals(FROZEN_TIME + fallbackDuration / 10 * 5,
nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
+ assertEquals(FROZEN_TIME + fallbackDuration / 10 * 6,
nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(2);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
+ assertEquals(FROZEN_TIME + fallbackDuration / 10 * 7,
nextTimeToDropNumConstraints);
// delay, no deadline
@@ -574,81 +576,83 @@ public class FlexibilityControllerTest {
// delay, deadline
jb = createJob(0)
- .setOverrideDeadline(2 * MIN_WINDOW_FOR_FLEXIBILITY_MS)
- .setMinimumLatency(MIN_WINDOW_FOR_FLEXIBILITY_MS);
+ .setOverrideDeadline(2 * HOUR_IN_MILLIS)
+ .setMinimumLatency(HOUR_IN_MILLIS);
js = createJobStatus("time", jb);
- final long windowStart = FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS;
+ final long windowStart = FROZEN_TIME + HOUR_IN_MILLIS;
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
+ assertEquals(windowStart + fallbackDuration / 10 * 5,
nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
+ assertEquals(windowStart + fallbackDuration / 10 * 6,
nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(2);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
+ assertEquals(windowStart + fallbackDuration / 10 * 7,
nextTimeToDropNumConstraints);
}
@Test
public void testCurPercent() {
+ final long fallbackDuration = 10 * HOUR_IN_MILLIS;
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES, "300=" + fallbackDuration);
long deadline = 100 * MINUTE_IN_MILLIS;
long nowElapsed = FROZEN_TIME;
JobInfo.Builder jb = createJob(0).setOverrideDeadline(deadline);
JobStatus js = createJobStatus("time", jb);
assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
- assertEquals(deadline + FROZEN_TIME,
+ assertEquals(FROZEN_TIME + fallbackDuration,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, FROZEN_TIME));
- nowElapsed = FROZEN_TIME + 60 * MINUTE_IN_MILLIS;
+ nowElapsed = FROZEN_TIME + 6 * HOUR_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
- nowElapsed = FROZEN_TIME + 130 * MINUTE_IN_MILLIS;
+ nowElapsed = FROZEN_TIME + 13 * HOUR_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
- nowElapsed = FROZEN_TIME + 95 * MINUTE_IN_MILLIS;
+ nowElapsed = FROZEN_TIME + 9 * HOUR_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
+ assertEquals(90, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
nowElapsed = FROZEN_TIME;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- long delay = MINUTE_IN_MILLIS;
- deadline = 101 * MINUTE_IN_MILLIS;
+ long delay = HOUR_IN_MILLIS;
+ deadline = HOUR_IN_MILLIS + 100 * MINUTE_IN_MILLIS;
jb = createJob(0).setOverrideDeadline(deadline).setMinimumLatency(delay);
js = createJobStatus("time", jb);
assertEquals(FROZEN_TIME + delay,
mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
- assertEquals(deadline + FROZEN_TIME,
+ assertEquals(FROZEN_TIME + delay + fallbackDuration,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed,
FROZEN_TIME + delay));
- nowElapsed = FROZEN_TIME + delay + 60 * MINUTE_IN_MILLIS;
+ nowElapsed = FROZEN_TIME + delay + 6 * HOUR_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
- nowElapsed = FROZEN_TIME + 130 * MINUTE_IN_MILLIS;
+ nowElapsed = FROZEN_TIME + 13 * HOUR_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
- nowElapsed = FROZEN_TIME + delay + 95 * MINUTE_IN_MILLIS;
+ nowElapsed = FROZEN_TIME + delay + 9 * HOUR_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
+ assertEquals(90, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
}
@Test
@@ -786,26 +790,27 @@ public class FlexibilityControllerTest {
// deadline
JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
JobStatus js = createJobStatus("time", jb);
- assertEquals(HOUR_IN_MILLIS + FROZEN_TIME,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+ assertEquals(3 * HOUR_IN_MILLIS + js.enqueueTime,
+ mFlexibilityController
+ .getLifeCycleEndElapsedLocked(js, nowElapsed, js.enqueueTime));
// no deadline
- assertEquals(FROZEN_TIME + 2 * HOUR_IN_MILLIS,
+ assertEquals(js.enqueueTime + 2 * HOUR_IN_MILLIS,
mFlexibilityController.getLifeCycleEndElapsedLocked(
createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_HIGH)),
- nowElapsed, 100L));
- assertEquals(FROZEN_TIME + 3 * HOUR_IN_MILLIS,
+ nowElapsed, js.enqueueTime));
+ assertEquals(js.enqueueTime + 3 * HOUR_IN_MILLIS,
mFlexibilityController.getLifeCycleEndElapsedLocked(
createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)),
- nowElapsed, 100L));
- assertEquals(FROZEN_TIME + 4 * HOUR_IN_MILLIS,
+ nowElapsed, js.enqueueTime));
+ assertEquals(js.enqueueTime + 4 * HOUR_IN_MILLIS,
mFlexibilityController.getLifeCycleEndElapsedLocked(
createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_LOW)),
- nowElapsed, 100L));
- assertEquals(FROZEN_TIME + 5 * HOUR_IN_MILLIS,
+ nowElapsed, js.enqueueTime));
+ assertEquals(js.enqueueTime + 5 * HOUR_IN_MILLIS,
mFlexibilityController.getLifeCycleEndElapsedLocked(
createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_MIN)),
- nowElapsed, 100L));
+ nowElapsed, js.enqueueTime));
}
@Test
@@ -871,14 +876,16 @@ public class FlexibilityControllerTest {
mFlexibilityController.prepareForExecutionLocked(jsLow);
mFlexibilityController.prepareForExecutionLocked(jsMin);
- // deadline
- JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
- JobStatus js = createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition", jb);
- assertEquals(HOUR_IN_MILLIS + FROZEN_TIME,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+ final long longDeadlineMs = 14 * 24 * HOUR_IN_MILLIS;
+ JobInfo.Builder jbWithLongDeadline = createJob(0).setOverrideDeadline(longDeadlineMs);
+ JobStatus jsWithLongDeadline = createJobStatus(
+ "testGetLifeCycleEndElapsedLocked_ScoreAddition", jbWithLongDeadline);
+ JobInfo.Builder jbWithShortDeadline =
+ createJob(0).setOverrideDeadline(15 * MINUTE_IN_MILLIS);
+ JobStatus jsWithShortDeadline = createJobStatus(
+ "testGetLifeCycleEndElapsedLocked_ScoreAddition", jbWithShortDeadline);
final long earliestMs = 123L;
- // no deadline
assertEquals(earliestMs + HOUR_IN_MILLIS + 5 * 15 * MINUTE_IN_MILLIS,
mFlexibilityController.getLifeCycleEndElapsedLocked(
createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
@@ -894,6 +901,9 @@ public class FlexibilityControllerTest {
createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)),
nowElapsed, earliestMs));
+ assertEquals(earliestMs + HOUR_IN_MILLIS + 3 * 15 * MINUTE_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ jsWithShortDeadline, nowElapsed, earliestMs));
assertEquals(earliestMs + HOUR_IN_MILLIS + 2 * 15 * MINUTE_IN_MILLIS,
mFlexibilityController.getLifeCycleEndElapsedLocked(
createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
@@ -904,6 +914,9 @@ public class FlexibilityControllerTest {
createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
createJob(0).setPriority(JobInfo.PRIORITY_MIN)),
nowElapsed, earliestMs));
+ assertEquals(jsWithLongDeadline.enqueueTime + longDeadlineMs,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ jsWithLongDeadline, nowElapsed, earliestMs));
}
@Test
@@ -1033,8 +1046,8 @@ public class FlexibilityControllerTest {
JobInfo.Builder jb = createJob(0);
jb.setMinimumLatency(1);
jb.setOverrideDeadline(2);
- JobStatus js = createJobStatus("Disable Flexible When Job Has Short Window", jb);
- assertFalse(js.hasFlexibilityConstraint());
+ JobStatus js = createJobStatus("testExceptions_ShortWindow", jb);
+ assertTrue(js.hasFlexibilityConstraint());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 344a4b0ce324..4dded1d0342d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
import static android.content.Context.DEVICE_POLICY_SERVICE;
+import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
@@ -62,6 +63,7 @@ import android.os.IInterface;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -1983,6 +1985,22 @@ public class ManagedServicesTest extends UiServiceTestCase {
new ComponentName("pkg1", "cmp1"))).isFalse();
}
+ @Test
+ @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
+ public void testManagedServiceInfoIsSystemUi() {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+
+ ManagedServices.ManagedServiceInfo service0 = service.new ManagedServiceInfo(
+ mock(IInterface.class), ComponentName.unflattenFromString("a/a"), 0, false,
+ mock(ServiceConnection.class), 26, 34);
+
+ service0.isSystemUi = true;
+ assertThat(service0.isSystemUi()).isTrue();
+ service0.isSystemUi = false;
+ assertThat(service0.isSystemUi()).isFalse();
+ }
+
private void mockServiceInfoWithMetaData(List<ComponentName> componentNames,
ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas)
throws RemoteException {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 96ffec1509da..046e0570d439 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -2545,6 +2545,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertThat(mBinderService.getActiveNotifications(sbn.getPackageName()).length).isEqualTo(1);
assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
+ // Checks that a post update is sent.
+ verify(mWorkerHandler, times(1))
+ .post(any(NotificationManagerService.PostNotificationRunnable.class));
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(captor.capture(), any(),
+ anyBoolean());
+ assertThat(captor.getValue().getNotification().flags
+ & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
+ FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+
mSetFlagsRule.disableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
mBinderService.cancelNotificationWithTag(PKG, PKG, sbn.getTag(), sbn.getId(),
sbn.getUserId());
@@ -2577,6 +2588,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
assertThat(notifs.length).isEqualTo(1);
assertThat(notifs[0].getId()).isEqualTo(1);
+
+ // Checks that a post update is sent.
+ verify(mWorkerHandler, times(1))
+ .post(any(NotificationManagerService.PostNotificationRunnable.class));
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(captor.capture(), any(),
+ anyBoolean());
+ assertThat(captor.getValue().getNotification().flags
+ & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
+ FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
}
@Test
@@ -2985,18 +3007,29 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testCancelNotificationsFromListener_clearAll_NoClearLifetimeExt()
throws Exception {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
-
final NotificationRecord notif = generateNotificationRecord(
mTestNotificationChannel, 1, null, false);
- notif.getNotification().flags = FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+ notif.getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
mService.addNotification(notif);
-
+ verify(mWorkerHandler, times(0))
+ .post(any(NotificationManagerService.PostNotificationRunnable.class));
mService.getBinderService().cancelNotificationsFromListener(null, null);
waitForIdle();
-
+ // Notification not cancelled.
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
assertThat(notifs.length).isEqualTo(1);
+
+ // Checks that a post update is sent.
+ verify(mWorkerHandler, times(1))
+ .post(any(NotificationManagerService.PostNotificationRunnable.class));
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(captor.capture(), any(),
+ anyBoolean());
+ assertThat(captor.getValue().getNotification().flags
+ & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
+ FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
}
@Test
@@ -3217,6 +3250,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
assertEquals(1, notifs.length);
+
+ // Checks that a post update is sent.
+ verify(mWorkerHandler, times(1))
+ .post(any(NotificationManagerService.PostNotificationRunnable.class));
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(captor.capture(), any(),
+ anyBoolean());
+ assertThat(captor.getValue().getNotification().flags
+ & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
+ FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
}
@Test
@@ -5659,6 +5703,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
assertThat(notifsAfter.length).isEqualTo(1);
assertThat(mService.getNotificationRecord(notif.getKey())).isEqualTo(notif);
+
+ // Checks that a post update is sent.
+ verify(mWorkerHandler, times(1))
+ .post(any(NotificationManagerService.PostNotificationRunnable.class));
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(captor.capture(), any(),
+ anyBoolean());
+ assertThat(captor.getValue().getNotification().flags
+ & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
+ FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
}
@Test
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 3a0a6abb2307..e8ffe541d641 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -34,8 +34,20 @@ java_library_static {
"android.hardware.usb-V1.2-java",
"android.hardware.usb-V1.3-java",
"android.hardware.usb-V3-java",
+ "usb_flags_lib",
],
lint: {
baseline_filename: "lint-baseline.xml",
},
}
+
+aconfig_declarations {
+ name: "usb_flags",
+ package: "com.android.server.usb.flags",
+ srcs: ["**/usb_flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "usb_flags_lib",
+ aconfig_declarations: "usb_flags",
+}
diff --git a/services/usb/java/com/android/server/usb/UsbHandlerManager.java b/services/usb/java/com/android/server/usb/UsbHandlerManager.java
index f3112743bcf2..d83ff1fbc381 100644
--- a/services/usb/java/com/android/server/usb/UsbHandlerManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHandlerManager.java
@@ -37,7 +37,7 @@ import java.util.ArrayList;
*
* @hide
*/
-class UsbHandlerManager {
+public class UsbHandlerManager {
private static final String LOG_TAG = UsbHandlerManager.class.getSimpleName();
private final Context mContext;
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index f91666081e82..2ff21ad40558 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -18,6 +18,7 @@ package com.android.server.usb;
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -62,6 +63,7 @@ import com.android.internal.util.XmlUtils;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.usb.flags.Flags;
import com.android.server.utils.EventLogger;
import libcore.io.IoUtils;
@@ -80,8 +82,20 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
-class UsbProfileGroupSettingsManager {
+public class UsbProfileGroupSettingsManager {
+ /**
+ * &lt;application&gt; level property that an app can specify to restrict any overlaying of
+ * activities when usb device is attached.
+ *
+ *
+ * <p>This should only be set by privileged apps having {@link Manifest.permission#MANAGE_USB}
+ * permission.
+ * @hide
+ */
+ public static final String PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES =
+ "android.app.PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES";
private static final String TAG = UsbProfileGroupSettingsManager.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -101,6 +115,8 @@ class UsbProfileGroupSettingsManager {
private final PackageManager mPackageManager;
+ private final ActivityManager mActivityManager;
+
private final UserManager mUserManager;
private final @NonNull UsbSettingsManager mSettingsManager;
@@ -224,7 +240,7 @@ class UsbProfileGroupSettingsManager {
* @param settingsManager The settings manager of the service
* @param usbResolveActivityManager The resovle activity manager of the service
*/
- UsbProfileGroupSettingsManager(@NonNull Context context, @NonNull UserHandle user,
+ public UsbProfileGroupSettingsManager(@NonNull Context context, @NonNull UserHandle user,
@NonNull UsbSettingsManager settingsManager,
@NonNull UsbHandlerManager usbResolveActivityManager) {
if (DEBUG) Slog.v(TAG, "Creating settings for " + user);
@@ -238,6 +254,7 @@ class UsbProfileGroupSettingsManager {
mContext = context;
mPackageManager = context.getPackageManager();
+ mActivityManager = context.getSystemService(ActivityManager.class);
mSettingsManager = settingsManager;
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
@@ -895,7 +912,10 @@ class UsbProfileGroupSettingsManager {
// Send broadcast to running activities with registered intent
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- resolveActivity(intent, device, true /* showMtpNotification */);
+ //resolving activities only if there is no foreground activity restricting it.
+ if (!shouldRestrictOverlayActivities()) {
+ resolveActivity(intent, device, true /* showMtpNotification */);
+ }
}
private void resolveActivity(Intent intent, UsbDevice device, boolean showMtpNotification) {
@@ -918,6 +938,63 @@ class UsbProfileGroupSettingsManager {
resolveActivity(intent, matches, defaultActivity, device, null);
}
+ /**
+ * @return true if any application in foreground have set restrict_usb_overlay_activities as
+ * true in manifest file. The application needs to have MANAGE_USB permission.
+ */
+ private boolean shouldRestrictOverlayActivities() {
+
+ if (!Flags.allowRestrictionOfOverlayActivities()) return false;
+
+ List<ActivityManager.RunningAppProcessInfo> appProcessInfos = mActivityManager
+ .getRunningAppProcesses();
+
+ List<String> filteredAppProcessInfos = new ArrayList<>();
+ boolean shouldRestrictOverlayActivities;
+
+ //filtering out applications in foreground.
+ for (ActivityManager.RunningAppProcessInfo processInfo : appProcessInfos) {
+ if (processInfo.importance <= ActivityManager
+ .RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+ filteredAppProcessInfos.addAll(List.of(processInfo.pkgList));
+ }
+ }
+
+ if (DEBUG) Slog.d(TAG, "packages in foreground : " + filteredAppProcessInfos);
+
+ List<String> packagesHoldingManageUsbPermission =
+ mPackageManager.getPackagesHoldingPermissions(
+ new String[]{android.Manifest.permission.MANAGE_USB},
+ PackageManager.MATCH_SYSTEM_ONLY).stream()
+ .map(packageInfo -> packageInfo.packageName).collect(Collectors.toList());
+
+ //retaining only packages that hold the required permission
+ filteredAppProcessInfos.retainAll(packagesHoldingManageUsbPermission);
+
+ if (DEBUG) {
+ Slog.d(TAG, "packages in foreground with required permission : "
+ + filteredAppProcessInfos);
+ }
+
+ shouldRestrictOverlayActivities = filteredAppProcessInfos.stream().anyMatch(pkg -> {
+ try {
+ return mPackageManager.getProperty(PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES, pkg)
+ .getBoolean();
+ } catch (NameNotFoundException e) {
+ if (DEBUG) {
+ Slog.d(TAG, "property PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES "
+ + "not present for " + pkg);
+ }
+ return false;
+ }
+ });
+
+ if (shouldRestrictOverlayActivities) {
+ Slog.d(TAG, "restricting starting of usb overlay activities");
+ }
+ return shouldRestrictOverlayActivities;
+ }
+
public void deviceAttachedForFixedHandler(UsbDevice device, ComponentName component) {
final Intent intent = createDeviceAttachedIntent(device);
diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
index 8e53ff412f0a..0b854a8440a5 100644
--- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
@@ -33,7 +33,7 @@ import java.util.List;
/**
* Maintains all {@link UsbUserSettingsManager} for all users.
*/
-class UsbSettingsManager {
+public class UsbSettingsManager {
private static final String LOG_TAG = UsbSettingsManager.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -70,7 +70,7 @@ class UsbSettingsManager {
*
* @return The settings for the user
*/
- @NonNull UsbUserSettingsManager getSettingsForUser(@UserIdInt int userId) {
+ public @NonNull UsbUserSettingsManager getSettingsForUser(@UserIdInt int userId) {
synchronized (mSettingsByUser) {
UsbUserSettingsManager settings = mSettingsByUser.get(userId);
if (settings == null) {
diff --git a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
index c2b8d0109e68..be729c562f64 100644
--- a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
@@ -49,7 +49,7 @@ import org.xmlpull.v1.XmlPullParser;
import java.util.ArrayList;
import java.util.List;
-class UsbUserSettingsManager {
+public class UsbUserSettingsManager {
private static final String TAG = UsbUserSettingsManager.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -81,7 +81,7 @@ class UsbUserSettingsManager {
*
* @return The resolve infos of the activities that can handle the intent
*/
- List<ResolveInfo> queryIntentActivities(@NonNull Intent intent) {
+ public List<ResolveInfo> queryIntentActivities(@NonNull Intent intent) {
return mPackageManager.queryIntentActivitiesAsUser(intent, PackageManager.GET_META_DATA,
mUser.getIdentifier());
}
diff --git a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
new file mode 100644
index 000000000000..ea6e50221cd0
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.usb.flags"
+
+flag {
+ name: "allow_restriction_of_overlay_activities"
+ namespace: "usb"
+ description: "This flag controls the restriction of usb overlay activities"
+ bug: "307231174"
+}
diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java
index 3c11da5f2daa..4ff9712f0907 100644
--- a/telephony/java/android/telephony/DomainSelectionService.java
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -831,7 +831,7 @@ public abstract class DomainSelectionService extends Service {
@NonNull String tag, @NonNull String errorLogName) {
try {
CompletableFuture.runAsync(
- () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
+ () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor);
} catch (CancellationException | CompletionException e) {
Rlog.w(tag, "Binder - " + errorLogName + " exception: " + e.getMessage());
}
diff --git a/tests/UsbManagerTests/Android.bp b/tests/UsbManagerTests/Android.bp
index c02d8e96abb0..70c7dad2fd55 100644
--- a/tests/UsbManagerTests/Android.bp
+++ b/tests/UsbManagerTests/Android.bp
@@ -29,12 +29,17 @@ android_test {
static_libs: [
"frameworks-base-testutils",
"androidx.test.rules",
- "mockito-target-inline-minus-junit4",
+ "mockito-target-extended-minus-junit4",
"platform-test-annotations",
"truth",
"UsbManagerTestLib",
],
- jni_libs: ["libdexmakerjvmtiagent"],
+ jni_libs: [
+ // Required for ExtendedMockito
+ "libdexmakerjvmtiagent",
+ "libmultiplejvmtiagentsinterferenceagent",
+ "libstaticjvmtiagent",
+ ],
certificate: "platform",
platform_apis: true,
test_suites: ["device-tests"],
diff --git a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java
new file mode 100644
index 000000000000..4780d8a610e8
--- /dev/null
+++ b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usbtest;
+
+import static com.android.server.usb.UsbProfileGroupSettingsManager.PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.Property;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.hardware.usb.UsbDevice;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.usb.UsbHandlerManager;
+import com.android.server.usb.UsbProfileGroupSettingsManager;
+import com.android.server.usb.UsbSettingsManager;
+import com.android.server.usb.UsbUserSettingsManager;
+import com.android.server.usb.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.usb.UsbProfileGroupSettingsManager}.
+ * Note: MUST claim MANAGE_USB permission in Manifest
+ */
+@RunWith(AndroidJUnit4.class)
+public class UsbProfileGroupSettingsManagerTest {
+
+ private static final String TEST_PACKAGE_NAME = "testPkg";
+ @Mock
+ private Context mContext;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private ActivityManager mActivityManager;
+ @Mock
+ private UserHandle mUserHandle;
+ @Mock
+ private UsbSettingsManager mUsbSettingsManager;
+ @Mock
+ private UsbHandlerManager mUsbHandlerManager;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private UsbUserSettingsManager mUsbUserSettingsManager;
+ @Mock private Property mProperty;
+ private ActivityManager.RunningAppProcessInfo mRunningAppProcessInfo;
+ private PackageInfo mPackageInfo;
+ private UsbProfileGroupSettingsManager mUsbProfileGroupSettingsManager;
+ private MockitoSession mStaticMockSession;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mRunningAppProcessInfo = new ActivityManager.RunningAppProcessInfo();
+ mRunningAppProcessInfo.pkgList = new String[]{TEST_PACKAGE_NAME};
+ mPackageInfo = new PackageInfo();
+ mPackageInfo.packageName = TEST_PACKAGE_NAME;
+ mPackageInfo.applicationInfo = Mockito.mock(ApplicationInfo.class);
+
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
+ when(mContext.getResources()).thenReturn(Mockito.mock(Resources.class));
+ when(mContext.createPackageContextAsUser(anyString(), anyInt(), any(UserHandle.class)))
+ .thenReturn(mContext);
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+ mUsbProfileGroupSettingsManager = new UsbProfileGroupSettingsManager(mContext, mUserHandle,
+ mUsbSettingsManager, mUsbHandlerManager);
+
+ mStaticMockSession = ExtendedMockito.mockitoSession()
+ .mockStatic(Flags.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+
+ when(mPackageManager.getPackageInfo(TEST_PACKAGE_NAME, 0)).thenReturn(mPackageInfo);
+ when(mPackageManager.getProperty(eq(PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES),
+ eq(TEST_PACKAGE_NAME))).thenReturn(mProperty);
+ when(mUserManager.getEnabledProfiles(anyInt()))
+ .thenReturn(List.of(Mockito.mock(UserInfo.class)));
+ when(mUsbSettingsManager.getSettingsForUser(anyInt())).thenReturn(mUsbUserSettingsManager);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testDeviceAttached_flagTrueWithoutForegroundActivity_resolveActivityCalled() {
+ when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
+ when(mActivityManager.getRunningAppProcesses()).thenReturn(new ArrayList<>());
+ when(mPackageManager.getPackagesHoldingPermissions(
+ new String[]{android.Manifest.permission.MANAGE_USB},
+ PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
+ UsbDevice device = Mockito.mock(UsbDevice.class);
+ mUsbProfileGroupSettingsManager.deviceAttached(device);
+ verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
+ }
+
+ @Test
+ public void testDeviceAttached_noForegroundActivityWithUsbPermission_resolveActivityCalled() {
+ when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
+ when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
+ when(mPackageManager.getPackagesHoldingPermissions(
+ new String[]{android.Manifest.permission.MANAGE_USB},
+ PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(new ArrayList<>());
+ UsbDevice device = Mockito.mock(UsbDevice.class);
+ mUsbProfileGroupSettingsManager.deviceAttached(device);
+ verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
+ }
+
+ @Test
+ public void testDeviceAttached_foregroundActivityWithManifestField_resolveActivityNotCalled() {
+ when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
+ when(mProperty.getBoolean()).thenReturn(true);
+ when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
+ when(mPackageManager.getPackagesHoldingPermissions(
+ new String[]{android.Manifest.permission.MANAGE_USB},
+ PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
+ UsbDevice device = Mockito.mock(UsbDevice.class);
+ mUsbProfileGroupSettingsManager.deviceAttached(device);
+ verify(mUsbUserSettingsManager, times(0))
+ .queryIntentActivities(any(Intent.class));
+ }
+
+ @Test
+ public void testDeviceAttached_foregroundActivityWithoutManifestField_resolveActivityCalled() {
+ when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
+ when(mProperty.getBoolean()).thenReturn(false);
+ when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
+ when(mPackageManager.getPackagesHoldingPermissions(
+ new String[]{android.Manifest.permission.MANAGE_USB},
+ PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
+ UsbDevice device = Mockito.mock(UsbDevice.class);
+ mUsbProfileGroupSettingsManager.deviceAttached(device);
+ verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
+ }
+
+ @Test
+ public void testDeviceAttached_flagFalseForegroundActivity_resolveActivityCalled() {
+ when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(false);
+ when(mProperty.getBoolean()).thenReturn(true);
+ when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
+ when(mPackageManager.getPackagesHoldingPermissions(
+ new String[]{android.Manifest.permission.MANAGE_USB},
+ PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
+ UsbDevice device = Mockito.mock(UsbDevice.class);
+ mUsbProfileGroupSettingsManager.deviceAttached(device);
+ verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
+ }
+}