Merge "Put back annotations in DeviceStateRotationLockSettingControllerTest" into main
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 963307b..a5a08fb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -573,6 +573,7 @@
case Constants.KEY_MIN_LINEAR_BACKOFF_TIME_MS:
case Constants.KEY_MIN_EXP_BACKOFF_TIME_MS:
case Constants.KEY_SYSTEM_STOP_TO_FAILURE_RATIO:
+ case Constants.KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF:
mConstants.updateBackoffConstantsLocked();
break;
case Constants.KEY_CONN_CONGESTION_DELAY_FRAC:
@@ -679,6 +680,8 @@
private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms";
private static final String KEY_SYSTEM_STOP_TO_FAILURE_RATIO =
"system_stop_to_failure_ratio";
+ private static final String KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF =
+ "abandoned_job_timeouts_before_aggressive_backoff";
private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
private static final String KEY_CONN_USE_CELL_SIGNAL_STRENGTH =
@@ -750,6 +753,7 @@
private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
private static final int DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO = 3;
+ private static final int DEFAULT_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF = 3;
private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
private static final boolean DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH = true;
@@ -845,7 +849,12 @@
* incremental failure in the backoff policy calculation.
*/
int SYSTEM_STOP_TO_FAILURE_RATIO = DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO;
-
+ /**
+ * Number of consecutive timeouts by abandoned jobs before we change to aggressive backoff
+ * policy.
+ */
+ int ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF =
+ DEFAULT_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF;
/**
* The fraction of a job's running window that must pass before we
* consider running it when the network is congested.
@@ -1078,6 +1087,10 @@
SYSTEM_STOP_TO_FAILURE_RATIO = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_SYSTEM_STOP_TO_FAILURE_RATIO,
DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO);
+ ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF,
+ DEFAULT_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF);
}
// TODO(141645789): move into ConnectivityController.CcConfig
@@ -1287,6 +1300,8 @@
pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println();
pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
pw.print(KEY_SYSTEM_STOP_TO_FAILURE_RATIO, SYSTEM_STOP_TO_FAILURE_RATIO).println();
+ pw.print(KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF,
+ ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF).println();
pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
pw.print(KEY_CONN_USE_CELL_SIGNAL_STRENGTH, CONN_USE_CELL_SIGNAL_STRENGTH).println();
@@ -2997,6 +3012,7 @@
final long initialBackoffMillis = job.getInitialBackoffMillis();
int numFailures = failureToReschedule.getNumFailures();
+ int numAbandonedFailures = failureToReschedule.getNumAbandonedFailures();
int numSystemStops = failureToReschedule.getNumSystemStops();
// We should back off slowly if JobScheduler keeps stopping the job,
// but back off immediately if the issue appeared to be the app's fault
@@ -3006,9 +3022,19 @@
|| internalStopReason == JobParameters.INTERNAL_STOP_REASON_ANR
|| stopReason == JobParameters.STOP_REASON_USER) {
numFailures++;
+ } else if (android.app.job.Flags.handleAbandonedJobs()
+ && internalStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED) {
+ numAbandonedFailures++;
+ numFailures++;
} else {
numSystemStops++;
}
+
+ int backoffPolicy = job.getBackoffPolicy();
+ if (shouldUseAggressiveBackoff(numAbandonedFailures)) {
+ backoffPolicy = JobInfo.BACKOFF_POLICY_EXPONENTIAL;
+ }
+
final int backoffAttempts =
numFailures + numSystemStops / mConstants.SYSTEM_STOP_TO_FAILURE_RATIO;
final long earliestRuntimeMs;
@@ -3017,7 +3043,7 @@
earliestRuntimeMs = JobStatus.NO_EARLIEST_RUNTIME;
} else {
long delayMillis;
- switch (job.getBackoffPolicy()) {
+ switch (backoffPolicy) {
case JobInfo.BACKOFF_POLICY_LINEAR: {
long backoff = initialBackoffMillis;
if (backoff < mConstants.MIN_LINEAR_BACKOFF_TIME_MS) {
@@ -3046,7 +3072,7 @@
}
JobStatus newJob = new JobStatus(failureToReschedule,
earliestRuntimeMs,
- JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops,
+ JobStatus.NO_LATEST_RUNTIME, numFailures, numAbandonedFailures, numSystemStops,
failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis(),
failureToReschedule.getCumulativeExecutionTimeMs());
if (stopReason == JobParameters.STOP_REASON_USER) {
@@ -3069,6 +3095,20 @@
}
/**
+ * Returns {@code true} if the given number of abandoned failures indicates that JobScheduler
+ * should use an aggressive backoff policy.
+ *
+ * @param numAbandonedFailures The number of abandoned failures.
+ * @return {@code true} if the given number of abandoned failures indicates that JobScheduler
+ * should use an aggressive backoff policy.
+ */
+ public boolean shouldUseAggressiveBackoff(int numAbandonedFailures) {
+ return android.app.job.Flags.handleAbandonedJobs()
+ && numAbandonedFailures
+ > mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF;
+ }
+
+ /**
* Maximum time buffer in which JobScheduler will try to optimize periodic job scheduling. This
* does not cause a job's period to be larger than requested (eg: if the requested period is
* shorter than this buffer). This is used to put a limit on when JobScheduler will intervene
@@ -3147,6 +3187,7 @@
return new JobStatus(periodicToReschedule,
elapsedNow + period - flex, elapsedNow + period,
0 /* numFailures */, 0 /* numSystemStops */,
+ 0 /* numAbandonedFailures */,
sSystemClock.millis() /* lastSuccessfulRunTime */,
periodicToReschedule.getLastFailedRunTime(),
0 /* Reset cumulativeExecutionTime because of successful execution */);
@@ -3163,6 +3204,7 @@
return new JobStatus(periodicToReschedule,
newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
0 /* numFailures */, 0 /* numSystemStops */,
+ 0 /* numAbandonedFailures */,
sSystemClock.millis() /* lastSuccessfulRunTime */,
periodicToReschedule.getLastFailedRunTime(),
0 /* Reset cumulativeExecutionTime because of successful execution */);
@@ -3171,6 +3213,10 @@
@VisibleForTesting
void maybeProcessBuggyJob(@NonNull JobStatus jobStatus, int debugStopReason) {
boolean jobTimedOut = debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT;
+ if (android.app.job.Flags.handleAbandonedJobs()) {
+ jobTimedOut |= (debugStopReason
+ == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+ }
// If madeActive = 0, the job never actually started.
if (!jobTimedOut && jobStatus.madeActive > 0) {
final long executionDurationMs = sUptimeMillisClock.millis() - jobStatus.madeActive;
@@ -3252,9 +3298,12 @@
// we stop it.
final JobStatus rescheduledJob = needsReschedule
? getRescheduleJobForFailureLocked(jobStatus, stopReason, debugStopReason) : null;
+ final boolean isStopReasonAbandoned = android.app.job.Flags.handleAbandonedJobs()
+ && (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
if (rescheduledJob != null
&& !rescheduledJob.shouldTreatAsUserInitiatedJob()
&& (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT
+ || isStopReasonAbandoned
|| debugStopReason == JobParameters.INTERNAL_STOP_REASON_PREEMPT)) {
rescheduledJob.disallowRunInBatterySaverAndDoze();
}
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 d8934d8..dfb3681 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -269,7 +269,9 @@
convertRtcBoundsToElapsed(utcTimes, elapsedNow);
JobStatus newJob = new JobStatus(job,
elapsedRuntimes.first, elapsedRuntimes.second,
- 0, 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime(),
+ 0 /* numFailures */, 0 /* numAbandonedFailures */,
+ 0 /* numSystemStops */, job.getLastSuccessfulRunTime(),
+ job.getLastFailedRunTime(),
job.getCumulativeExecutionTimeMs());
newJob.prepareLocked();
toAdd.add(newJob);
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 a3eaefd..5a33aa0 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
@@ -316,6 +316,12 @@
private final int numFailures;
/**
+ * How many times this job has stopped due to {@link
+ * JobParameters#STOP_REASON_TIMEOUT_ABANDONED}.
+ */
+ private final int mNumAbandonedFailures;
+
+ /**
* The number of times JobScheduler has forced this job to stop due to reasons mostly outside
* of the app's control.
*/
@@ -605,6 +611,8 @@
* @param tag A string associated with the job for debugging/logging purposes.
* @param numFailures Count of how many times this job has requested a reschedule because
* its work was not yet finished.
+ * @param mNumAbandonedFailures Count of how many times this job has requested a reschedule
+ * because it was abandoned.
* @param numSystemStops Count of how many times JobScheduler has forced this job to stop due to
* factors mostly out of the app's control.
* @param earliestRunTimeElapsedMillis Milestone: earliest point in time at which the job
@@ -617,7 +625,7 @@
*/
private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
int sourceUserId, int standbyBucket, @Nullable String namespace, String tag,
- int numFailures, int numSystemStops,
+ int numFailures, int mNumAbandonedFailures, int numSystemStops,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs,
int internalFlags,
@@ -677,6 +685,7 @@
this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
this.numFailures = numFailures;
+ this.mNumAbandonedFailures = mNumAbandonedFailures;
mNumSystemStops = numSystemStops;
int requiredConstraints = job.getConstraintFlags();
@@ -750,7 +759,8 @@
this(jobStatus.getJob(), jobStatus.getUid(),
jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
jobStatus.getStandbyBucket(), jobStatus.getNamespace(),
- jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getNumSystemStops(),
+ jobStatus.getSourceTag(), jobStatus.getNumFailures(),
+ jobStatus.getNumAbandonedFailures(), jobStatus.getNumSystemStops(),
jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
jobStatus.getCumulativeExecutionTimeMs(),
@@ -787,6 +797,7 @@
this(job, callingUid, sourcePkgName, sourceUserId,
standbyBucket, namespace,
sourceTag, /* numFailures */ 0, /* numSystemStops */ 0,
+ /* mNumAbandonedFailures */ 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTimeMs,
innerFlags, dynamicConstraints);
@@ -806,13 +817,15 @@
/** Create a new job to be rescheduled with the provided parameters. */
public JobStatus(JobStatus rescheduling,
long newEarliestRuntimeElapsedMillis,
- long newLatestRuntimeElapsedMillis, int numFailures, int numSystemStops,
+ long newLatestRuntimeElapsedMillis, int numFailures,
+ int mNumAbandonedFailures, int numSystemStops,
long lastSuccessfulRunTime, long lastFailedRunTime,
long cumulativeExecutionTimeMs) {
this(rescheduling.job, rescheduling.getUid(),
rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
rescheduling.getStandbyBucket(), rescheduling.getNamespace(),
- rescheduling.getSourceTag(), numFailures, numSystemStops,
+ rescheduling.getSourceTag(), numFailures,
+ mNumAbandonedFailures, numSystemStops,
newEarliestRuntimeElapsedMillis,
newLatestRuntimeElapsedMillis,
lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTimeMs,
@@ -851,7 +864,8 @@
int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
sourceUserId, elapsedNow);
return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
- standbyBucket, namespace, tag, /* numFailures */ 0, /* numSystemStops */ 0,
+ standbyBucket, namespace, tag, /* numFailures */ 0,
+ /* mNumAbandonedFailures */ 0, /* numSystemStops */ 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
/* cumulativeExecutionTime */ 0,
@@ -1146,6 +1160,13 @@
}
/**
+ * Returns the number of times the job stopped previously for STOP_REASON_TIMEOUT_ABANDONED.
+ */
+ public int getNumAbandonedFailures() {
+ return mNumAbandonedFailures;
+ }
+
+ /**
* Returns the number of times the system stopped a previous execution of this job for reasons
* that were likely outside the app's control.
*/
diff --git a/core/api/current.txt b/core/api/current.txt
index 3af8e7e..55e1aec 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -33612,9 +33612,14 @@
@FlaggedApi("android.os.cpu_gpu_headrooms") public final class CpuHeadroomParams {
ctor public CpuHeadroomParams();
method public int getCalculationType();
+ method @IntRange(from=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) public long getCalculationWindowMillis();
method public void setCalculationType(int);
+ method public void setCalculationWindowMillis(@IntRange(from=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int);
+ method public void setTids(@NonNull int...);
field public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1
field public static final int CPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0
+ field public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; // 0x2710
+ field public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; // 0x32
}
public final class CpuUsageInfo implements android.os.Parcelable {
@@ -33867,9 +33872,13 @@
@FlaggedApi("android.os.cpu_gpu_headrooms") public final class GpuHeadroomParams {
ctor public GpuHeadroomParams();
method public int getCalculationType();
+ method @IntRange(from=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) public int getCalculationWindowMillis();
method public void setCalculationType(int);
+ method public void setCalculationWindowMillis(@IntRange(from=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int);
field public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1
field public static final int GPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0
+ field public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; // 0x2710
+ field public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; // 0x32
}
public class Handler {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 4b83b43..5de46b5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3940,6 +3940,7 @@
field public static final String WIFI_NL80211_SERVICE = "wifinl80211";
field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
field public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
+ field @FlaggedApi("android.net.wifi.flags.usd") public static final String WIFI_USD_SERVICE = "wifi_usd";
}
public final class ContextParams {
@@ -5209,6 +5210,11 @@
method @NonNull public java.util.Collection<android.hardware.contexthub.HubServiceInfo> getServiceInfoCollection();
method @Nullable public String getTag();
method public int getVersion();
+ field public static final int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4; // 0x4
+ field public static final int REASON_ENDPOINT_INVALID = 5; // 0x5
+ field public static final int REASON_ENDPOINT_STOPPED = 6; // 0x6
+ field public static final int REASON_FAILURE = 0; // 0x0
+ field public static final int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3; // 0x3
}
public static final class HubEndpoint.Builder {
@@ -5295,16 +5301,13 @@
@FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointDiscoveryCallback {
method public void onEndpointsStarted(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>);
- method public void onEndpointsStopped(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>);
+ method public void onEndpointsStopped(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>, int);
}
@FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback {
method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable android.hardware.contexthub.HubServiceInfo);
method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession);
- field public static final int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4; // 0x4
- field public static final int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3; // 0x3
- field public static final int REASON_UNSPECIFIED = 0; // 0x0
}
@FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointMessageCallback {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index f3d999a..7fcae45 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -817,13 +817,13 @@
R.layout.notification_2025_template_expanded_base,
R.layout.notification_2025_template_heads_up_base,
R.layout.notification_2025_template_header,
+ R.layout.notification_2025_template_collapsed_messaging,
+ R.layout.notification_2025_template_collapsed_media,
R.layout.notification_template_material_big_picture,
R.layout.notification_template_material_big_text,
R.layout.notification_template_material_inbox,
- R.layout.notification_template_material_messaging,
R.layout.notification_template_material_big_messaging,
R.layout.notification_template_material_conversation,
- R.layout.notification_template_material_media,
R.layout.notification_template_material_big_media,
R.layout.notification_template_material_call,
R.layout.notification_template_material_big_call,
@@ -5924,7 +5924,7 @@
|| resId == getCompactHeadsUpBaseLayoutResource()
|| resId == getMessagingCompactHeadsUpLayoutResource()
|| resId == getCollapsedMessagingLayoutResource()
- || resId == R.layout.notification_template_material_media);
+ || resId == getCollapsedMediaLayoutResource());
RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
resetStandardTemplate(contentView);
@@ -7573,13 +7573,25 @@
}
private int getCollapsedMessagingLayoutResource() {
- return R.layout.notification_template_material_messaging;
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_collapsed_messaging;
+ } else {
+ return R.layout.notification_template_material_messaging;
+ }
}
private int getExpandedMessagingLayoutResource() {
return R.layout.notification_template_material_big_messaging;
}
+ private int getCollapsedMediaLayoutResource() {
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_collapsed_media;
+ } else {
+ return R.layout.notification_template_material_media;
+ }
+ }
+
private int getConversationLayoutResource() {
return R.layout.notification_template_material_conversation;
}
@@ -10480,7 +10492,7 @@
.fillTextsFrom(mBuilder);
TemplateBindResult result = new TemplateBindResult();
RemoteViews template = mBuilder.applyStandardTemplate(
- R.layout.notification_template_material_media, p,
+ mBuilder.getCollapsedMediaLayoutResource(), p,
null /* result */);
for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index e218418..3973c58 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -61,6 +61,8 @@
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
@@ -680,12 +682,17 @@
@GuardedBy("mLock")
private boolean mTestMode = false;
- /**
- * The local value of the handler, used during testing but also used directly by the
- * NonceLocal handler.
- */
+ // This is the local value of the nonce, as last set by the NonceHandler. It is always
+ // updated by the setNonce() operation. The getNonce() operation returns this value in
+ // NonceLocal handlers and handlers in test mode.
@GuardedBy("mLock")
- protected long mTestNonce = NONCE_UNSET;
+ protected long mShadowNonce = NONCE_UNSET;
+
+ // A list of watchers to be notified of changes. This is null until at least one watcher
+ // registers. Checking for null is meant to be the fastest way the handler can determine
+ // that there are no watchers to be notified.
+ @GuardedBy("mLock")
+ private ArrayList<Semaphore> mWatchers;
/**
* The methods to get and set a nonce from whatever storage is being used. mLock may be
@@ -701,27 +708,60 @@
/**
* Get a nonce from storage. If the handler is in test mode, the nonce is returned from
- * the local mTestNonce.
+ * the local mShadowNonce.
*/
long getNonce() {
synchronized (mLock) {
- if (mTestMode) return mTestNonce;
+ if (mTestMode) return mShadowNonce;
}
return getNonceInternal();
}
/**
- * Write a nonce to storage. If the handler is in test mode, the nonce is written to the
- * local mTestNonce and storage is not affected.
+ * Write a nonce to storage. The nonce is always written to the local mShadowNonce. If
+ * the handler is not in test mode the nonce is also written to storage.
*/
void setNonce(long val) {
synchronized (mLock) {
- if (mTestMode) {
- mTestNonce = val;
- return;
+ mShadowNonce = val;
+ if (!mTestMode) {
+ setNonceInternal(val);
+ }
+ wakeAllWatchersLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void wakeAllWatchersLocked() {
+ if (mWatchers != null) {
+ for (int i = 0; i < mWatchers.size(); i++) {
+ mWatchers.get(i).release();
}
}
- setNonceInternal(val);
+ }
+
+ /**
+ * Register a watcher to be notified when a nonce changes. There is no check for
+ * duplicates. In general, this method is called only from {@link NonceWatcher}.
+ */
+ void registerWatcher(Semaphore s) {
+ synchronized (mLock) {
+ if (mWatchers == null) {
+ mWatchers = new ArrayList<>();
+ }
+ mWatchers.add(s);
+ }
+ }
+
+ /**
+ * Unregister a watcher. Nothing happens if the watcher is not registered.
+ */
+ void unregisterWatcher(Semaphore s) {
+ synchronized (mLock) {
+ if (mWatchers != null) {
+ mWatchers.remove(s);
+ }
+ }
}
/**
@@ -854,7 +894,7 @@
void setTestMode(boolean mode) {
synchronized (mLock) {
mTestMode = mode;
- mTestNonce = NONCE_UNSET;
+ mShadowNonce = NONCE_UNSET;
}
}
@@ -1028,7 +1068,7 @@
/**
* SystemProperties and shared storage are protected and cannot be written by random
* processes. So, for testing purposes, the NonceLocal handler stores the nonce locally. The
- * NonceLocal uses the mTestNonce in the superclass, regardless of test mode.
+ * NonceLocal uses the mShadowNonce in the superclass, regardless of test mode.
*/
private static class NonceLocal extends NonceHandler {
// The saved nonce.
@@ -1040,16 +1080,130 @@
@Override
long getNonceInternal() {
- return mTestNonce;
+ return mShadowNonce;
}
@Override
void setNonceInternal(long value) {
- mTestNonce = value;
+ mShadowNonce = value;
}
}
/**
+ * A NonceWatcher lets an external client test if a nonce value has changed from the last time
+ * the watcher was checked.
+ * @hide
+ */
+ public static class NonceWatcher implements AutoCloseable {
+ // The handler for the key.
+ private final NonceHandler mHandler;
+
+ // The last-seen value. This is initialized to "unset".
+ private long mLastSeen = NONCE_UNSET;
+
+ // The semaphore that the watcher waits on. A permit is released every time the nonce
+ // changes. Permits are acquired in the wait method.
+ private final Semaphore mSem = new Semaphore(0);
+
+ /**
+ * Create a watcher for a handler. The last-seen value is not set here and will be
+ * "unset". Therefore, a call to isChanged() will return true if the nonce has ever been
+ * set, no matter when the watcher is first created. Clients may want to flush that
+ * change by calling isChanged() immediately after constructing the object.
+ */
+ private NonceWatcher(@NonNull NonceHandler handler) {
+ mHandler = handler;
+ mHandler.registerWatcher(mSem);
+ }
+
+ /**
+ * Unregister to be notified when a nonce changes. NonceHandler allows a call to
+ * unregisterWatcher with a semaphore that is not registered, so there is no check inside
+ * this method to guard against multiple closures.
+ */
+ @Override
+ public void close() {
+ mHandler.unregisterWatcher(mSem);
+ }
+
+ /**
+ * Return the last seen value of the nonce. This does not update that value. Only
+ * {@link #isChanged()} updates the value.
+ */
+ public long lastSeen() {
+ return mLastSeen;
+ }
+
+ /**
+ * Return true if the nonce has changed from the last time isChanged() was called. The
+ * method is not thread safe.
+ * @hide
+ */
+ public boolean isChanged() {
+ long current = mHandler.getNonce();
+ if (current != mLastSeen) {
+ mLastSeen = current;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Wait for the nonce value to change. It is not guaranteed that the nonce has changed when
+ * this returns: clients must confirm with {@link #isChanged}. The wait operation is only
+ * effective in a process that writes the nonces. The function returns the number of times
+ * the nonce had changed since the last call to the method.
+ * @hide
+ */
+ public int waitForChange() throws InterruptedException {
+ mSem.acquire(1);
+ return 1 + mSem.drainPermits();
+ }
+
+ /**
+ * Wait for the nonce value to change. It is not guaranteed that the nonce has changed when
+ * this returns: clients must confirm with {@link #isChanged}. The wait operation is only
+ * effective in a process that writes the nonces. The function returns the number of times
+ * the nonce changed since the last call to the method. A return value of zero means the
+ * timeout expired. Beware that a timeout of 0 means the function will not wait at all.
+ * @hide
+ */
+ public int waitForChange(long timeout, TimeUnit timeUnit) throws InterruptedException {
+ if (mSem.tryAcquire(1, timeout, timeUnit)) {
+ return 1 + mSem.drainPermits();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Wake the watcher by releasing the semaphore. This can be used to wake clients that are
+ * blocked in {@link #waitForChange} without affecting the underlying nonce.
+ * @hide
+ */
+ public void wakeUp() {
+ mSem.release();
+ }
+ }
+
+ /**
+ * Return a NonceWatcher for the cache.
+ * @hide
+ */
+ public NonceWatcher getNonceWatcher() {
+ return new NonceWatcher(mNonce);
+ }
+
+ /**
+ * Return a NonceWatcher for the given property. If a handler does not exist for the
+ * property, one is created. This throws if the property name is not a valid cache key.
+ * @hide
+ */
+ public static NonceWatcher getNonceWatcher(@NonNull String propertyName) {
+ return new NonceWatcher(getNonceHandler(propertyName));
+ }
+
+ /**
* Complete key prefixes.
*/
private static final String PREFIX_TEST = CACHE_KEY_PREFIX + "." + MODULE_TEST + ".";
@@ -1663,6 +1817,26 @@
}
/**
+ * Non-static version of corkInvalidations() for situations in which the cache instance is
+ * available. This is slightly faster than than the static versions because it does not have
+ * to look up the NonceHandler for a given property name.
+ * @hide
+ */
+ public void corkInvalidations() {
+ mNonce.cork();
+ }
+
+ /**
+ * Non-static version of uncorkInvalidations() for situations in which the cache instance is
+ * available. This is slightly faster than than the static versions because it does not have
+ * to look up the NonceHandler for a given property name.
+ * @hide
+ */
+ public void uncorkInvalidations() {
+ mNonce.uncork();
+ }
+
+ /**
* Invalidate caches in all processes that are keyed for the module and api.
* @hide
*/
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index acad92c9..6e2ca2c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4250,6 +4250,7 @@
//@hide: WIFI_RTT_SERVICE,
//@hide: ETHERNET_SERVICE,
WIFI_RTT_RANGING_SERVICE,
+ WIFI_USD_SERVICE,
NSD_SERVICE,
AUDIO_SERVICE,
AUDIO_DEVICE_VOLUME_SERVICE,
@@ -5096,6 +5097,19 @@
*/
public static final String WIFI_RTT_RANGING_SERVICE = "wifirtt";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.net.wifi.usd.UsdManager} for Unsynchronized Service Discovery (USD) operation.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.wifi.usd.UsdManager
+ * @hide
+ */
+ @FlaggedApi(android.net.wifi.flags.Flags.FLAG_USD)
+ @SystemApi
+ public static final String WIFI_USD_SERVICE = "wifi_usd";
+
/**
* Use with {@link #getSystemService(String)} to retrieve a {@link
* android.net.lowpan.LowpanManager} for handling management of
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
index 078b4d4..7efdd6d 100644
--- a/core/java/android/hardware/contexthub/HubEndpoint.java
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -18,6 +18,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -31,6 +32,8 @@
import androidx.annotation.GuardedBy;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -48,6 +51,46 @@
public class HubEndpoint {
private static final String TAG = "HubEndpoint";
+ /**
+ * Constants describing the outcome of operations through HubEndpoints (like opening/closing of
+ * sessions or stopping of endpoints).
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"REASON_"},
+ value = {
+ REASON_FAILURE,
+ REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED,
+ REASON_CLOSE_ENDPOINT_SESSION_REQUESTED,
+ REASON_ENDPOINT_INVALID,
+ REASON_ENDPOINT_STOPPED,
+ })
+ public @interface Reason {}
+
+ /** Unclassified failure */
+ public static final int REASON_FAILURE = 0;
+
+ // The values 1 and 2 are reserved at the Context Hub HAL but not exposed to apps.
+
+ /** The peer rejected the request to open this endpoint session. */
+ public static final int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3;
+
+ /** The peer closed this endpoint session. */
+ public static final int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4;
+
+ /** The peer endpoint is invalid. */
+ public static final int REASON_ENDPOINT_INVALID = 5;
+
+ /**
+ * The endpoint is now stopped. The app should retrieve the endpoint info using {@link
+ * android.hardware.location.ContextHubManager#findEndpoints} or register updates through
+ * {@link android.hardware.location.ContextHubManager#registerEndpointDiscoveryCallback}
+ * to get notified if the endpoint restarts.
+ */
+ public static final int REASON_ENDPOINT_STOPPED = 6;
+
private final Object mLock = new Object();
private final HubEndpointInfo mPendingHubEndpointInfo;
@Nullable private final IHubEndpointLifecycleCallback mLifecycleCallback;
@@ -173,9 +216,7 @@
try {
mServiceToken.closeSession(
- sessionId,
- IHubEndpointLifecycleCallback
- .REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
+ sessionId, REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -396,9 +437,7 @@
try {
// Oneway notification to system service
- serviceToken.closeSession(
- session.getId(),
- IHubEndpointLifecycleCallback.REASON_CLOSE_ENDPOINT_SESSION_REQUESTED);
+ serviceToken.closeSession(session.getId(), REASON_CLOSE_ENDPOINT_SESSION_REQUESTED);
} catch (RemoteException e) {
Log.e(TAG, "closeSession: failed to close session " + session, e);
e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpointDiscoveryCallback.aidl b/core/java/android/hardware/contexthub/IContextHubEndpointDiscoveryCallback.aidl
index 85775c0..245be93 100644
--- a/core/java/android/hardware/contexthub/IContextHubEndpointDiscoveryCallback.aidl
+++ b/core/java/android/hardware/contexthub/IContextHubEndpointDiscoveryCallback.aidl
@@ -31,6 +31,7 @@
/**
* Called when endpoint(s) stopped.
* @param hubEndpointInfoList The list of endpoints that started.
+ * @param reason The reason why the endpoints stopped.
*/
- void onEndpointsStopped(in HubEndpointInfo[] hubEndpointInfoList);
+ void onEndpointsStopped(in HubEndpointInfo[] hubEndpointInfoList, int reason);
}
diff --git a/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java b/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java
index 0b77ddb..a61a7eb 100644
--- a/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java
+++ b/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java
@@ -42,7 +42,8 @@
* Called when a list of hub endpoints have stopped.
*
* @param discoveryInfoList The list containing hub discovery information.
+ * @param reason The reason the endpoints stopped.
*/
- // TODO(b/375487784): Add endpoint stop reason
- void onEndpointsStopped(@NonNull List<HubDiscoveryInfo> discoveryInfoList);
+ void onEndpointsStopped(
+ @NonNull List<HubDiscoveryInfo> discoveryInfoList, @HubEndpoint.Reason int reason);
}
diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
index 4688439..fe449bb 100644
--- a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
+++ b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
@@ -17,15 +17,11 @@
package android.hardware.contexthub;
import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.chre.flags.Flags;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
/**
* Interface for listening to lifecycle events of a hub endpoint.
*
@@ -34,24 +30,6 @@
@SystemApi
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
public interface IHubEndpointLifecycleCallback {
- /** Unknown reason. */
- int REASON_UNSPECIFIED = 0;
-
- /** The peer rejected the request to open this endpoint session. */
- int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3;
-
- /** The peer closed this endpoint session. */
- int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4;
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({
- REASON_UNSPECIFIED,
- REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED,
- REASON_CLOSE_ENDPOINT_SESSION_REQUESTED,
- })
- @interface EndpointLifecycleReason {}
-
/**
* Called when an endpoint is requesting a session be opened with another endpoint.
*
@@ -78,5 +56,5 @@
* used.
* @param reason The reason why this session was closed.
*/
- void onSessionClosed(@NonNull HubEndpointSession session, @EndpointLifecycleReason int reason);
+ void onSessionClosed(@NonNull HubEndpointSession session, @HubEndpoint.Reason int reason);
}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 5e8a187..117d8fe 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -791,7 +791,7 @@
}
@Override
- public void onEndpointsStopped(HubEndpointInfo[] hubEndpointInfoList) {
+ public void onEndpointsStopped(HubEndpointInfo[] hubEndpointInfoList, int reason) {
if (hubEndpointInfoList.length == 0) {
Log.w(TAG, "onEndpointsStopped: received empty discovery list");
return;
@@ -815,7 +815,7 @@
if (discoveryList.isEmpty()) {
Log.w(TAG, "onEndpointsStopped: no matching service descriptor");
} else {
- callback.onEndpointsStopped(discoveryList);
+ callback.onEndpointsStopped(discoveryList, reason);
}
});
}
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 6d42612..6e67578 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -24,6 +24,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
/**
* Query parameters for the {@link BatteryStatsManager#getBatteryUsageStats()} call.
@@ -196,7 +197,24 @@
return mAggregatedToTimestamp;
}
+ @Override
+ public String toString() {
+ return "BatteryUsageStatsQuery{"
+ + "mFlags=" + Integer.toHexString(mFlags)
+ + ", mUserIds=" + Arrays.toString(mUserIds)
+ + ", mMaxStatsAgeMs=" + mMaxStatsAgeMs
+ + ", mAggregatedFromTimestamp=" + mAggregatedFromTimestamp
+ + ", mAggregatedToTimestamp=" + mAggregatedToTimestamp
+ + ", mMonotonicStartTime=" + mMonotonicStartTime
+ + ", mMonotonicEndTime=" + mMonotonicEndTime
+ + ", mMinConsumedPowerThreshold=" + mMinConsumedPowerThreshold
+ + ", mPowerComponents=" + Arrays.toString(mPowerComponents)
+ + '}';
+ }
+
private BatteryUsageStatsQuery(Parcel in) {
+ mMonotonicStartTime = in.readLong();
+ mMonotonicEndTime = in.readLong();
mFlags = in.readInt();
mUserIds = new int[in.readInt()];
in.readIntArray(mUserIds);
@@ -209,6 +227,8 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mMonotonicStartTime);
+ dest.writeLong(mMonotonicEndTime);
dest.writeInt(mFlags);
dest.writeInt(mUserIds.length);
dest.writeIntArray(mUserIds);
diff --git a/core/java/android/os/CpuHeadroomParams.java b/core/java/android/os/CpuHeadroomParams.java
index f0d4f7d..8e78b7e 100644
--- a/core/java/android/os/CpuHeadroomParams.java
+++ b/core/java/android/os/CpuHeadroomParams.java
@@ -18,10 +18,13 @@
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.os.health.SystemHealthManager;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
/**
* Headroom request params used by {@link SystemHealthManager#getCpuHeadroom(CpuHeadroomParams)}.
@@ -54,6 +57,16 @@
public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1;
/**
+ * Minimum CPU headroom calculation window size.
+ */
+ public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50;
+
+ /**
+ * Maximum CPU headroom calculation window size.
+ */
+ public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000;
+
+ /**
* Sets the headroom calculation type.
* <p>
*
@@ -83,6 +96,63 @@
}
/**
+ * Sets the headroom calculation window size in milliseconds.
+ * <p>
+ *
+ * @param windowMillis the window size in milliseconds, ranged from
+ * [{@link #CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN},
+ * {@link #CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX}]. The smaller
+ * the value, the larger fluctuation in value should be expected. The
+ * default value can be retrieved from the
+ * {@link #getCalculationWindowMillis}. The device will try to use the
+ * closest feasible window size to this param.
+ * @throws IllegalArgumentException if the window size is not in allowed range.
+ */
+ public void setCalculationWindowMillis(
+ @IntRange(from = CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to =
+ CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) {
+ if (windowMillis < CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN
+ || windowMillis > CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) {
+ throw new IllegalArgumentException("Invalid calculation window: " + windowMillis);
+ }
+ mInternal.calculationWindowMillis = windowMillis;
+ }
+
+ /**
+ * Gets the headroom calculation window size in milliseconds.
+ * <p>
+ * This will return the default value chosen by the device if not set.
+ */
+ public @IntRange(from = CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to =
+ CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) long getCalculationWindowMillis() {
+ return mInternal.calculationWindowMillis;
+ }
+
+ /**
+ * Sets the thread TIDs to track.
+ * <p>
+ * The TIDs should belong to the same of the process that will the headroom call. And they
+ * should not have different core affinity.
+ * <p>
+ * If not set, the headroom will be based on the PID of the process making the call.
+ *
+ * @param tids non-empty list of TIDs, maximum 5.
+ * @throws IllegalArgumentException if the list size is not in allowed range or TID is not
+ * positive.
+ */
+ public void setTids(@NonNull int... tids) {
+ if (tids.length == 0 || tids.length > 5) {
+ throw new IllegalArgumentException("Invalid number of TIDs: " + tids.length);
+ }
+ for (int tid : tids) {
+ if (tid <= 0) {
+ throw new IllegalArgumentException("Invalid TID: " + tid);
+ }
+ }
+ mInternal.tids = Arrays.copyOf(tids, tids.length);
+ }
+
+ /**
* @hide
*/
public CpuHeadroomParamsInternal getInternal() {
diff --git a/core/java/android/os/CpuHeadroomParamsInternal.aidl b/core/java/android/os/CpuHeadroomParamsInternal.aidl
index 6cc4699..d572f965 100644
--- a/core/java/android/os/CpuHeadroomParamsInternal.aidl
+++ b/core/java/android/os/CpuHeadroomParamsInternal.aidl
@@ -25,6 +25,8 @@
@JavaDerive(equals = true, toString = true)
parcelable CpuHeadroomParamsInternal {
boolean usesDeviceHeadroom = false;
+ int[] tids;
+ int calculationWindowMillis = 1000;
CpuHeadroomParams.CalculationType calculationType = CpuHeadroomParams.CalculationType.MIN;
CpuHeadroomParams.SelectionType selectionType = CpuHeadroomParams.SelectionType.ALL;
}
diff --git a/core/java/android/os/GpuHeadroomParams.java b/core/java/android/os/GpuHeadroomParams.java
index efb2a28..4dc9826 100644
--- a/core/java/android/os/GpuHeadroomParams.java
+++ b/core/java/android/os/GpuHeadroomParams.java
@@ -18,6 +18,7 @@
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.os.health.SystemHealthManager;
import java.lang.annotation.Retention;
@@ -54,6 +55,16 @@
public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1;
/**
+ * Minimum GPU headroom calculation window size.
+ */
+ public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50;
+
+ /**
+ * Maximum GPU headroom calculation window size.
+ */
+ public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000;
+
+ /**
* Sets the headroom calculation type.
* <p>
*
@@ -83,6 +94,39 @@
}
/**
+ * Sets the headroom calculation window size in milliseconds.
+ * <p>
+ *
+ * @param windowMillis the window size in milliseconds, ranged from
+ * [{@link #GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN},
+ * {@link #GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX}]. The smaller
+ * the value, the larger fluctuation in value should be expected. The
+ * default value can be retrieved from the
+ * {@link #getCalculationWindowMillis}. If the device will try to use the
+ * closest feasible window size to this param.
+ * @throws IllegalArgumentException if the window is invalid.
+ */
+ public void setCalculationWindowMillis(
+ @IntRange(from = GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to =
+ GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) {
+ if (windowMillis < GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN
+ || windowMillis > GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) {
+ throw new IllegalArgumentException("Invalid calculation window: " + windowMillis);
+ }
+ mInternal.calculationWindowMillis = windowMillis;
+ }
+
+ /**
+ * Gets the headroom calculation window size in milliseconds.
+ * <p>
+ * This will return the default value chosen by the device if not set.
+ */
+ public @IntRange(from = GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to =
+ GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int getCalculationWindowMillis() {
+ return mInternal.calculationWindowMillis;
+ }
+
+ /**
* @hide
*/
public GpuHeadroomParamsInternal getInternal() {
diff --git a/core/java/android/os/GpuHeadroomParamsInternal.aidl b/core/java/android/os/GpuHeadroomParamsInternal.aidl
index 20309e7..40d5d8e 100644
--- a/core/java/android/os/GpuHeadroomParamsInternal.aidl
+++ b/core/java/android/os/GpuHeadroomParamsInternal.aidl
@@ -24,5 +24,6 @@
*/
@JavaDerive(equals = true, toString = true)
parcelable GpuHeadroomParamsInternal {
+ int calculationWindowMillis = 1000;
GpuHeadroomParams.CalculationType calculationType = GpuHeadroomParams.CalculationType.MIN;
}
diff --git a/core/java/android/os/IHintManager.aidl b/core/java/android/os/IHintManager.aidl
index a043da9..f1936b5 100644
--- a/core/java/android/os/IHintManager.aidl
+++ b/core/java/android/os/IHintManager.aidl
@@ -21,7 +21,9 @@
import android.os.GpuHeadroomParamsInternal;
import android.os.IHintSession;
import android.os.SessionCreationConfig;
+import android.hardware.power.CpuHeadroomResult;
import android.hardware.power.ChannelConfig;
+import android.hardware.power.GpuHeadroomResult;
import android.hardware.power.SessionConfig;
import android.hardware.power.SessionTag;
@@ -53,9 +55,9 @@
*/
@nullable ChannelConfig getSessionChannel(in IBinder token);
oneway void closeSessionChannel();
- float[] getCpuHeadroom(in CpuHeadroomParamsInternal params);
+ @nullable CpuHeadroomResult getCpuHeadroom(in CpuHeadroomParamsInternal params);
long getCpuHeadroomMinIntervalMillis();
- float getGpuHeadroom(in GpuHeadroomParamsInternal params);
+ @nullable GpuHeadroomResult getGpuHeadroom(in GpuHeadroomParamsInternal params);
long getGpuHeadroomMinIntervalMillis();
/**
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 94259d7..f9789c1 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -91,6 +91,8 @@
# PerformanceHintManager
per-file CpuHeadroom*.aidl = file:/ADPF_OWNERS
per-file GpuHeadroom*.aidl = file:/ADPF_OWNERS
+per-file CpuHeadroom*.java = file:/ADPF_OWNERS
+per-file GpuHeadroom*.java = file:/ADPF_OWNERS
per-file PerformanceHintManager.java = file:/ADPF_OWNERS
per-file WorkDuration.java = file:/ADPF_OWNERS
per-file IHintManager.aidl = file:/ADPF_OWNERS
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index 4db9bc3..cd79e41 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -23,6 +23,8 @@
import android.annotation.SystemService;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.hardware.power.CpuHeadroomResult;
+import android.hardware.power.GpuHeadroomResult;
import android.os.BatteryStats;
import android.os.Build;
import android.os.Bundle;
@@ -110,15 +112,16 @@
}
/**
- * Provides an estimate of global available CPU headroom of the calling thread.
+ * Provides an estimate of global available CPU headroom.
* <p>
*
* @param params params to customize the CPU headroom calculation, null to use default params.
- * @return a single value a {@code Float.NaN} if it's temporarily unavailable.
+ * @return a single value headroom or a {@code Float.NaN} if it's temporarily unavailable.
* A valid value is ranged from [0, 100], where 0 indicates no more CPU resources can be
* granted.
- * @throws UnsupportedOperationException if the API is unsupported or the request params can't
- * be served.
+ * @throws UnsupportedOperationException if the API is unsupported.
+ * @throws SecurityException if the TIDs of the params don't belong to the same process.
+ * @throws IllegalStateException if the TIDs of the params don't have the same affinity setting.
*/
@FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS)
public @FloatRange(from = 0f, to = 100f) float getCpuHeadroom(
@@ -127,8 +130,12 @@
throw new UnsupportedOperationException();
}
try {
- return mHintManager.getCpuHeadroom(
- params != null ? params.getInternal() : new CpuHeadroomParamsInternal())[0];
+ final CpuHeadroomResult ret = mHintManager.getCpuHeadroom(
+ params != null ? params.getInternal() : new CpuHeadroomParamsInternal());
+ if (ret == null || ret.getTag() != CpuHeadroomResult.globalHeadroom) {
+ return Float.NaN;
+ }
+ return ret.getGlobalHeadroom();
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -144,8 +151,7 @@
* @return a single value headroom or a {@code Float.NaN} if it's temporarily unavailable.
* A valid value is ranged from [0, 100], where 0 indicates no more GPU resources can be
* granted.
- * @throws UnsupportedOperationException if the API is unsupported or the request params can't
- * be served.
+ * @throws UnsupportedOperationException if the API is unsupported.
*/
@FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS)
public @FloatRange(from = 0f, to = 100f) float getGpuHeadroom(
@@ -154,8 +160,12 @@
throw new UnsupportedOperationException();
}
try {
- return mHintManager.getGpuHeadroom(
+ final GpuHeadroomResult ret = mHintManager.getGpuHeadroom(
params != null ? params.getInternal() : new GpuHeadroomParamsInternal());
+ if (ret == null || ret.getTag() != GpuHeadroomResult.globalHeadroom) {
+ return Float.NaN;
+ }
+ return ret.getGlobalHeadroom();
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 3c35701..a5c837b 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -132,3 +132,9 @@
description: "Android Advanced Protection Mode Feature: Memory Tagging Extension"
bug: "378931989"
}
+flag {
+ name: "aapm_feature_disable_cellular_2g"
+ namespace: "responsible_apis"
+ description: "Android Advanced Protection Mode Feature: Disable Cellular 2G"
+ bug: "377748286"
+}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b6e114b..a0feccd 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1236,6 +1236,8 @@
private @ActivityInfo.ColorMode int mCurrentColorMode = ActivityInfo.COLOR_MODE_DEFAULT;
private long mColorModeLastSetMillis = -1;
+ private final boolean mIsSubscribeGranularDisplayEventsEnabled;
+
public ViewRootImpl(Context context, Display display) {
this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout());
}
@@ -1333,6 +1335,8 @@
// Disable DRAW_WAKE_LOCK starting U.
mDisableDrawWakeLock =
CompatChanges.isChangeEnabled(DISABLE_DRAW_WAKE_LOCK) && disableDrawWakeLock();
+ mIsSubscribeGranularDisplayEventsEnabled =
+ com.android.server.display.feature.flags.Flags.subscribeGranularDisplayEvents();
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -1810,14 +1814,22 @@
mAccessibilityInteractionConnectionManager, mHandler);
mAccessibilityManager.addHighContrastTextStateChangeListener(
mExecutor, mHighContrastTextManager);
+
+
+ long eventsToBeRegistered =
+ (mIsSubscribeGranularDisplayEventsEnabled)
+ ? DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_STATE
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED
+ : DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
DisplayManagerGlobal
.getInstance()
.registerDisplayListener(
mDisplayListener,
mHandler,
- DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
- | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
+ eventsToBeRegistered,
mBasePackageName);
if (forceInvertColor()) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 595eb26..7e3b904 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1298,7 +1298,13 @@
@Override
public void visitUris(@NonNull Consumer<Uri> visitor) {
- if (mIntentId != -1 || mItems == null) {
+ if (mItems == null) {
+ // Null item indicates adapter conversion took place, so the URIs in cached items
+ // need to be validated.
+ RemoteCollectionItems cachedItems = mCollectionCache.getItemsForId(mIntentId);
+ if (cachedItems != null) {
+ cachedItems.visitUris(visitor);
+ }
return;
}
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index fdaab66..2b370b9 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -94,6 +94,23 @@
mController.attachToDisplayArea(mType, getDisplayId(), mOptions);
}
+ /**
+ * Updates this context to a new displayId.
+ * <p>
+ * Note that this doesn't re-parent previously attached windows (they should be removed and
+ * re-added manually after this is called). Resources associated with this context will have
+ * the correct value and configuration for the new display after this is called.
+ */
+ @Override
+ public void updateDisplay(int displayId) {
+ if (displayId == getDisplayId()) {
+ return;
+ }
+ super.updateDisplay(displayId);
+ mController.detachIfNeeded();
+ mController.attachToDisplayArea(mType, displayId, mOptions);
+ }
+
@Override
public Object getSystemService(String name) {
if (WINDOW_SERVICE.equals(name)) {
diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml
index 4eac6d9..a790e5d 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_base.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml
@@ -123,7 +123,7 @@
<!-- This is the simplest way to keep this text vertically centered without
gravity="center_vertical" which causes jumpiness in expansion animations. -->
<include
- layout="@layout/notification_2025_template_text"
+ layout="@layout/notification_2025_text"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_text_height"
android:layout_gravity="center_vertical"
diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml
new file mode 100644
index 0000000..427c4e4
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml
@@ -0,0 +1,197 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2014 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
+ -->
+
+<!-- Note: This is the old media style notification (different from UMO). -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.MediaNotificationView
+ android:id="@+id/status_bar_latest_event_content"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_min_height"
+ android:tag="media"
+ >
+
+
+ <ImageView
+ android:id="@+id/left_icon"
+ android:layout_width="@dimen/notification_2025_left_icon_size"
+ android:layout_height="@dimen/notification_2025_left_icon_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_left_icon_start"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ android:visibility="gone"
+ />
+
+ <com.android.internal.widget.NotificationRowIconView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/notification_2025_icon_circle_size"
+ android:layout_height="@dimen/notification_2025_icon_circle_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:background="@drawable/notification_icon_circle"
+ android:padding="@dimen/notification_2025_icon_circle_padding"
+ />
+
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_2025_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ android:focusable="false"
+ />
+
+ <LinearLayout
+ android:id="@+id/notification_headerless_view_row"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:orientation="horizontal"
+ >
+
+ <LinearLayout
+ android:id="@+id/notification_headerless_view_column"
+ android:layout_width="0px"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
+ android:orientation="vertical"
+ >
+
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_headerless_line_height"
+ android:clipChildren="false"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <!--
+ NOTE: The notification_top_line_views layout contains the app_name_text.
+ In order to include the title view at the beginning, the Notification.Builder
+ has logic to hide that view whenever this title view is to be visible.
+ -->
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ />
+
+ <include layout="@layout/notification_top_line_views" />
+
+ </NotificationTopLineView>
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ >
+
+ <com.android.internal.widget.NotificationVanishingFrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ >
+ <!-- This is the simplest way to keep this text vertically centered without
+ gravity="center_vertical" which causes jumpiness in expansion animations. -->
+ <include
+ layout="@layout/notification_template_text"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_text_height"
+ android:layout_gravity="center_vertical"
+ android:layout_marginTop="0dp"
+ />
+ </com.android.internal.widget.NotificationVanishingFrameLayout>
+
+ <include
+ layout="@layout/notification_template_progress"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/right_icon"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ />
+
+ <LinearLayout
+ android:id="@+id/media_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layoutDirection="ltr"
+ android:orientation="horizontal"
+ >
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action0"
+ />
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action1"
+ />
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action2"
+ />
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
+
+ </FrameLayout>
+
+ </LinearLayout>
+</com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
new file mode 100644
index 0000000..f0e4c0f
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -0,0 +1,220 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 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
+ -->
+
+<!-- Note: This is the old "Messaging Style" notification (not a conversation). -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.MessagingLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:tag="messaging"
+ >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:orientation="vertical"
+ >
+
+
+ <com.android.internal.widget.NotificationMaxHeightFrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_min_height"
+ android:clipChildren="false"
+ >
+
+ <ImageView
+ android:id="@+id/left_icon"
+ android:layout_width="@dimen/notification_2025_left_icon_size"
+ android:layout_height="@dimen/notification_2025_left_icon_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_left_icon_start"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ android:visibility="gone"
+ />
+
+ <com.android.internal.widget.NotificationRowIconView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/notification_2025_icon_circle_size"
+ android:layout_height="@dimen/notification_2025_icon_circle_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:background="@drawable/notification_icon_circle"
+ android:padding="@dimen/notification_2025_icon_circle_padding"
+ />
+
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_2025_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ android:focusable="false"
+ />
+
+ <!--
+ NOTE: to make the expansion animation of id/notification_messaging happen vertically,
+ its X positioning must be the left edge of the notification, so instead of putting the
+ layout_marginStart on the id/notification_headerless_view_row, we put it on
+ id/notification_top_line, making the layout here just a bit different from the base.
+ -->
+ <LinearLayout
+ android:id="@+id/notification_headerless_view_row"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:clipChildren="false"
+ >
+
+ <!--
+ NOTE: because messaging will always have 2 lines, this LinearLayout should NOT
+ have the id/notification_headerless_view_column, as that is used for modifying
+ vertical margins to accommodate the single-line state that base supports
+ -->
+ <LinearLayout
+ android:layout_width="0px"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:clipChildren="false"
+ android:orientation="vertical"
+ >
+
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_headerless_line_height"
+ android:clipChildren="false"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <!--
+ NOTE: The notification_top_line_views layout contains the app_name_text.
+ In order to include the title view at the beginning, the Notification.Builder
+ has logic to hide that view whenever this title view is to be visible.
+ -->
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ />
+
+ <include layout="@layout/notification_top_line_views" />
+
+ </NotificationTopLineView>
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ >
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/notification_messaging"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:spacing="@dimen/notification_messaging_spacing" />
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <!-- Images -->
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/conversation_image_message_container"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:forceHasOverlappingRendering="false"
+ android:spacing="0dp"
+ android:clipChildren="false"
+ android:visibility="gone"
+ />
+
+ <ImageView
+ android:id="@+id/right_icon"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ />
+
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+ </com.android.internal.widget.NotificationMaxHeightFrameLayout>
+
+ <LinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="-20dp"
+ android:clipChildren="false"
+ android:orientation="vertical">
+ <include layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end" />
+ <include layout="@layout/notification_material_action_list" />
+ </LinearLayout>
+</LinearLayout>
+</com.android.internal.widget.MessagingLayout>
diff --git a/core/res/res/layout/notification_2025_template_text.xml b/core/res/res/layout/notification_2025_text.xml
similarity index 100%
rename from core/res/res/layout/notification_2025_template_text.xml
rename to core/res/res/layout/notification_2025_text.xml
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8ab6632..0622d72 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2393,6 +2393,8 @@
<java-symbol type="layout" name="notification_2025_template_expanded_base" />
<java-symbol type="layout" name="notification_2025_template_heads_up_base" />
<java-symbol type="layout" name="notification_2025_template_header" />
+ <java-symbol type="layout" name="notification_2025_template_collapsed_messaging" />
+ <java-symbol type="layout" name="notification_2025_template_collapsed_media" />
<java-symbol type="layout" name="notification_template_material_base" />
<java-symbol type="layout" name="notification_template_material_heads_up_base" />
<java-symbol type="layout" name="notification_template_material_compact_heads_up_base" />
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_ltr.9.png b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_ltr.9.png
new file mode 100644
index 0000000..c7e937c
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_ltr.9.png
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_tr.9.png b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_tr.9.png
new file mode 100644
index 0000000..a3cff98
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_tr.9.png
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/power_other_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/power_other_24.xml
new file mode 100644
index 0000000..c9fb53e
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/power_other_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M720,600L720,520L800,520Q817,520 828.5,531.5Q840,543 840,560Q840,577 828.5,588.5Q817,600 800,600L720,600ZM720,760L720,680L800,680Q817,680 828.5,691.5Q840,703 840,720Q840,737 828.5,748.5Q817,760 800,760L720,760ZM560,800Q527,800 503.5,776.5Q480,753 480,720L400,720L400,560L480,560Q480,527 503.5,503.5Q527,480 560,480L680,480L680,800L560,800ZM280,680Q214,680 167,633Q120,586 120,520Q120,454 167,407Q214,360 280,360L340,360Q365,360 382.5,342.5Q400,325 400,300Q400,275 382.5,257.5Q365,240 340,240L200,240Q183,240 171.5,228.5Q160,217 160,200Q160,183 171.5,171.5Q183,160 200,160L340,160Q398,160 439,201Q480,242 480,300Q480,358 439,399Q398,440 340,440L280,440Q247,440 223.5,463.5Q200,487 200,520Q200,553 223.5,576.5Q247,600 280,600L360,600L360,680L280,680Z" />
+</vector>
\ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_off_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_off_24.xml
new file mode 100644
index 0000000..ca94825
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_off_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M280,920Q247,920 223.5,896.5Q200,873 200,840L200,120Q200,87 223.5,63.5Q247,40 280,40L680,40Q713,40 736.5,63.5Q760,87 760,120L760,840Q760,873 736.5,896.5Q713,920 680,920L280,920ZM280,800L280,840Q280,840 280,840Q280,840 280,840L680,840Q680,840 680,840Q680,840 680,840L680,800L280,800ZM280,720L680,240L280,720ZM280,160L680,160L680,120Q680,120 680,120Q680,120 680,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,160L280,120Q280,120 280,120Q280,120 280,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,800L280,800L280,840Q280,840 280,840Q280,840 280,840L280,840Q280,840 280,840Q280,840 280,840L280,800Z" />
+</vector>
\ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_on_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_on_24.xml
new file mode 100644
index 0000000..48f990c
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_on_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M280,920Q247,920 223.5,896.5Q200,873 200,840L200,120Q200,87 223.5,63.5Q247,40 280,40L680,40Q713,40 736.5,63.5Q760,87 760,120L760,840Q760,873 736.5,896.5Q713,920 680,920L280,920ZM280,800L280,840Q280,840 280,840Q280,840 280,840L680,840Q680,840 680,840Q680,840 680,840L680,800L280,800ZM280,720L680,720L680,240L280,240L280,720ZM280,160L680,160L680,120Q680,120 680,120Q680,120 680,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,160L280,120Q280,120 280,120Q280,120 280,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,800L280,800L280,840Q280,840 280,840Q280,840 280,840L280,840Q280,840 280,840Q280,840 280,840L280,800Z" />
+</vector>
\ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml
index be0e135..e1f4623 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,46 +13,127 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingTop="8dp"
- android:paddingBottom="8dp">
+ android:orientation="vertical">
- <ImageView
- android:id="@+id/icon"
- android:layout_width="wrap_content"
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginEnd="8dp"
- android:paddingBottom="8dp"/>
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:orientation="horizontal"
+ android:paddingBottom="8dp"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingTop="8dp">
- <TextView
- android:id="@+id/title"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearanceBody"/>
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="8dp"
+ android:paddingBottom="8dp" />
- <TextView
- android:id="@+id/value1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="8dp"
- android:gravity="right"
- android:maxLines="1"
- android:textAppearance="@style/TextAppearanceBody"/>
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="@style/TextAppearanceBody" />
- <TextView
- android:id="@+id/value2"
- android:layout_width="76dp"
+ <TextView
+ android:id="@+id/value1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:gravity="right"
+ android:maxLines="1"
+ android:textAppearance="@style/TextAppearanceBody" />
+
+ <TextView
+ android:id="@+id/value2"
+ android:layout_width="76dp"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:maxLines="1"
+ android:textAppearance="@style/TextAppearanceBody" />
+ </LinearLayout>
+
+ <TableLayout
+ android:id="@+id/table"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="right"
- android:maxLines="1"
- android:textAppearance="@style/TextAppearanceBody"/>
+ android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:layout_marginStart="50dp"
+ android:stretchColumns="1,2,3,4">
+
+ <TableRow android:background="#EEFFEE">
+ <LinearLayout
+ style="@style/TableCell.Start"
+ android:layout_width="65dp">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="3dip"
+ android:text="State"
+ android:textStyle="bold" />
+ </LinearLayout>
+
+ <RelativeLayout style="@style/TableCell.Inner">
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:src="@drawable/screen_on_24"
+ android:tint="@color/battery_consumer_slice_icon" />
+ </RelativeLayout>
+
+ <RelativeLayout style="@style/TableCell.Inner">
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:src="@drawable/screen_off_24"
+ android:tint="@color/battery_consumer_slice_icon" />
+ </RelativeLayout>
+
+ <RelativeLayout style="@style/TableCell.Inner">
+ <ImageView
+ android:id="@+id/screen_on_24_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:src="@drawable/screen_on_24"
+ android:tint="@color/battery_consumer_slice_icon" />
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/screen_on_24_icon"
+ android:src="@drawable/power_other_24"
+ android:tint="@color/battery_consumer_slice_icon" />
+ </RelativeLayout>
+
+ <RelativeLayout style="@style/TableCell.End">
+ <ImageView
+ android:id="@+id/screen_off_24_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:src="@drawable/screen_off_24"
+ android:tint="@color/battery_consumer_slice_icon" />
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/screen_off_24_icon"
+ android:src="@drawable/power_other_24"
+ android:tint="@color/battery_consumer_slice_icon" />
+ </RelativeLayout>
+ </TableRow>
+
+ <View
+ android:layout_height="1dip"
+ android:background="#000000" />
+ </TableLayout>
</LinearLayout>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml
index 987de6b..b88425a 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml
@@ -14,16 +14,30 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/swipe_refresh"
- android:paddingTop="?attr/actionBarSize"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:fitsSystemWindows="true">
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/list_view"
+ <androidx.appcompat.widget.Toolbar
+ android:id="@+id/toolbar"
android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ android:layout_height="?attr/actionBarSize"
+ android:background="?attr/colorPrimary"
+ android:elevation="4dp"
+ android:theme="@style/ThemeOverlay.AppCompat.ActionBar" />
-</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
+ <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+ android:id="@+id/swipe_refresh"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
+</LinearLayout>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_slices_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_slices_layout.xml
new file mode 100644
index 0000000..642c0de
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_slices_layout.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout style="@style/TableCell.Start">
+ <TextView
+ android:id="@+id/procState"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <LinearLayout
+ style="@style/TableCell.Inner"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/power_b_on"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ <TextView
+ android:id="@+id/duration_b_on"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ </LinearLayout>
+
+ <LinearLayout
+ style="@style/TableCell.Inner"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/power_b_off"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ <TextView
+ android:id="@+id/duration_b_off"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ </LinearLayout>
+
+ <LinearLayout
+ style="@style/TableCell.Inner"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/power_c_on"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ <TextView
+ android:id="@+id/duration_c_on"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ </LinearLayout>
+
+ <LinearLayout
+ style="@style/TableCell.End"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/power_c_off"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ <TextView
+ android:id="@+id/duration_c_off"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ </LinearLayout>
+</TableRow>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
index 2d276a5..46d8f04 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
@@ -17,13 +17,12 @@
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipe_refresh"
- android:paddingTop="?attr/actionBarSize"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
<LinearLayout
android:orientation="vertical"
- android:paddingTop="?attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="match_parent">
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
index 6cc70bd..1dc288a 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
@@ -18,4 +18,5 @@
<resources>
<color name="battery_consumer_bg_power_profile">#ffffff</color>
<color name="battery_consumer_bg_energy_consumption">#fff5eb</color>
+ <color name="battery_consumer_slice_icon">#aaaaaa</color>
</resources>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml
index fa30b2c..a298cc9 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml
@@ -17,11 +17,9 @@
-->
<resources>
- <style name="Theme" parent="Theme.MaterialComponents.Light">
+ <style name="Theme" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="colorPrimary">#34a853</item>
- <item name="android:windowActionBar">true</item>
- <item name="android:windowNoTitle">false</item>
- <item name="android:windowDrawsSystemBarBackgrounds">false</item>
+ <item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
</style>
<style name="LoadTestCardView" parent="Widget.MaterialComponents.CardView">
@@ -32,4 +30,25 @@
<item name="android:textColor">#000000</item>
<item name="android:textSize">18sp</item>
</style>
-</resources>
\ No newline at end of file
+
+ <style name="TableCell">
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:padding">4dp</item>
+ </style>
+
+ <style name="TableCell.Start" parent="TableCell">
+ <item name="android:background">@drawable/border_ltr</item>
+ </style>
+
+ <style name="TableCell.Inner" parent="TableCell">
+ <item name="android:background">@drawable/border_tr</item>
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_weight">1</item>
+ </style>
+
+ <style name="TableCell.End" parent="TableCell">
+ <item name="android:background">@drawable/border_tr</item>
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_weight">1</item>
+ </style>
+</resources>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
index f691a1b..35175a7 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
@@ -31,22 +31,16 @@
public static final String UID_BATTERY_CONSUMER_ID_PREFIX = "APP|";
public static final String AGGREGATE_BATTERY_CONSUMER_ID = "SYS|";
- enum EntryType {
- UID_TOTAL_POWER,
- UID_POWER_PROFILE,
- UID_POWER_PROFILE_PROCESS_STATE,
- UID_POWER_ENERGY_CONSUMPTION,
- UID_POWER_ENERGY_PROCESS_STATE,
- UID_POWER_CUSTOM,
- UID_DURATION,
+ public enum EntryType {
DEVICE_TOTAL_POWER,
- DEVICE_POWER_MODELED,
+ DEVICE_POWER,
DEVICE_POWER_ENERGY_CONSUMPTION,
DEVICE_POWER_CUSTOM,
DEVICE_DURATION,
+ UID,
}
- enum ConsumerType {
+ public enum ConsumerType {
UID_BATTERY_CONSUMER,
DEVICE_POWER_COMPONENT,
}
@@ -56,34 +50,38 @@
public String title;
public double value1;
public double value2;
+ public List<Slice> slices;
+ }
+
+ public static class Slice {
+ public int powerState;
+ public int screenState;
+ public int processState;
+ public double powerMah;
+ public long durationMs;
}
private BatteryConsumerInfoHelper.BatteryConsumerInfo mBatteryConsumerInfo;
private final List<Entry> mEntries = new ArrayList<>();
public BatteryConsumerData(Context context,
- List<BatteryUsageStats> batteryUsageStatsList, String batteryConsumerId) {
+ BatteryUsageStats batteryUsageStats, String batteryConsumerId) {
switch (getConsumerType(batteryConsumerId)) {
case UID_BATTERY_CONSUMER:
- populateForUidBatteryConsumer(context, batteryUsageStatsList, batteryConsumerId);
+ populateForUidBatteryConsumer(context, batteryUsageStats, batteryConsumerId);
break;
case DEVICE_POWER_COMPONENT:
- populateForAggregateBatteryConsumer(context, batteryUsageStatsList);
+ populateForAggregateBatteryConsumer(context, batteryUsageStats);
break;
}
}
- private void populateForUidBatteryConsumer(
- Context context, List<BatteryUsageStats> batteryUsageStatsList,
+ private void populateForUidBatteryConsumer(Context context, BatteryUsageStats batteryUsageStats,
String batteryConsumerId) {
- BatteryUsageStats batteryUsageStats = batteryUsageStatsList.get(0);
- BatteryUsageStats modeledBatteryUsageStats = batteryUsageStatsList.get(1);
BatteryConsumer requestedBatteryConsumer = getRequestedBatteryConsumer(batteryUsageStats,
batteryConsumerId);
- BatteryConsumer requestedModeledBatteryConsumer = getRequestedBatteryConsumer(
- modeledBatteryUsageStats, batteryConsumerId);
- if (requestedBatteryConsumer == null || requestedModeledBatteryConsumer == null) {
+ if (requestedBatteryConsumer == null) {
mBatteryConsumerInfo = null;
return;
}
@@ -92,118 +90,95 @@
batteryUsageStats, batteryConsumerId, context.getPackageManager());
double[] totalPowerByComponentMah = new double[BatteryConsumer.POWER_COMPONENT_COUNT];
- double[] totalModeledPowerByComponentMah =
- new double[BatteryConsumer.POWER_COMPONENT_COUNT];
long[] totalDurationByComponentMs = new long[BatteryConsumer.POWER_COMPONENT_COUNT];
- final int customComponentCount =
- requestedBatteryConsumer.getCustomPowerComponentCount();
+ final int customComponentCount = requestedBatteryConsumer.getCustomPowerComponentCount();
double[] totalCustomPowerByComponentMah = new double[customComponentCount];
computeTotalPower(batteryUsageStats, totalPowerByComponentMah);
- computeTotalPower(modeledBatteryUsageStats, totalModeledPowerByComponentMah);
computeTotalPowerForCustomComponent(batteryUsageStats, totalCustomPowerByComponentMah);
computeTotalDuration(batteryUsageStats, totalDurationByComponentMs);
- if (isPowerProfileModelsOnly(requestedBatteryConsumer)) {
- addEntry("Consumed", EntryType.UID_TOTAL_POWER,
- requestedBatteryConsumer.getConsumedPower(),
- batteryUsageStats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
- .getConsumedPower());
- } else {
- addEntry("Consumed (PowerStats)", EntryType.UID_TOTAL_POWER,
- requestedBatteryConsumer.getConsumedPower(),
- batteryUsageStats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
- .getConsumedPower());
- addEntry("Consumed (PowerProfile)", EntryType.UID_TOTAL_POWER,
- requestedModeledBatteryConsumer.getConsumedPower(),
- modeledBatteryUsageStats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
- .getConsumedPower());
+ Entry totalsEntry = addEntry("Consumed", EntryType.UID,
+ requestedBatteryConsumer.getConsumedPower(),
+ batteryUsageStats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .getConsumedPower());
+ addSlices(totalsEntry, requestedBatteryConsumer, BatteryConsumer.POWER_COMPONENT_BASE);
+ for (Slice slice : totalsEntry.slices) {
+ slice.powerMah = requestedBatteryConsumer.getConsumedPower(
+ new BatteryConsumer.Dimensions(BatteryConsumer.POWER_COMPONENT_ANY,
+ slice.processState, slice.screenState, slice.powerState));
}
for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
+ if (component == BatteryConsumer.POWER_COMPONENT_BASE) {
+ continue;
+ }
final String metricTitle = getPowerMetricTitle(component);
- final int powerModel = requestedBatteryConsumer.getPowerModel(component);
- if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE
- || powerModel == BatteryConsumer.POWER_MODEL_UNDEFINED) {
- addEntry(metricTitle, EntryType.UID_POWER_PROFILE,
- requestedBatteryConsumer.getConsumedPower(component),
+ double consumedPower = requestedBatteryConsumer.getConsumedPower(component);
+ if (consumedPower != 0) {
+ Entry entry = addEntry(metricTitle, EntryType.UID, consumedPower,
totalPowerByComponentMah[component]);
- addProcessStateEntries(metricTitle, EntryType.UID_POWER_PROFILE_PROCESS_STATE,
- requestedBatteryConsumer, component);
- } else {
- addEntry(metricTitle + " (PowerStats)", EntryType.UID_POWER_ENERGY_CONSUMPTION,
- requestedBatteryConsumer.getConsumedPower(component),
- totalPowerByComponentMah[component]);
- addProcessStateEntries(metricTitle, EntryType.UID_POWER_ENERGY_PROCESS_STATE,
- requestedBatteryConsumer, component);
- addEntry(metricTitle + " (PowerProfile)", EntryType.UID_POWER_PROFILE,
- requestedModeledBatteryConsumer.getConsumedPower(component),
- totalModeledPowerByComponentMah[component]);
- addProcessStateEntries(metricTitle, EntryType.UID_POWER_PROFILE_PROCESS_STATE,
- requestedModeledBatteryConsumer, component);
+ addSlices(entry, requestedBatteryConsumer, component);
}
}
for (int component = 0; component < customComponentCount; component++) {
- final String name = requestedBatteryConsumer.getCustomPowerComponentName(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
- addEntry(name + " (PowerStats)", EntryType.UID_POWER_CUSTOM,
- requestedBatteryConsumer.getConsumedPowerForCustomComponent(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component),
- totalCustomPowerByComponentMah[component]
- );
- }
-
- for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
- final String metricTitle = getTimeMetricTitle(component);
- addEntry(metricTitle, EntryType.UID_DURATION,
- requestedBatteryConsumer.getUsageDurationMillis(component),
- totalDurationByComponentMs[component]
- );
+ int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component;
+ final String name = requestedBatteryConsumer.getCustomPowerComponentName(componentId);
+ double consumedPower = requestedBatteryConsumer.getConsumedPower(componentId);
+ if (consumedPower != 0) {
+ Entry entry = addEntry(name, EntryType.UID, consumedPower,
+ totalCustomPowerByComponentMah[component]);
+ addSlices(entry, requestedBatteryConsumer, componentId);
+ }
}
mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo(batteryUsageStats,
batteryConsumerId, context.getPackageManager());
}
- private void addProcessStateEntries(String metricTitle, EntryType entryType,
- BatteryConsumer batteryConsumer, int component) {
+ private void addSlices(Entry entry, BatteryConsumer batteryConsumer, int component) {
final BatteryConsumer.Key[] keys = batteryConsumer.getKeys(component);
if (keys == null || keys.length <= 1) {
return;
}
+ boolean hasProcStateData = false;
for (BatteryConsumer.Key key : keys) {
- String label;
- switch (key.processState) {
- case BatteryConsumer.PROCESS_STATE_FOREGROUND:
- label = "foreground";
- break;
- case BatteryConsumer.PROCESS_STATE_BACKGROUND:
- label = "background";
- break;
- case BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE:
- label = "FGS";
- break;
- case BatteryConsumer.PROCESS_STATE_CACHED:
- label = "cached";
- break;
- default:
- continue;
+ if (key.processState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+ hasProcStateData = true;
+ break;
}
- addEntry(metricTitle + " \u2022 " + label, entryType,
- batteryConsumer.getConsumedPower(key), 0);
}
+
+ ArrayList<Slice> slices = new ArrayList<>();
+ for (BatteryConsumer.Key key : keys) {
+ if (hasProcStateData && key.processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+ continue;
+ }
+
+ double powerMah = batteryConsumer.getConsumedPower(key);
+ long durationMs = batteryConsumer.getUsageDurationMillis(key);
+
+ if (powerMah == 0 && durationMs == 0) {
+ continue;
+ }
+
+ Slice slice = new Slice();
+ slice.powerState = key.powerState;
+ slice.screenState = key.screenState;
+ slice.processState = key.processState;
+ slice.powerMah = powerMah;
+ slice.durationMs = durationMs;
+
+ slices.add(slice);
+ }
+ entry.slices = slices;
}
private void populateForAggregateBatteryConsumer(Context context,
- List<BatteryUsageStats> batteryUsageStatsList) {
- BatteryUsageStats batteryUsageStats = batteryUsageStatsList.get(0);
- BatteryUsageStats modeledBatteryUsageStats = batteryUsageStatsList.get(1);
-
+ BatteryUsageStats batteryUsageStats) {
final BatteryConsumer deviceBatteryConsumer =
batteryUsageStats.getAggregateBatteryConsumer(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
@@ -211,46 +186,18 @@
batteryUsageStats.getAggregateBatteryConsumer(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
- BatteryConsumer modeledDeviceBatteryConsumer =
- modeledBatteryUsageStats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
- BatteryConsumer modeledAppsBatteryConsumer =
- modeledBatteryUsageStats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
-
- if (isPowerProfileModelsOnly(deviceBatteryConsumer)) {
- addEntry("Consumed", EntryType.DEVICE_TOTAL_POWER,
- deviceBatteryConsumer.getConsumedPower(),
- appsBatteryConsumer.getConsumedPower());
- } else {
- addEntry("Consumed (PowerStats)", EntryType.DEVICE_TOTAL_POWER,
- deviceBatteryConsumer.getConsumedPower(),
- appsBatteryConsumer.getConsumedPower());
- addEntry("Consumed (PowerProfile)", EntryType.DEVICE_TOTAL_POWER,
- modeledDeviceBatteryConsumer.getConsumedPower(),
- modeledAppsBatteryConsumer.getConsumedPower());
- }
+ addEntry("Consumed", EntryType.DEVICE_TOTAL_POWER,
+ deviceBatteryConsumer.getConsumedPower(),
+ appsBatteryConsumer.getConsumedPower());
mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo(batteryUsageStats,
AGGREGATE_BATTERY_CONSUMER_ID, context.getPackageManager());
-
for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
final String metricTitle = getPowerMetricTitle(component);
- final int powerModel = deviceBatteryConsumer.getPowerModel(component);
- if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE
- || powerModel == BatteryConsumer.POWER_MODEL_UNDEFINED) {
- addEntry(metricTitle, EntryType.DEVICE_POWER_MODELED,
- deviceBatteryConsumer.getConsumedPower(component),
- appsBatteryConsumer.getConsumedPower(component));
- } else {
- addEntry(metricTitle + " (PowerStats)", EntryType.DEVICE_POWER_ENERGY_CONSUMPTION,
- deviceBatteryConsumer.getConsumedPower(component),
- appsBatteryConsumer.getConsumedPower(component));
- addEntry(metricTitle + " (PowerProfile)", EntryType.DEVICE_POWER_MODELED,
- modeledDeviceBatteryConsumer.getConsumedPower(component),
- modeledAppsBatteryConsumer.getConsumedPower(component));
- }
+ addEntry(metricTitle, EntryType.DEVICE_POWER,
+ deviceBatteryConsumer.getConsumedPower(component),
+ appsBatteryConsumer.getConsumedPower(component));
}
final int customComponentCount =
@@ -258,10 +205,10 @@
for (int component = 0; component < customComponentCount; component++) {
final String name = deviceBatteryConsumer.getCustomPowerComponentName(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
- addEntry(name + " (PowerStats)", EntryType.DEVICE_POWER_CUSTOM,
- deviceBatteryConsumer.getConsumedPowerForCustomComponent(
+ addEntry(name, EntryType.DEVICE_POWER_CUSTOM,
+ deviceBatteryConsumer.getConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component),
- appsBatteryConsumer.getConsumedPowerForCustomComponent(
+ appsBatteryConsumer.getConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component));
}
@@ -272,17 +219,6 @@
}
}
- private boolean isPowerProfileModelsOnly(BatteryConsumer batteryConsumer) {
- for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
- final int powerModel = batteryConsumer.getPowerModel(component);
- if (powerModel != BatteryConsumer.POWER_MODEL_POWER_PROFILE
- && powerModel != BatteryConsumer.POWER_MODEL_UNDEFINED) {
- return false;
- }
- }
- return true;
- }
-
private BatteryConsumer getRequestedBatteryConsumer(BatteryUsageStats batteryUsageStats,
String batteryConsumerId) {
for (UidBatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
@@ -352,13 +288,14 @@
}
}
- private void addEntry(String title, EntryType entryType, double value1, double value2) {
+ private Entry addEntry(String title, EntryType entryType, double value1, double value2) {
Entry entry = new Entry();
entry.title = title;
entry.entryType = entryType;
entry.value1 = value1;
entry.value2 = value2;
mEntries.add(entry);
+ return entry;
}
public BatteryConsumerInfoHelper.BatteryConsumerInfo getBatteryConsumerInfo() {
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
index c6d71c3..37d6b17 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
@@ -24,6 +24,8 @@
import androidx.annotation.NonNull;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
import java.util.List;
class BatteryConsumerInfoHelper {
@@ -76,6 +78,8 @@
String packageWithHighestDrain = uidBatteryConsumer.getPackageWithHighestDrain();
if (uid == Process.ROOT_UID) {
info.label = "<root>";
+ } else if (uid < Process.FIRST_APPLICATION_UID) {
+ info.label = makeSystemUidLabel(uid);
} else {
String[] packages = packageManager.getPackagesForUid(uid);
String primaryPackageName = null;
@@ -134,6 +138,23 @@
return info;
}
+ private static CharSequence makeSystemUidLabel(int uid) {
+ for (Field field : Process.class.getDeclaredFields()) {
+ final int modifiers = field.getModifiers();
+ if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+ && field.getType().equals(int.class) && field.getName().endsWith("_UID")) {
+ try {
+ if (uid == field.getInt(null)) {
+ String label = field.getName();
+ return label.substring(0, label.lastIndexOf("_UID"));
+ }
+ } catch (IllegalAccessException ignored) {
+ }
+ }
+ }
+ return null;
+ }
+
private static BatteryConsumerInfo makeAggregateBatteryConsumerInfo(
BatteryUsageStats batteryUsageStats) {
BatteryConsumerInfo info = new BatteryConsumerInfo();
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java
index 4469168..3699690 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java
@@ -30,8 +30,8 @@
import android.widget.ImageView;
import android.widget.TextView;
-import androidx.activity.ComponentActivity;
import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -50,7 +50,7 @@
* Picker, showing a sorted lists of applications and other types of entities consuming power.
* Opens BatteryStatsViewerActivity upon item selection.
*/
-public class BatteryConsumerPickerActivity extends ComponentActivity {
+public class BatteryConsumerPickerActivity extends AppCompatActivity {
private static final String PREF_SELECTED_BATTERY_CONSUMER = "batteryConsumerId";
private static final int BATTERY_STATS_REFRESH_RATE_MILLIS = 60 * 1000;
private static final String FORCE_FRESH_STATS = "force_fresh_stats";
@@ -68,6 +68,7 @@
super.onCreate(icicle);
setContentView(R.layout.battery_consumer_picker_layout);
+ setSupportActionBar(findViewById(R.id.toolbar));
mSwipeRefreshLayout = findViewById(R.id.swipe_refresh);
mSwipeRefreshLayout.setColorSchemeResources(android.R.color.holo_green_light);
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
index e165c49..35021316 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
@@ -17,14 +17,18 @@
package com.android.frameworks.core.batterystatsviewer;
import android.content.Context;
+import android.os.BatteryConsumer;
import android.os.BatteryStatsManager;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.Bundle;
+import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+import android.widget.TableLayout;
+import android.widget.TableRow;
import android.widget.TextView;
import android.widget.Toast;
@@ -40,6 +44,7 @@
import com.android.settingslib.utils.AsyncLoaderCompat;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -63,7 +68,16 @@
private SwipeRefreshLayout mSwipeRefreshLayout;
private View mCardView;
private View mEmptyView;
- private List<BatteryUsageStats> mBatteryUsageStats;
+ private BatteryUsageStats mBatteryUsageStats;
+
+ private static SparseArray<String> sProcStateNames = new SparseArray<>();
+ static {
+ sProcStateNames.put(BatteryConsumer.PROCESS_STATE_UNSPECIFIED, "-");
+ sProcStateNames.put(BatteryConsumer.PROCESS_STATE_FOREGROUND, "FG");
+ sProcStateNames.put(BatteryConsumer.PROCESS_STATE_BACKGROUND, "BG");
+ sProcStateNames.put(BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, "FGS");
+ sProcStateNames.put(BatteryConsumer.PROCESS_STATE_CACHED, "Cached");
+ }
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -122,7 +136,7 @@
}
private static class BatteryUsageStatsLoader extends
- AsyncLoaderCompat<List<BatteryUsageStats>> {
+ AsyncLoaderCompat<BatteryUsageStats> {
private final BatteryStatsManager mBatteryStatsManager;
private final boolean mForceFreshStats;
@@ -133,51 +147,44 @@
}
@Override
- public List<BatteryUsageStats> loadInBackground() {
+ public BatteryUsageStats loadInBackground() {
final int maxStatsAgeMs = mForceFreshStats ? 0 : BATTERY_STATS_REFRESH_RATE_MILLIS;
final BatteryUsageStatsQuery queryDefault =
new BatteryUsageStatsQuery.Builder()
- .includePowerModels()
.includeProcessStateData()
+ .includeScreenStateData()
+ .includePowerStateData()
.setMaxStatsAgeMs(maxStatsAgeMs)
.build();
- final BatteryUsageStatsQuery queryPowerProfileModeledOnly =
- new BatteryUsageStatsQuery.Builder()
- .powerProfileModeledOnly()
- .includePowerModels()
- .includeProcessStateData()
- .setMaxStatsAgeMs(maxStatsAgeMs)
- .build();
- return mBatteryStatsManager.getBatteryUsageStats(
- List.of(queryDefault, queryPowerProfileModeledOnly));
+ return mBatteryStatsManager.getBatteryUsageStats(queryDefault);
}
@Override
- protected void onDiscardResult(List<BatteryUsageStats> result) {
+ protected void onDiscardResult(BatteryUsageStats result) {
}
}
private class BatteryUsageStatsLoaderCallbacks
- implements LoaderCallbacks<List<BatteryUsageStats>> {
+ implements LoaderCallbacks<BatteryUsageStats> {
@NonNull
@Override
- public Loader<List<BatteryUsageStats>> onCreateLoader(int id, Bundle args) {
+ public Loader<BatteryUsageStats> onCreateLoader(int id, Bundle args) {
return new BatteryUsageStatsLoader(BatteryStatsViewerActivity.this,
args.getBoolean(FORCE_FRESH_STATS));
}
@Override
- public void onLoadFinished(@NonNull Loader<List<BatteryUsageStats>> loader,
- List<BatteryUsageStats> batteryUsageStats) {
+ public void onLoadFinished(@NonNull Loader<BatteryUsageStats> loader,
+ BatteryUsageStats batteryUsageStats) {
onBatteryUsageStatsLoaded(batteryUsageStats);
}
@Override
- public void onLoaderReset(@NonNull Loader<List<BatteryUsageStats>> loader) {
+ public void onLoaderReset(@NonNull Loader<BatteryUsageStats> loader) {
}
}
- private void onBatteryUsageStatsLoaded(List<BatteryUsageStats> batteryUsageStats) {
+ private void onBatteryUsageStatsLoaded(BatteryUsageStats batteryUsageStats) {
mBatteryUsageStats = batteryUsageStats;
onBatteryStatsDataLoaded();
}
@@ -238,10 +245,21 @@
private static class BatteryStatsDataAdapter extends
RecyclerView.Adapter<BatteryStatsDataAdapter.ViewHolder> {
public static class ViewHolder extends RecyclerView.ViewHolder {
+ public static class SliceViewHolder {
+ public TableRow tableRow;
+ public int procState;
+ public int powerState;
+ public int screenState;
+ public TextView powerTextView;
+ public TextView durationTextView;
+ }
+
public ImageView iconImageView;
public TextView titleTextView;
public TextView value1TextView;
public TextView value2TextView;
+ public TableLayout table;
+ public List<SliceViewHolder> slices = new ArrayList<>();
ViewHolder(View itemView) {
super(itemView);
@@ -250,6 +268,40 @@
titleTextView = itemView.findViewById(R.id.title);
value1TextView = itemView.findViewById(R.id.value1);
value2TextView = itemView.findViewById(R.id.value2);
+ table = itemView.findViewById(R.id.table);
+
+ for (int i = 0; i < sProcStateNames.size(); i++) {
+ int procState = sProcStateNames.keyAt(i);
+ slices.add(createSliceViewHolder(procState,
+ BatteryConsumer.POWER_STATE_BATTERY,
+ BatteryConsumer.SCREEN_STATE_ON,
+ R.id.power_b_on, R.id.duration_b_on));
+ slices.add(createSliceViewHolder(procState,
+ BatteryConsumer.POWER_STATE_BATTERY,
+ BatteryConsumer.SCREEN_STATE_OTHER,
+ R.id.power_b_off, R.id.duration_b_off));
+ slices.add(createSliceViewHolder(procState,
+ BatteryConsumer.POWER_STATE_OTHER,
+ BatteryConsumer.SCREEN_STATE_ON,
+ R.id.power_c_on, R.id.duration_c_on));
+ slices.add(createSliceViewHolder(procState,
+ BatteryConsumer.POWER_STATE_OTHER,
+ BatteryConsumer.SCREEN_STATE_OTHER,
+ R.id.power_c_off, R.id.duration_c_off));
+ }
+ }
+
+ private SliceViewHolder createSliceViewHolder(int procState, int powerState,
+ int screenState, int powerTextViewResId, int durationTextViewResId) {
+ TableRow powerRow = table.findViewWithTag("procstate" + procState);
+ SliceViewHolder svh = new SliceViewHolder();
+ svh.tableRow = powerRow;
+ svh.procState = procState;
+ svh.powerState = powerState;
+ svh.screenState = screenState;
+ svh.powerTextView = powerRow.findViewById(powerTextViewResId);
+ svh.durationTextView = powerRow.findViewById(durationTextViewResId);
+ return svh;
}
}
@@ -269,62 +321,32 @@
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
- View itemView = layoutInflater.inflate(R.layout.battery_consumer_entry_layout, parent,
- false);
+ ViewGroup itemView = (ViewGroup) layoutInflater.inflate(
+ R.layout.battery_consumer_entry_layout, parent, false);
+ TableLayout table = itemView.findViewById(R.id.table);
+ int offset = 1; // Skip header
+ for (int i = 0; i < sProcStateNames.size(); i++) {
+ View powerRow = layoutInflater.inflate(R.layout.battery_consumer_slices_layout,
+ itemView, false);
+ ((TextView) powerRow.findViewById(R.id.procState))
+ .setText(sProcStateNames.valueAt(i));
+ powerRow.setTag("procstate" + sProcStateNames.keyAt(i));
+ table.addView(powerRow, offset++);
+ }
+
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
BatteryConsumerData.Entry entry = mEntries.get(position);
-
switch (entry.entryType) {
- case UID_TOTAL_POWER:
+ case UID:
setTitleIconAndBackground(viewHolder, entry.title,
- R.drawable.gm_sum_24, 0);
+ R.drawable.gm_energy_24, 0);
setPowerText(viewHolder.value1TextView, entry.value1);
setProportionText(viewHolder.value2TextView, entry);
- break;
- case UID_POWER_PROFILE:
- setTitleIconAndBackground(viewHolder, entry.title,
- R.drawable.gm_calculate_24,
- R.color.battery_consumer_bg_power_profile);
- setPowerText(viewHolder.value1TextView, entry.value1);
- setProportionText(viewHolder.value2TextView, entry);
- break;
- case UID_POWER_PROFILE_PROCESS_STATE:
- setTitleIconAndBackground(viewHolder, " " + entry.title,
- R.drawable.gm_calculate_24,
- R.color.battery_consumer_bg_power_profile);
- setPowerText(viewHolder.value1TextView, entry.value1);
- viewHolder.value2TextView.setVisibility(View.INVISIBLE);
- break;
- case UID_POWER_ENERGY_CONSUMPTION:
- setTitleIconAndBackground(viewHolder, entry.title,
- R.drawable.gm_energy_24,
- R.color.battery_consumer_bg_energy_consumption);
- setPowerText(viewHolder.value1TextView, entry.value1);
- setProportionText(viewHolder.value2TextView, entry);
- break;
- case UID_POWER_ENERGY_PROCESS_STATE:
- setTitleIconAndBackground(viewHolder, " " + entry.title,
- R.drawable.gm_energy_24,
- R.color.battery_consumer_bg_energy_consumption);
- setPowerText(viewHolder.value1TextView, entry.value1);
- viewHolder.value2TextView.setVisibility(View.INVISIBLE);
- break;
- case UID_POWER_CUSTOM:
- setTitleIconAndBackground(viewHolder, entry.title,
- R.drawable.gm_energy_24,
- R.color.battery_consumer_bg_energy_consumption);
- setPowerText(viewHolder.value1TextView, entry.value1);
- setProportionText(viewHolder.value2TextView, entry);
- break;
- case UID_DURATION:
- setTitleIconAndBackground(viewHolder, entry.title,
- R.drawable.gm_timer_24, 0);
- setDurationText(viewHolder.value1TextView, (long) entry.value1);
- setProportionText(viewHolder.value2TextView, entry);
+ bindSlices(viewHolder, entry);
break;
case DEVICE_TOTAL_POWER:
setTitleIconAndBackground(viewHolder, entry.title,
@@ -332,27 +354,13 @@
setPowerText(viewHolder.value1TextView, entry.value1);
setPowerText(viewHolder.value2TextView, entry.value2);
break;
- case DEVICE_POWER_MODELED:
+ case DEVICE_POWER:
setTitleIconAndBackground(viewHolder, entry.title,
R.drawable.gm_calculate_24,
R.color.battery_consumer_bg_power_profile);
setPowerText(viewHolder.value1TextView, entry.value1);
setPowerText(viewHolder.value2TextView, entry.value2);
break;
- case DEVICE_POWER_ENERGY_CONSUMPTION:
- setTitleIconAndBackground(viewHolder, entry.title,
- R.drawable.gm_energy_24,
- R.color.battery_consumer_bg_energy_consumption);
- setPowerText(viewHolder.value1TextView, entry.value1);
- setPowerText(viewHolder.value2TextView, entry.value2);
- break;
- case DEVICE_POWER_CUSTOM:
- setTitleIconAndBackground(viewHolder, entry.title,
- R.drawable.gm_energy_24,
- R.color.battery_consumer_bg_energy_consumption);
- setPowerText(viewHolder.value1TextView, entry.value1);
- setPowerText(viewHolder.value2TextView, entry.value2);
- break;
case DEVICE_DURATION:
setTitleIconAndBackground(viewHolder, entry.title,
R.drawable.gm_timer_24, 0);
@@ -362,6 +370,65 @@
}
}
+ private void bindSlices(ViewHolder viewHolder, BatteryConsumerData.Entry entry) {
+ if (entry.slices == null || entry.slices.isEmpty()) {
+ viewHolder.table.setVisibility(View.GONE);
+ return;
+ }
+ viewHolder.table.setVisibility(View.VISIBLE);
+
+ boolean[] procStateRowPopulated =
+ new boolean[BatteryConsumer.PROCESS_STATE_COUNT];
+ for (BatteryConsumerData.Slice s : entry.slices) {
+ if (s.powerMah != 0 || s.durationMs != 0) {
+ procStateRowPopulated[s.processState] = true;
+ }
+ }
+
+ for (ViewHolder.SliceViewHolder sliceViewHolder : viewHolder.slices) {
+ BatteryConsumerData.Slice slice = null;
+ for (BatteryConsumerData.Slice s : entry.slices) {
+ if (s.powerState == sliceViewHolder.powerState
+ && s.screenState == sliceViewHolder.screenState
+ && s.processState == sliceViewHolder.procState) {
+ slice = s;
+ break;
+ }
+ }
+ if (!procStateRowPopulated[sliceViewHolder.procState]) {
+ sliceViewHolder.tableRow.setVisibility(View.GONE);
+ } else {
+ sliceViewHolder.tableRow.setVisibility(View.VISIBLE);
+
+ if (slice != null && (slice.powerMah != 0 || slice.durationMs != 0)) {
+ sliceViewHolder.powerTextView.setText(
+ String.format(Locale.getDefault(), "%.1f", slice.powerMah));
+ } else {
+ sliceViewHolder.powerTextView.setText(null);
+ }
+
+ if (slice != null && slice.durationMs != 0) {
+ sliceViewHolder.durationTextView.setVisibility(View.VISIBLE);
+ String timeString;
+ if (slice.durationMs < MILLIS_IN_MINUTE) {
+ timeString = String.format(Locale.getDefault(), "%ds",
+ slice.durationMs / 1000);
+ } else if (slice.durationMs < 60 * MILLIS_IN_MINUTE) {
+ timeString = String.format(Locale.getDefault(), "%dm %ds",
+ slice.durationMs / MILLIS_IN_MINUTE,
+ (slice.durationMs % MILLIS_IN_MINUTE) / 1000);
+ } else {
+ timeString = String.format(Locale.getDefault(), "%dm",
+ slice.durationMs / MILLIS_IN_MINUTE);
+ }
+ sliceViewHolder.durationTextView.setText(timeString);
+ } else {
+ sliceViewHolder.durationTextView.setVisibility(View.GONE);
+ }
+ }
+ }
+ }
+
private void setTitleIconAndBackground(ViewHolder viewHolder, String title, int icon,
int background) {
viewHolder.titleTextView.setText(title);
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java
index b016488..412169e 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java
@@ -42,5 +42,6 @@
private void launchMainActivity() {
startActivity(new Intent(this, BatteryConsumerPickerActivity.class));
+ finish();
}
}
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index 2fc72e1..177c7f0 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -26,6 +26,7 @@
import static com.android.internal.os.Flags.FLAG_APPLICATION_SHARED_MEMORY_ENABLED;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
@@ -34,6 +35,8 @@
import android.annotation.SuppressLint;
import android.app.PropertyInvalidatedCache.Args;
+import android.app.PropertyInvalidatedCache.NonceWatcher;
+import android.app.PropertyInvalidatedCache.NonceStore;
import android.os.Binder;
import com.android.internal.os.ApplicationSharedMemory;
@@ -45,11 +48,15 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.os.ApplicationSharedMemory;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import java.util.concurrent.TimeUnit;
+
/**
* Test for verifying the behavior of {@link PropertyInvalidatedCache}. This test does
* not use any actual binder calls - it is entirely self-contained. This test also relies
@@ -490,6 +497,62 @@
}
}
+ // Verify that NonceWatcher change reporting works properly
+ @Test
+ public void testNonceWatcherChanged() {
+ // Create a cache that will write a system nonce.
+ TestCache sysCache = new TestCache(MODULE_SYSTEM, "watcher1");
+ sysCache.testPropertyName();
+
+ try (NonceWatcher watcher1 = sysCache.getNonceWatcher()) {
+
+ // The property has never been invalidated so it is still unset.
+ assertFalse(watcher1.isChanged());
+
+ // Invalidate the cache. The first call to isChanged will return true but the second
+ // call will return false;
+ sysCache.invalidateCache();
+ assertTrue(watcher1.isChanged());
+ assertFalse(watcher1.isChanged());
+
+ // Invalidate the cache. The first call to isChanged will return true but the second
+ // call will return false;
+ sysCache.invalidateCache();
+ sysCache.invalidateCache();
+ assertTrue(watcher1.isChanged());
+ assertFalse(watcher1.isChanged());
+
+ NonceWatcher watcher2 = sysCache.getNonceWatcher();
+ // This watcher return isChanged() immediately because the nonce is not UNSET.
+ assertTrue(watcher2.isChanged());
+ }
+ }
+
+ // Verify that NonceWatcher wait-for-change works properly
+ @Test
+ public void testNonceWatcherWait() throws Exception {
+ // Create a cache that will write a system nonce.
+ TestCache sysCache = new TestCache(MODULE_TEST, "watcher1");
+
+ // Use the watcher outside a try-with-resources block.
+ NonceWatcher watcher1 = sysCache.getNonceWatcher();
+
+ // Invalidate the cache and then "wait".
+ sysCache.invalidateCache();
+ assertEquals(watcher1.waitForChange(), 1);
+
+ // Invalidate the cache three times and then "wait".
+ sysCache.invalidateCache();
+ sysCache.invalidateCache();
+ sysCache.invalidateCache();
+ assertEquals(watcher1.waitForChange(), 3);
+
+ // Wait for a change. It won't happen, but the code will time out after 10ms.
+ assertEquals(watcher1.waitForChange(10, TimeUnit.MILLISECONDS), 0);
+
+ watcher1.close();
+ }
+
// Verify the behavior of shared memory nonce storage. This does not directly test the cache
// storing nonces in shared memory.
@RequiresFlagsEnabled(FLAG_APPLICATION_SHARED_MEMORY_ENABLED)
@@ -502,10 +565,8 @@
// Create a server-side store and a client-side store. The server's store is mutable and
// the client's store is not mutable.
- PropertyInvalidatedCache.NonceStore server =
- new PropertyInvalidatedCache.NonceStore(shmem.getSystemNonceBlock(), true);
- PropertyInvalidatedCache.NonceStore client =
- new PropertyInvalidatedCache.NonceStore(shmem.getSystemNonceBlock(), false);
+ NonceStore server = new NonceStore(shmem.getSystemNonceBlock(), true);
+ NonceStore client = new NonceStore(shmem.getSystemNonceBlock(), false);
final String name1 = "name1";
assertEquals(server.getHandleForName(name1), INVALID_NONCE_INDEX);
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index 4620cb8..c45080f 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -15,3 +15,6 @@
# RemoteCallbackList
per-file RemoteCallbackListTest.java = shayba@google.com
+
+# MessageQueue
+per-file MessageQueueTest.java = mfasheh@google.com, shayba@google.com
diff --git a/core/tests/coretests/src/android/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java
index f1fbd55..21930d1 100644
--- a/core/tests/coretests/src/android/window/WindowContextTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextTest.java
@@ -29,6 +29,11 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import android.app.Activity;
import android.app.EmptyActivity;
@@ -60,6 +65,8 @@
import com.android.frameworks.coretests.R;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -88,9 +95,21 @@
private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
private final WindowContext mWindowContext = createWindowContext();
private final IWindowManager mWms = WindowManagerGlobal.getWindowManagerService();
+ private WindowTokenClientController mOriginalWindowTokenClientController;
private static final int TIMEOUT_IN_SECONDS = 4;
+ @Before
+ public void setUp() {
+ // Keeping the original to set it back after each test, in case they applied any override.
+ mOriginalWindowTokenClientController = WindowTokenClientController.getInstance();
+ }
+
+ @After
+ public void tearDown() {
+ WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController);
+ }
+
@Test
public void testCreateWindowContextWindowManagerAttachClientToken() {
final WindowManager windowContextWm = WindowManagerImpl
@@ -320,6 +339,20 @@
}
}
+ @Test
+ public void updateDisplay_wasAttached_detachThenAttachedPropagatedToTokenController() {
+ final WindowTokenClientController mockWindowTokenClientController =
+ mock(WindowTokenClientController.class);
+ WindowTokenClientController.overrideForTesting(mockWindowTokenClientController);
+
+ mWindowContext.updateDisplay(DEFAULT_DISPLAY + 1);
+
+ verify(mockWindowTokenClientController).detachIfNeeded(any());
+ verify(mockWindowTokenClientController).attachToDisplayArea(any(),
+ anyInt(), /* displayId= */ eq(DEFAULT_DISPLAY + 1),
+ any());
+ }
+
private WindowContext createWindowContext() {
return createWindowContext(TYPE_APPLICATION_OVERLAY);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 8ee087b..8f02c1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -35,6 +35,7 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
+import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Point;
import android.graphics.PointF;
@@ -57,6 +58,7 @@
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.pip2.animation.PipEnterAnimator;
@@ -74,8 +76,8 @@
private static final String TAG = PipTransition.class.getSimpleName();
// Used when for ENTERING_PIP state update.
- private static final String PIP_TASK_TOKEN = "pip_task_token";
private static final String PIP_TASK_LEASH = "pip_task_leash";
+ private static final String PIP_TASK_INFO = "pip_task_info";
// Used for PiP CHANGING_BOUNDS state update.
static final String PIP_START_TX = "pip_start_tx";
@@ -245,8 +247,8 @@
// Update the PipTransitionState while supplying the PiP leash and token to be cached.
Bundle extra = new Bundle();
- extra.putParcelable(PIP_TASK_TOKEN, pipChange.getContainer());
extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash());
+ extra.putParcelable(PIP_TASK_INFO, pipChange.getTaskInfo());
mPipTransitionState.setState(PipTransitionState.ENTERING_PIP, extra);
if (isInSwipePipToHomeTransition()) {
@@ -899,10 +901,10 @@
Preconditions.checkState(extra != null,
"No extra bundle for " + mPipTransitionState);
- mPipTransitionState.setPipTaskToken(extra.getParcelable(
- PIP_TASK_TOKEN, WindowContainerToken.class));
mPipTransitionState.setPinnedTaskLeash(extra.getParcelable(
PIP_TASK_LEASH, SurfaceControl.class));
+ mPipTransitionState.setPipTaskInfo(extra.getParcelable(
+ PIP_TASK_INFO, TaskInfo.class));
boolean hasValidTokenAndLeash = mPipTransitionState.getPipTaskToken() != null
&& mPipTransitionState.getPinnedTaskLeash() != null;
@@ -915,16 +917,16 @@
mPipBoundsState.getBounds());
mPipBoundsState.saveReentryState(snapFraction);
- mPipTransitionState.setPipTaskToken(null);
mPipTransitionState.setPinnedTaskLeash(null);
+ mPipTransitionState.setPipTaskInfo(null);
break;
}
}
@Override
public boolean isPackageActiveInPip(@Nullable String packageName) {
- return packageName != null
- && mPipBoundsState.getLastPipComponentName() != null
- && packageName.equals(mPipBoundsState.getLastPipComponentName().getPackageName());
+ final TaskInfo inPipTask = mPipTransitionState.getPipTaskInfo();
+ return packageName != null && inPipTask != null && mPipTransitionState.isInPip()
+ && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 8e90bfe..6f9f40a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip2.phone;
import android.annotation.IntDef;
+import android.app.TaskInfo;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
@@ -133,17 +134,17 @@
private final Rect mSwipePipToHomeAppBounds = new Rect();
//
- // Tokens and leashes
+ // Task related caches
//
- // pinned PiP task's WC token
- @Nullable
- private WindowContainerToken mPipTaskToken;
-
// pinned PiP task's leash
@Nullable
private SurfaceControl mPinnedTaskLeash;
+ // pinned PiP task info
+ @Nullable
+ private TaskInfo mPipTaskInfo;
+
// Overlay leash potentially used during swipe PiP to home transition;
// if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid.
@Nullable
@@ -305,11 +306,7 @@
}
@Nullable WindowContainerToken getPipTaskToken() {
- return mPipTaskToken;
- }
-
- public void setPipTaskToken(@Nullable WindowContainerToken token) {
- mPipTaskToken = token;
+ return mPipTaskInfo != null ? mPipTaskInfo.getToken() : null;
}
@Nullable SurfaceControl getPinnedTaskLeash() {
@@ -320,6 +317,14 @@
mPinnedTaskLeash = leash;
}
+ @Nullable TaskInfo getPipTaskInfo() {
+ return mPipTaskInfo;
+ }
+
+ void setPipTaskInfo(@Nullable TaskInfo pipTaskInfo) {
+ mPipTaskInfo = pipTaskInfo;
+ }
+
/**
* @return true if either in swipe or button-nav fixed rotation.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index bfea342..96cc559 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -1459,10 +1459,13 @@
@NonNull Function1<Integer, Unit> onIconClickListener
) {
if (mTaskInfo.isFreeform()) {
+ // The menu uses display-wide coordinates for positioning, so make position the sum
+ // of task position and caption position.
+ final Rect taskBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
mManageWindowsMenu = new DesktopHeaderManageWindowsMenu(
mTaskInfo,
- /* x= */ mResult.mCaptionX,
- /* y= */ mResult.mCaptionY + mResult.mCaptionTopPadding,
+ /* x= */ taskBounds.left + mResult.mCaptionX,
+ /* y= */ taskBounds.top + mResult.mCaptionY + mResult.mCaptionTopPadding,
mDisplayController,
mRootTaskDisplayAreaOrganizer,
mContext,
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index c5fb808..b006580 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -73,13 +73,13 @@
MOCK_METHOD(ScopedAStatus, closeSessionChannel, (), (override));
MOCK_METHOD(ScopedAStatus, getCpuHeadroom,
(const ::aidl::android::os::CpuHeadroomParamsInternal& in_params,
- std::vector<float>* _aidl_return),
+ std::optional<hal::CpuHeadroomResult>* _aidl_return),
(override));
MOCK_METHOD(ScopedAStatus, getCpuHeadroomMinIntervalMillis, (int64_t* _aidl_return),
(override));
MOCK_METHOD(ScopedAStatus, getGpuHeadroom,
(const ::aidl::android::os::GpuHeadroomParamsInternal& in_params,
- float* _aidl_return),
+ std::optional<hal::GpuHeadroomResult>* _aidl_return),
(override));
MOCK_METHOD(ScopedAStatus, getGpuHeadroomMinIntervalMillis, (int64_t* _aidl_return),
(override));
diff --git a/nfc/tests/src/android/nfc/NdefMessageTest.java b/nfc/tests/src/android/nfc/NdefMessageTest.java
new file mode 100644
index 0000000..9ca295d
--- /dev/null
+++ b/nfc/tests/src/android/nfc/NdefMessageTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.nfc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class NdefMessageTest {
+ private NdefMessage mNdefMessage;
+ private NdefRecord mNdefRecord;
+
+ @Before
+ public void setUp() {
+ mNdefRecord = NdefRecord.createUri("http://www.example.com");
+ mNdefMessage = new NdefMessage(mNdefRecord);
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ @Test
+ public void testGetRecords() {
+ NdefRecord[] records = mNdefMessage.getRecords();
+ assertThat(records).isNotNull();
+ assertThat(records).hasLength(1);
+ assertThat(records[0]).isEqualTo(mNdefRecord);
+ }
+
+ @Test
+ public void testToByteArray() throws FormatException {
+ byte[] bytes = mNdefMessage.toByteArray();
+ assertThat(bytes).isNotNull();
+ assertThat(bytes.length).isGreaterThan(0);
+ NdefMessage ndefMessage = new NdefMessage(bytes);
+ assertThat(ndefMessage).isNotNull();
+ }
+}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreAdapter.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreAdapter.kt
index c2728b4..7601b9a 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreAdapter.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreAdapter.kt
@@ -20,7 +20,7 @@
import com.android.settingslib.datastore.KeyValueStore
/** Adapter to translate [KeyValueStore] into [PreferenceDataStore]. */
-class PreferenceDataStoreAdapter(private val keyValueStore: KeyValueStore) : PreferenceDataStore() {
+class PreferenceDataStoreAdapter(val keyValueStore: KeyValueStore) : PreferenceDataStore() {
override fun getBoolean(key: String, defValue: Boolean): Boolean =
keyValueStore.getValue(key, Boolean::class.javaObjectType) ?: defValue
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index 62ac3ad..7cec59c 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -74,16 +74,12 @@
private val preferences: ImmutableMap<String, PreferenceHierarchyNode>
private val dependencies: ImmutableMultimap<String, String>
private val lifecycleAwarePreferences: Array<PreferenceLifecycleProvider>
- private val storages = mutableSetOf<KeyedObservable<String>>()
+ private val storages = mutableMapOf<String, KeyedObservable<String>>()
private val preferenceObserver: KeyedObserver<String?>
private val storageObserver =
- KeyedObserver<String?> { key, _ ->
- if (key != null) {
- notifyChange(key, CHANGE_REASON_VALUE)
- }
- }
+ KeyedObserver<String> { key, _ -> notifyChange(key, CHANGE_REASON_VALUE) }
init {
val preferencesBuilder = ImmutableMap.builder<String, PreferenceHierarchyNode>()
@@ -98,7 +94,6 @@
preferencesBuilder.put(it.key, this)
it.dependencyOfEnabledState(context)?.addDependency(it)
if (it is PreferenceLifecycleProvider) lifecycleAwarePreferences.add(it)
- if (it is PersistentPreference<*>) storages.add(it.storage(context))
}
}
@@ -120,7 +115,16 @@
preferenceObserver = KeyedObserver { key, reason -> onPreferenceChange(key, reason) }
addObserver(preferenceObserver, mainExecutor)
- for (storage in storages) storage.addObserver(storageObserver, mainExecutor)
+
+ preferenceScreen.forEachRecursively {
+ val preferenceDataStore = it.preferenceDataStore
+ if (preferenceDataStore is PreferenceDataStoreAdapter) {
+ val key = it.key
+ val keyValueStore = preferenceDataStore.keyValueStore
+ storages[key] = keyValueStore
+ keyValueStore.addObserver(key, storageObserver, mainExecutor)
+ }
+ }
}
private fun onPreferenceChange(key: String?, reason: Int) {
@@ -181,7 +185,7 @@
fun onDestroy() {
removeObserver(preferenceObserver)
- for (storage in storages) storage.removeObserver(storageObserver)
+ for ((key, storage) in storages) storage.removeObserver(key, storageObserver)
for (preference in lifecycleAwarePreferences) {
preference.onDestroy(preferenceLifecycleContext)
}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt
new file mode 100644
index 0000000..2e7221b
--- /dev/null
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.preference
+
+import androidx.preference.Preference
+import androidx.preference.PreferenceGroup
+
+/** Traversals preference hierarchy recursively and applies an action. */
+fun PreferenceGroup.forEachRecursively(action: (Preference) -> Unit) {
+ action.invoke(this)
+ for (index in 0 until preferenceCount) {
+ val preference = getPreference(index)
+ if (preference is PreferenceGroup) {
+ preference.forEachRecursively(action)
+ } else {
+ action.invoke(preference)
+ }
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
index b0409c0..5ce97eb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
@@ -33,6 +33,7 @@
final class WritableNamespaces {
public static final Set<String> ALLOWLIST =
new ArraySet<String>(Arrays.asList(
+ "adservices",
"captive_portal_login",
"connectivity",
"exo",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
index 3db61a5..6bc0f42 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
@@ -220,9 +220,6 @@
@SuppressLint("MissingPermission")
private boolean isShortcutRestricted(int shortcutId) {
- if (!Flags.hideRestrictedActions()) {
- return false;
- }
final UserManager userManager = mService.getSystemService(UserManager.class);
if (userManager == null) {
return false;
@@ -366,12 +363,11 @@
if (mLayout.getVisibility() == View.VISIBLE) {
mLayout.setVisibility(View.GONE);
} else {
- if (Flags.hideRestrictedActions()) {
- // Reconfigure the shortcut list in case the set of restricted actions has changed.
- mA11yMenuViewPager.configureViewPagerAndFooter(
- mLayout, createShortcutList(), getPageIndex());
- updateViewLayout();
- }
+ // Reconfigure the shortcut list in case the set of restricted actions has changed.
+ mA11yMenuViewPager.configureViewPagerAndFooter(
+ mLayout, createShortcutList(), getPageIndex());
+ updateViewLayout();
+
mLayout.setVisibility(View.VISIBLE);
}
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 4ab771b..7172619 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -46,9 +46,6 @@
import android.media.AudioManager;
import android.os.PowerManager;
import android.os.UserManager;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.uiautomator_helpers.WaitUtils;
import android.provider.Settings;
import android.util.Log;
@@ -63,7 +60,6 @@
import androidx.test.uiautomator.UiDevice;
import com.android.compatibility.common.util.TestUtils;
-import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;
import org.junit.After;
@@ -71,7 +67,6 @@
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -82,9 +77,6 @@
@RunWith(AndroidJUnit4.class)
public class AccessibilityMenuServiceTest {
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
private static final String TAG = "A11yMenuServiceTest";
private static final int CLICK_ID = AccessibilityNodeInfo.ACTION_CLICK;
@@ -499,7 +491,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_HIDE_RESTRICTED_ACTIONS)
public void testRestrictedActions_BrightnessNotAvailable() throws Throwable {
try {
setUserRestriction(UserManager.DISALLOW_CONFIG_BRIGHTNESS, true);
@@ -519,7 +510,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_HIDE_RESTRICTED_ACTIONS)
public void testRestrictedActions_VolumeNotAvailable() throws Throwable {
try {
setUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME, true);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index f41d5c8..8552e48 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -63,8 +63,6 @@
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.TestableLooper;
import android.testing.TestableResources;
@@ -90,7 +88,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -612,46 +609,6 @@
.isEqualTo(expectedRatio);
}
- @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
- @Test
- public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierWindow() {
- int newSmallestScreenWidthDp =
- mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
- int windowFrameSize = mResources.getDimensionPixelSize(
- com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
- Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize);
- mSharedPreferences
- .edit()
- .putString(String.valueOf(newSmallestScreenWidthDp),
- preferredWindowSize.toString())
- .commit();
- mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
- Float.NaN);
- });
-
- // Screen density and size change
- mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp;
- final Rect testWindowBounds = new Rect(
- mWindowManager.getCurrentWindowMetrics().getBounds());
- testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
- testWindowBounds.right + 100, testWindowBounds.bottom + 100);
- mWindowManager.setWindowBounds(testWindowBounds);
- mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
- });
-
- // wait for rect update
- waitForIdleSync();
- ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
- final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
- R.dimen.magnification_mirror_surface_margin);
- // The width and height of the view include the magnification frame and the margins.
- assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
- assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
- }
-
- @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
@Test
public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierIndexAndWindow() {
int newSmallestScreenWidthDp =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
index 944066fa..d47ec8c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
@@ -25,15 +25,12 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.util.Size;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.FakeSharedPreferences;
@@ -59,18 +56,6 @@
mWindowMagnificationFrameSizePrefs = new WindowMagnificationFrameSizePrefs(mContext);
}
- @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
- @Test
- public void saveSizeForCurrentDensity_getExpectedSize() {
- Size testSize = new Size(500, 500);
- mWindowMagnificationFrameSizePrefs
- .saveIndexAndSizeForCurrentDensity(MagnificationSize.CUSTOM, testSize);
-
- assertThat(mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity())
- .isEqualTo(testSize);
- }
-
- @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
@Test
public void saveSizeForCurrentDensity_validPreference_getExpectedSize() {
int testIndex = MagnificationSize.MEDIUM;
@@ -81,7 +66,6 @@
.isEqualTo(testSize);
}
- @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
@Test
public void saveSizeForCurrentDensity_validPreference_getExpectedIndex() {
int testIndex = MagnificationSize.MEDIUM;
@@ -92,7 +76,6 @@
.isEqualTo(testIndex);
}
- @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
@Test
public void saveSizeForCurrentDensity_invalidPreference_getDefaultIndex() {
mSharedPreferences
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt
index bd26e42..bef995f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt
@@ -18,6 +18,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.widget.lockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
@@ -33,6 +34,8 @@
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -52,14 +55,21 @@
testScope.runTest {
val values by collectValues(underTest.lockWhileAwakeEvents)
- underTest.onKeyguardServiceDoKeyguardTimeout(options = null)
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true)
+ runCurrent()
+
+ kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(
+ options = null
+ )
runCurrent()
assertThat(values)
.containsExactly(LockWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON)
advanceTimeBy(1000)
- underTest.onKeyguardServiceDoKeyguardTimeout(options = null)
+ kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(
+ options = null
+ )
runCurrent()
assertThat(values)
@@ -69,8 +79,15 @@
)
}
+ /**
+ * We re-show keyguard when it's re-enabled, but only if it was originally showing when we
+ * disabled it.
+ *
+ * If it wasn't showing when originally disabled it, re-enabling it should do nothing (the
+ * keyguard will re-show next time we're locked).
+ */
@Test
- fun emitsWhenKeyguardEnabled_onlyIfShowingWhenDisabled() =
+ fun emitsWhenKeyguardReenabled_onlyIfShowingWhenDisabled() =
testScope.runTest {
val values by collectValues(underTest.lockWhileAwakeEvents)
@@ -98,4 +115,49 @@
assertThat(values).containsExactly(LockWhileAwakeReason.KEYGUARD_REENABLED)
}
+
+ /**
+ * Un-suppressing keyguard should never cause us to re-show. We'll re-show when we're next
+ * locked, even if we were showing when originally suppressed.
+ */
+ @Test
+ fun doesNotEmit_keyguardNoLongerSuppressed() =
+ testScope.runTest {
+ val values by collectValues(underTest.lockWhileAwakeEvents)
+
+ // Enable keyguard and then suppress it.
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true)
+ whenever(kosmos.lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+ runCurrent()
+
+ assertEquals(0, values.size)
+
+ // Un-suppress keyguard.
+ whenever(kosmos.lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+ runCurrent()
+
+ assertEquals(0, values.size)
+ }
+
+ /**
+ * Lockdown and lockNow() should not cause us to lock while awake if we are suppressed via adb.
+ */
+ @Test
+ fun doesNotEmit_fromLockdown_orFromLockNow_ifEnabledButSuppressed() =
+ testScope.runTest {
+ val values by collectValues(underTest.lockWhileAwakeEvents)
+
+ // Set keyguard enabled, but then disable lockscreen (suppress it).
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true)
+ whenever(kosmos.lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+ runCurrent()
+
+ kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(null)
+ runCurrent()
+
+ kosmos.biometricSettingsRepository.setIsUserInLockdown(true)
+ runCurrent()
+
+ assertEquals(0, values.size)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
index 7e249e8..ead151e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
@@ -87,9 +87,9 @@
assertEquals(
listOf(
- false, // Defaults to false.
+ false // Defaults to false.
),
- canWake
+ canWake,
)
repository.setKeyguardEnabled(false)
@@ -100,33 +100,26 @@
false, // Default to false.
true, // True now that keyguard service is disabled
),
- canWake
+ canWake,
)
repository.setKeyguardEnabled(true)
runCurrent()
- assertEquals(
- listOf(
- false,
- true,
- false,
- ),
- canWake
- )
+ assertEquals(listOf(false, true, false), canWake)
}
@Test
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
- fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled() =
+ fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled_onlyAfterWakefulnessChange() =
testScope.runTest {
val canWake by collectValues(underTest.canWakeDirectlyToGone)
assertEquals(
listOf(
- false, // Defaults to false.
+ false // Defaults to false.
),
- canWake
+ canWake,
)
whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
@@ -136,9 +129,9 @@
listOf(
// Still false - isLockScreenDisabled only causes canWakeDirectlyToGone to
// update on the next wake/sleep event.
- false,
+ false
),
- canWake
+ canWake,
)
kosmos.powerInteractor.setAsleepForTest()
@@ -150,7 +143,7 @@
// True since we slept after setting isLockScreenDisabled=true
true,
),
- canWake
+ canWake,
)
kosmos.powerInteractor.setAwakeForTest()
@@ -159,25 +152,75 @@
kosmos.powerInteractor.setAsleepForTest()
runCurrent()
- assertEquals(
- listOf(
- false,
- true,
- ),
- canWake
- )
+ assertEquals(listOf(false, true), canWake)
whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
kosmos.powerInteractor.setAwakeForTest()
runCurrent()
+ assertEquals(listOf(false, true, false), canWake)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled_lockNowEvent() =
+ testScope.runTest {
+ val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+ assertEquals(
+ listOf(
+ false // Defaults to false.
+ ),
+ canWake,
+ )
+
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ // Still false - isLockScreenDisabled only causes canWakeDirectlyToGone to
+ // update on the next wakefulness or lockNow event.
+ false
+ ),
+ canWake,
+ )
+
+ kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(null)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ // True when lockNow() called after setting isLockScreenDisabled=true
+ true,
+ ),
+ canWake,
+ )
+
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ // Still true since no lockNow() calls made.
+ true,
+ ),
+ canWake,
+ )
+
+ kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(null)
+ runCurrent()
+
assertEquals(
listOf(
false,
true,
+ // False again after the lockNow() call.
false,
),
- canWake
+ canWake,
)
}
@@ -189,9 +232,9 @@
assertEquals(
listOf(
- false, // Defaults to false.
+ false // Defaults to false.
),
- canWake
+ canWake,
)
repository.setBiometricUnlockState(BiometricUnlockMode.WAKE_AND_UNLOCK)
@@ -213,9 +256,9 @@
assertEquals(
listOf(
- false, // Defaults to false.
+ false // Defaults to false.
),
- canWake
+ canWake,
)
repository.setCanIgnoreAuthAndReturnToGone(true)
@@ -237,9 +280,9 @@
assertEquals(
listOf(
- false, // Defaults to false.
+ false // Defaults to false.
),
- canWake
+ canWake,
)
whenever(kosmos.devicePolicyManager.getMaximumTimeToLock(eq(null), anyInt()))
@@ -257,13 +300,7 @@
)
runCurrent()
- assertEquals(
- listOf(
- false,
- true,
- ),
- canWake
- )
+ assertEquals(listOf(false, true), canWake)
verify(kosmos.alarmManager)
.setExactAndAllowWhileIdle(
@@ -281,9 +318,9 @@
assertEquals(
listOf(
- false, // Defaults to false.
+ false // Defaults to false.
),
- canWake
+ canWake,
)
whenever(kosmos.devicePolicyManager.getMaximumTimeToLock(eq(null), anyInt()))
@@ -312,7 +349,7 @@
// Timed out, so we can ignore auth/return to GONE.
true,
),
- canWake
+ canWake,
)
verify(kosmos.alarmManager)
@@ -338,7 +375,7 @@
// alarm in flight that should be canceled.
false,
),
- canWake
+ canWake,
)
kosmos.powerInteractor.setAsleepForTest(
@@ -354,25 +391,17 @@
// Back to sleep.
true,
),
- canWake
+ canWake,
)
// Simulate the first sleep's alarm coming in.
lastRegisteredBroadcastReceiver?.onReceive(
kosmos.mockedContext,
- Intent("com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD")
+ Intent("com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD"),
)
runCurrent()
// It should not have any effect.
- assertEquals(
- listOf(
- false,
- true,
- false,
- true,
- ),
- canWake
- )
+ assertEquals(listOf(false, true, false, true), canWake)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
new file mode 100644
index 0000000..0966759
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.display.ShadeDisplayPolicy
+import com.android.systemui.shade.display.SpecificDisplayIdPolicy
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeDisplaysRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val defaultPolicy = SpecificDisplayIdPolicy(0)
+
+ private val shadeDisplaysRepository =
+ ShadeDisplaysRepositoryImpl(defaultPolicy, testScope.backgroundScope)
+
+ @Test
+ fun policy_changing_propagatedFromTheLatestPolicy() =
+ testScope.runTest {
+ val displayIds by collectValues(shadeDisplaysRepository.displayId)
+ val policy1 = MutablePolicy()
+ val policy2 = MutablePolicy()
+
+ assertThat(displayIds).containsExactly(0)
+
+ shadeDisplaysRepository.policy.value = policy1
+
+ policy1.sendDisplayId(1)
+
+ assertThat(displayIds).containsExactly(0, 1)
+
+ policy1.sendDisplayId(2)
+
+ assertThat(displayIds).containsExactly(0, 1, 2)
+
+ shadeDisplaysRepository.policy.value = policy2
+
+ assertThat(displayIds).containsExactly(0, 1, 2, 0)
+
+ policy1.sendDisplayId(4)
+
+ // Changes to the first policy don't affect the output now
+ assertThat(displayIds).containsExactly(0, 1, 2, 0)
+
+ policy2.sendDisplayId(5)
+
+ assertThat(displayIds).containsExactly(0, 1, 2, 0, 5)
+ }
+
+ private class MutablePolicy : ShadeDisplayPolicy {
+ fun sendDisplayId(id: Int) {
+ _displayId.value = id
+ }
+
+ private val _displayId = MutableStateFlow(0)
+ override val name: String
+ get() = "mutable_policy"
+
+ override val displayId: StateFlow<Int>
+ get() = _displayId
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
index af01547..d584dc9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
@@ -23,12 +23,17 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.shade.ShadePrimaryDisplayCommand
+import com.android.systemui.shade.display.ShadeDisplayPolicy
import com.android.systemui.statusbar.commandline.commandRegistry
import com.android.systemui.testKosmos
+import com.google.common.truth.StringSubject
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -37,15 +42,26 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadePrimaryDisplayCommandTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
private val commandRegistry = kosmos.commandRegistry
private val displayRepository = kosmos.displayRepository
- private val shadeDisplaysRepository = ShadeDisplaysRepositoryImpl()
+ private val defaultPolicy = kosmos.defaultShadeDisplayPolicy
+ private val policy1 = makePolicy("policy_1")
+ private val shadeDisplaysRepository = kosmos.shadeDisplaysRepository
private val pw = PrintWriter(StringWriter())
+ private val policies =
+ setOf(defaultPolicy, policy1, makePolicy("policy_2"), makePolicy("policy_3"))
+
private val underTest =
- ShadePrimaryDisplayCommand(commandRegistry, displayRepository, shadeDisplaysRepository)
+ ShadePrimaryDisplayCommand(
+ commandRegistry,
+ displayRepository,
+ shadeDisplaysRepository,
+ policies,
+ defaultPolicy,
+ )
@Before
fun setUp() {
@@ -96,4 +112,41 @@
assertThat(displayId).isEqualTo(newDisplayId)
}
+
+ @Test
+ fun policies_listsAllPolicies() =
+ testScope.runTest {
+ val stringWriter = StringWriter()
+ commandRegistry.onShellCommand(
+ PrintWriter(stringWriter),
+ arrayOf("shade_display_override", "policies"),
+ )
+ val result = stringWriter.toString()
+
+ assertThat(result).containsAllIn(policies.map { it.name })
+ }
+
+ @Test
+ fun policies_setsSpecificPolicy() =
+ testScope.runTest {
+ val policy by collectLastValue(shadeDisplaysRepository.policy)
+
+ commandRegistry.onShellCommand(pw, arrayOf("shade_display_override", policy1.name))
+
+ assertThat(policy!!.name).isEqualTo(policy1.name)
+ }
+
+ private fun makePolicy(policyName: String): ShadeDisplayPolicy {
+ return object : ShadeDisplayPolicy {
+ override val name: String
+ get() = policyName
+
+ override val displayId: StateFlow<Int>
+ get() = MutableStateFlow(0)
+ }
+ }
+}
+
+private fun StringSubject.containsAllIn(strings: List<String>) {
+ strings.forEach { contains(it) }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicyTest.kt
new file mode 100644
index 0000000..4d4efd1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicyTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.display
+
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.display
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AnyExternalShadeDisplayPolicyTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val displayRepository = kosmos.displayRepository
+ val underTest = AnyExternalShadeDisplayPolicy(displayRepository, testScope.backgroundScope)
+
+ @Test
+ fun displayId_ignoresUnwantedTypes() =
+ testScope.runTest {
+ val displayId by collectLastValue(underTest.displayId)
+
+ displayRepository.addDisplays(
+ display(id = 0, type = Display.TYPE_INTERNAL),
+ display(id = 1, type = Display.TYPE_UNKNOWN),
+ display(id = 2, type = Display.TYPE_VIRTUAL),
+ display(id = 3, type = Display.TYPE_EXTERNAL),
+ )
+
+ assertThat(displayId).isEqualTo(3)
+ }
+
+ @Test
+ fun displayId_onceRemoved_goesToNextDisplay() =
+ testScope.runTest {
+ val displayId by collectLastValue(underTest.displayId)
+
+ displayRepository.addDisplays(
+ display(id = 0, type = Display.TYPE_INTERNAL),
+ display(id = 2, type = Display.TYPE_EXTERNAL),
+ display(id = 3, type = Display.TYPE_EXTERNAL),
+ )
+
+ assertThat(displayId).isEqualTo(2)
+
+ displayRepository.removeDisplay(2)
+
+ assertThat(displayId).isEqualTo(3)
+ }
+
+ @Test
+ fun displayId_onlyDefaultDisplay_defaultDisplay() =
+ testScope.runTest {
+ val displayId by collectLastValue(underTest.displayId)
+
+ displayRepository.addDisplays(display(id = 0, type = Display.TYPE_INTERNAL))
+
+ assertThat(displayId).isEqualTo(0)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
index 016a24a..982c51b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade.domain.interactor
import android.content.Context
-import android.content.MutableContextWrapper
import android.content.res.Configuration
import android.content.res.Resources
import android.view.Display
@@ -30,7 +29,6 @@
import com.android.systemui.display.shared.model.DisplayWindowProperties
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.data.repository.FakeShadeDisplayRepository
-import com.android.systemui.statusbar.phone.ConfigurationForwarder
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -39,6 +37,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.inOrder
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
@@ -53,13 +52,10 @@
private val shadeRootview = mock<WindowRootView>()
private val positionRepository = FakeShadeDisplayRepository()
- private val defaultContext = mock<Context>()
- private val secondaryContext = mock<Context>()
+ private val shadeContext = mock<Context>()
private val contextStore = FakeDisplayWindowPropertiesRepository()
private val testScope = TestScope(UnconfinedTestDispatcher())
- private val configurationForwarder = mock<ConfigurationForwarder>()
- private val defaultWm = mock<WindowManager>()
- private val secondaryWm = mock<WindowManager>()
+ private val shadeWm = mock<WindowManager>()
private val resources = mock<Resources>()
private val configuration = mock<Configuration>()
private val display = mock<Display>()
@@ -68,11 +64,9 @@
ShadeDisplaysInteractor(
Optional.of(shadeRootview),
positionRepository,
- MutableContextWrapper(defaultContext),
- resources,
- contextStore,
+ shadeContext,
+ shadeWm,
testScope.backgroundScope,
- configurationForwarder,
testScope.backgroundScope.coroutineContext,
)
@@ -83,28 +77,15 @@
whenever(resources.configuration).thenReturn(configuration)
- whenever(defaultContext.displayId).thenReturn(0)
- whenever(defaultContext.getSystemService(any())).thenReturn(defaultWm)
- whenever(defaultContext.resources).thenReturn(resources)
+ whenever(shadeContext.displayId).thenReturn(0)
+ whenever(shadeContext.getSystemService(any())).thenReturn(shadeWm)
+ whenever(shadeContext.resources).thenReturn(resources)
contextStore.insert(
DisplayWindowProperties(
displayId = 0,
windowType = TYPE_NOTIFICATION_SHADE,
- context = defaultContext,
- windowManager = defaultWm,
- layoutInflater = mock(),
- )
- )
-
- whenever(secondaryContext.displayId).thenReturn(1)
- whenever(secondaryContext.getSystemService(any())).thenReturn(secondaryWm)
- whenever(secondaryContext.resources).thenReturn(resources)
- contextStore.insert(
- DisplayWindowProperties(
- displayId = 1,
- windowType = TYPE_NOTIFICATION_SHADE,
- context = secondaryContext,
- windowManager = secondaryWm,
+ context = shadeContext,
+ windowManager = shadeWm,
layoutInflater = mock(),
)
)
@@ -117,8 +98,7 @@
interactor.start()
testScope.advanceUntilIdle()
- verifyNoMoreInteractions(defaultWm)
- verifyNoMoreInteractions(secondaryWm)
+ verifyNoMoreInteractions(shadeWm)
}
@Test
@@ -127,8 +107,10 @@
positionRepository.setDisplayId(1)
interactor.start()
- verify(defaultWm).removeView(eq(shadeRootview))
- verify(secondaryWm).addView(eq(shadeRootview), any())
+ inOrder(shadeWm).apply {
+ verify(shadeWm).removeView(eq(shadeRootview))
+ verify(shadeWm).addView(eq(shadeRootview), any())
+ }
}
@Test
@@ -139,18 +121,9 @@
positionRepository.setDisplayId(1)
- verify(defaultWm).removeView(eq(shadeRootview))
- verify(secondaryWm).addView(eq(shadeRootview), any())
- }
-
- @Test
- fun start_shadePositionChanges_newConfigPropagated() {
- whenever(display.displayId).thenReturn(0)
- positionRepository.setDisplayId(0)
- interactor.start()
-
- positionRepository.setDisplayId(1)
-
- verify(configurationForwarder).onConfigurationChanged(eq(configuration))
+ inOrder(shadeWm).apply {
+ verify(shadeWm).removeView(eq(shadeRootview))
+ verify(shadeWm).addView(eq(shadeRootview), any())
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 7d5cf23..3794e7b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -283,7 +283,7 @@
com.android.internal.R.integer.config_shortAnimTime));
updateDimensions();
- final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible();
+ final Size windowFrameSize = restoreMagnificationWindowFrameIndexAndSizeIfPossible();
setMagnificationFrame(windowFrameSize.getWidth(), windowFrameSize.getHeight(),
mWindowBounds.width() / 2, mWindowBounds.height() / 2);
computeBounceAnimationScale();
@@ -541,7 +541,7 @@
return false;
}
mWindowBounds.set(currentWindowBounds);
- final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible();
+ final Size windowFrameSize = restoreMagnificationWindowFrameIndexAndSizeIfPossible();
final float newCenterX = (getCenterX()) * mWindowBounds.width() / oldWindowBounds.width();
final float newCenterY = (getCenterY()) * mWindowBounds.height() / oldWindowBounds.height();
@@ -787,18 +787,6 @@
mMagnificationFrame.set(initX, initY, initX + width, initY + height);
}
- private Size restoreMagnificationWindowFrameSizeIfPossible() {
- if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
- return restoreMagnificationWindowFrameIndexAndSizeIfPossible();
- }
-
- if (!mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) {
- return getDefaultMagnificationWindowFrameSize();
- }
-
- return mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity();
- }
-
private Size restoreMagnificationWindowFrameIndexAndSizeIfPossible() {
if (!mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) {
notifyWindowSizeRestored(MagnificationSize.DEFAULT);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
index ee36c6e..558c87c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
@@ -22,8 +22,6 @@
import android.content.SharedPreferences;
import android.util.Size;
-import com.android.systemui.Flags;
-
/**
* Class to handle SharedPreference for window magnification size.
*/
@@ -52,14 +50,10 @@
* Saves the window frame size for current screen density.
*/
public void saveIndexAndSizeForCurrentDensity(int index, Size size) {
- if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
- mWindowMagnificationSizePreferences.edit()
- .putString(getKey(),
- WindowMagnificationFrameSpec.serialize(index, size)).apply();
- } else {
- mWindowMagnificationSizePreferences.edit()
- .putString(getKey(), size.toString()).apply();
- }
+ mWindowMagnificationSizePreferences
+ .edit()
+ .putString(getKey(), WindowMagnificationFrameSpec.serialize(index, size))
+ .apply();
}
/**
@@ -91,13 +85,9 @@
* Gets the size preference for current screen density.
*/
public Size getSizeForCurrentDensity() {
- if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
- return WindowMagnificationFrameSpec
- .deserialize(mWindowMagnificationSizePreferences.getString(getKey(), null))
- .getSize();
- } else {
- return Size.parseSize(mWindowMagnificationSizePreferences.getString(getKey(), null));
- }
+ return WindowMagnificationFrameSpec.deserialize(
+ mWindowMagnificationSizePreferences.getString(getKey(), null))
+ .getSize();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 2f0ca6e..9525822 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -59,7 +59,6 @@
import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.Flags;
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView;
import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
@@ -460,12 +459,8 @@
mAllowDiagonalScrollingView.setVisibility(View.VISIBLE);
mFullScreenButton.setVisibility(View.GONE);
if (selectedButtonIndex == MagnificationSize.FULLSCREEN) {
- if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
- selectedButtonIndex =
- windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
- } else {
- selectedButtonIndex = MagnificationSize.CUSTOM;
- }
+ selectedButtonIndex =
+ windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
}
break;
@@ -482,10 +477,8 @@
} else { // mode = ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
mEditButton.setVisibility(View.VISIBLE);
mAllowDiagonalScrollingView.setVisibility(View.VISIBLE);
- if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
- selectedButtonIndex =
- windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
- }
+ selectedButtonIndex =
+ windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
}
break;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 7097c1d..d40fe46 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -81,7 +81,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardLockWhileAwakeInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardServiceLockNowInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardStateCallbackInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardWakeDirectlyToGoneInteractor;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
@@ -330,8 +330,7 @@
return new FoldGracePeriodProvider();
}
};
- private final KeyguardLockWhileAwakeInteractor
- mKeyguardLockWhileAwakeInteractor;
+ private final KeyguardServiceLockNowInteractor mKeyguardServiceLockNowInteractor;
@Inject
public KeyguardService(
@@ -357,7 +356,7 @@
KeyguardDismissInteractor keyguardDismissInteractor,
Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy,
KeyguardStateCallbackInteractor keyguardStateCallbackInteractor,
- KeyguardLockWhileAwakeInteractor keyguardLockWhileAwakeInteractor) {
+ KeyguardServiceLockNowInteractor keyguardServiceLockNowInteractor) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
@@ -389,7 +388,7 @@
mKeyguardEnabledInteractor = keyguardEnabledInteractor;
mKeyguardWakeDirectlyToGoneInteractor = keyguardWakeDirectlyToGoneInteractor;
mKeyguardDismissInteractor = keyguardDismissInteractor;
- mKeyguardLockWhileAwakeInteractor = keyguardLockWhileAwakeInteractor;
+ mKeyguardServiceLockNowInteractor = keyguardServiceLockNowInteractor;
}
@Override
@@ -665,7 +664,7 @@
if (SceneContainerFlag.isEnabled()) {
mDeviceEntryInteractorLazy.get().lockNow();
} else if (KeyguardWmStateRefactor.isEnabled()) {
- mKeyguardLockWhileAwakeInteractor.onKeyguardServiceDoKeyguardTimeout(options);
+ mKeyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(options);
}
mKeyguardViewMediator.doKeyguardTimeout(options);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 01ec4d0..9f13160 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3943,7 +3943,7 @@
}
private void notifyDefaultDisplayCallbacks(boolean showing) {
- if (SceneContainerFlag.isEnabled()) {
+ if (SceneContainerFlag.isEnabled() || KeyguardWmStateRefactor.isEnabled()) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
index 631e44a..42cbd7d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -16,39 +16,52 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.withContext
/**
- * Logic around the keyguard being enabled/disabled, per [KeyguardService]. If the keyguard is not
- * enabled, the lockscreen cannot be shown and the device will go from AOD/DOZING directly to GONE.
+ * Logic around the keyguard being enabled, disabled, or suppressed via adb. If the keyguard is
+ * disabled or suppressed, the lockscreen cannot be shown and the device will go from AOD/DOZING
+ * directly to GONE.
*
* Keyguard can be disabled by selecting Security: "None" in settings, or by apps that hold
* permission to do so (such as Phone). Some CTS tests also disable keyguard in onCreate or onStart
* rather than simply dismissing the keyguard or setting up the device to have Security: None, for
* reasons unknown.
+ *
+ * Keyguard can be suppressed by calling "adb shell locksettings set-disabled true", which is
+ * frequently done in tests. If keyguard is suppressed, it won't show even if the keyguard is
+ * enabled. If keyguard is not suppressed, then we defer to whether keyguard is enabled or disabled.
*/
@SysUISingleton
class KeyguardEnabledInteractor
@Inject
constructor(
- @Application scope: CoroutineScope,
+ @Application val scope: CoroutineScope,
+ @Background val backgroundDispatcher: CoroutineDispatcher,
val repository: KeyguardRepository,
val biometricSettingsRepository: BiometricSettingsRepository,
- keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor,
+ private val selectedUserInteractor: SelectedUserInteractor,
+ private val lockPatternUtils: LockPatternUtils,
+ keyguardDismissTransitionInteractor: dagger.Lazy<KeyguardDismissTransitionInteractor>,
internalTransitionInteractor: InternalKeyguardTransitionInteractor,
) {
@@ -62,6 +75,10 @@
* If the keyguard is disabled while we're locked, we will transition to GONE unless we're in
* lockdown mode. If the keyguard is re-enabled, we'll transition back to LOCKSCREEN if we were
* locked when it was disabled.
+ *
+ * Even if the keyguard is enabled, it's possible for it to be suppressed temporarily via adb.
+ * If you need to respect that adb command, you will need to use
+ * [isKeyguardEnabledAndNotSuppressed] instead of using this flow.
*/
val isKeyguardEnabled: StateFlow<Boolean> = repository.isKeyguardEnabled
@@ -96,9 +113,9 @@
val currentTransitionInfo =
internalTransitionInteractor.currentTransitionInfoInternal()
if (currentTransitionInfo.to != KeyguardState.GONE && !inLockdown) {
- keyguardDismissTransitionInteractor.startDismissKeyguardTransition(
- "keyguard disabled"
- )
+ keyguardDismissTransitionInteractor
+ .get()
+ .startDismissKeyguardTransition("keyguard disabled")
}
}
}
@@ -116,4 +133,37 @@
fun isShowKeyguardWhenReenabled(): Boolean {
return repository.isShowKeyguardWhenReenabled()
}
+
+ /**
+ * Whether the keyguard is enabled, and has not been suppressed via adb.
+ *
+ * There is unfortunately no callback for [isKeyguardSuppressed], which means this can't be a
+ * flow, since it's ambiguous when we would query the latest suppression value.
+ */
+ suspend fun isKeyguardEnabledAndNotSuppressed(): Boolean {
+ return isKeyguardEnabled.value && !isKeyguardSuppressed()
+ }
+
+ /**
+ * Returns whether the lockscreen has been disabled ("suppressed") via "adb shell locksettings
+ * set-disabled". If suppressed, we'll ignore all signals that would typically result in showing
+ * the keyguard, regardless of the value of [isKeyguardEnabled].
+ *
+ * It's extremely confusing to have [isKeyguardEnabled] not be the inverse of "is lockscreen
+ * disabled", so this method intentionally re-terms it as "suppressed".
+ *
+ * Note that if the lockscreen is currently showing when it's suppressed, it will remain visible
+ * until it's unlocked, at which point it will never re-appear until suppression is removed.
+ */
+ suspend fun isKeyguardSuppressed(
+ userId: Int = selectedUserInteractor.getSelectedUserId()
+ ): Boolean {
+ // isLockScreenDisabled returns true whenever keyguard is not enabled, even if the adb
+ // command was not used to disable/suppress the lockscreen. To make these booleans as clear
+ // as possible, only return true if keyguard is suppressed when it otherwise would have
+ // been enabled.
+ return withContext(backgroundDispatcher) {
+ isKeyguardEnabled.value && lockPatternUtils.isLockScreenDisabled(userId)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt
index 0ab3e5c..ce84e71 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt
@@ -16,27 +16,16 @@
package com.android.systemui.keyguard.domain.interactor
-import android.os.Bundle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
-/**
- * Emitted when we receive a [KeyguardLockWhileAwakeInteractor.onKeyguardServiceDoKeyguardTimeout]
- * call.
- *
- * Includes a timestamp so it's not conflated by the StateFlow.
- */
-data class KeyguardTimeoutWhileAwakeEvent(val timestamp: Long, val options: Bundle?)
-
/** The reason we're locking while awake, used for logging. */
enum class LockWhileAwakeReason(private val logReason: String) {
LOCKDOWN("Lockdown initiated."),
@@ -71,10 +60,8 @@
constructor(
biometricSettingsRepository: BiometricSettingsRepository,
keyguardEnabledInteractor: KeyguardEnabledInteractor,
+ keyguardServiceLockNowInteractor: KeyguardServiceLockNowInteractor,
) {
- /** Emits whenever a timeout event is received by [KeyguardService]. */
- private val timeoutEvents: MutableStateFlow<KeyguardTimeoutWhileAwakeEvent?> =
- MutableStateFlow(null)
/** Emits whenever the current user is in lockdown mode. */
private val inLockdown: Flow<LockWhileAwakeReason> =
@@ -97,25 +84,19 @@
/** Emits whenever we should lock while the screen is on, for any reason. */
val lockWhileAwakeEvents: Flow<LockWhileAwakeReason> =
merge(
- inLockdown,
- keyguardReenabled,
- timeoutEvents.filterNotNull().map {
- LockWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON
- },
+ // We're in lockdown, and the keyguard is enabled. If the keyguard is disabled, the
+ // lockdown button is hidden in the UI, but it's still possible to trigger lockdown in
+ // tests.
+ inLockdown
+ .filter { keyguardEnabledInteractor.isKeyguardEnabledAndNotSuppressed() }
+ .map { LockWhileAwakeReason.LOCKDOWN },
+ // The keyguard was re-enabled, and it was showing when it was originally disabled.
+ // Tests currently expect that if the keyguard is re-enabled, it will show even if it's
+ // suppressed, so we don't check for isKeyguardEnabledAndNotSuppressed() on this flow.
+ keyguardReenabled.map { LockWhileAwakeReason.KEYGUARD_REENABLED },
+ // KeyguardService says we need to lock now, and the lockscreen is enabled.
+ keyguardServiceLockNowInteractor.lockNowEvents
+ .filter { keyguardEnabledInteractor.isKeyguardEnabledAndNotSuppressed() }
+ .map { LockWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON },
)
-
- /**
- * Called by [KeyguardService] when it receives a doKeyguardTimeout() call. This indicates that
- * the device locked while the screen was on.
- *
- * [options] appears to be no longer used, but we'll keep it in this interactor in case that
- * turns out not to be true.
- */
- fun onKeyguardServiceDoKeyguardTimeout(options: Bundle?) {
- timeoutEvents.value =
- KeyguardTimeoutWhileAwakeEvent(
- timestamp = System.currentTimeMillis(),
- options = options,
- )
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt
new file mode 100644
index 0000000..9ed53ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.launch
+
+/**
+ * Emitted when we receive a [KeyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout]
+ * call.
+ */
+data class KeyguardLockNowEvent(val options: Bundle?)
+
+/**
+ * Logic around requests by [KeyguardService] to lock the device right now, even though the device
+ * is awake and not going to sleep.
+ *
+ * This can happen if WM#lockNow() is called, or if the screen is forced to stay awake but the lock
+ * timeout elapses.
+ *
+ * This is not the only way for the device to lock while the screen is on. The other cases, which do
+ * not directly involve [KeyguardService], are handled in [KeyguardLockWhileAwakeInteractor].
+ */
+@SysUISingleton
+class KeyguardServiceLockNowInteractor
+@Inject
+constructor(@Background val backgroundScope: CoroutineScope) {
+
+ /**
+ * Emits whenever [KeyguardService] receives a call that indicates we should lock the device
+ * right now, even though the device is awake and not going to sleep.
+ *
+ * WARNING: This is only one of multiple reasons the device might need to lock while not going
+ * to sleep. Unless you're dealing with keyguard internals that specifically need to know that
+ * we're locking due to a call to doKeyguardTimeout, use
+ * [KeyguardLockWhileAwakeInteractor.lockWhileAwakeEvents].
+ *
+ * This is fundamentally an event flow, hence the SharedFlow.
+ */
+ @SuppressLint("SharedFlowCreation")
+ val lockNowEvents: MutableSharedFlow<KeyguardLockNowEvent> = MutableSharedFlow()
+
+ /**
+ * Called by [KeyguardService] when it receives a doKeyguardTimeout() call. This indicates that
+ * the device locked while the screen was on.
+ *
+ * [options] appears to be no longer used, but we'll keep it in this interactor in case that
+ * turns out not to be true.
+ */
+ fun onKeyguardServiceDoKeyguardTimeout(options: Bundle?) {
+ backgroundScope.launch { lockNowEvents.emit(KeyguardLockNowEvent(options = options)) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
index fbc7e2a..8641dfa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
@@ -25,6 +25,7 @@
import android.content.IntentFilter
import android.provider.Settings
import android.provider.Settings.Secure
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -48,20 +49,21 @@
import kotlin.math.max
import kotlin.math.min
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
/**
* Logic related to the ability to wake directly to GONE from asleep (AOD/DOZING), without going
* through LOCKSCREEN or a BOUNCER state.
*
* This is possible in the following scenarios:
- * - The lockscreen is disabled, either from an app request (SUW does this), or by the security
+ * - The keyguard is not enabled, either from an app request (SUW does this), or by the security
* "None" setting.
+ * - The keyguard was suppressed via adb.
* - A biometric authentication event occurred while we were asleep (fingerprint auth, etc). This
* specifically is referred to throughout the codebase as "wake and unlock".
* - The screen timed out, but the "lock after screen timeout" duration has not elapsed.
@@ -86,43 +88,44 @@
private val lockPatternUtils: LockPatternUtils,
private val systemSettings: SystemSettings,
private val selectedUserInteractor: SelectedUserInteractor,
+ keyguardEnabledInteractor: KeyguardEnabledInteractor,
+ keyguardServiceLockNowInteractor: KeyguardServiceLockNowInteractor,
) {
/**
- * Whether the lockscreen was disabled as of the last wake/sleep event, according to
- * LockPatternUtils.
- *
- * This will always be true if [repository.isKeyguardServiceEnabled]=false, but it can also be
- * true when the keyguard service is enabled if the lockscreen has been disabled via adb using
- * the `adb shell locksettings set-disabled true` command, which is often done in tests.
- *
- * Unlike keyguardServiceEnabled, changes to this value should *not* immediately show or hide
- * the keyguard. If the lockscreen is disabled in this way, it will just not show on the next
- * sleep/wake.
+ * Whether the keyguard was suppressed as of the most recent wakefulness event or lockNow
+ * command. Keyguard suppression can only be queried (there is no callback available), and
+ * legacy code only queried the value in onStartedGoingToSleep and doKeyguardTimeout. Tests now
+ * depend on that behavior, so for now, we'll replicate it here.
*/
- private val isLockscreenDisabled: Flow<Boolean> =
- powerInteractor.isAwake.map { isLockscreenDisabled() }
+ private val shouldSuppressKeyguard =
+ merge(powerInteractor.isAwake, keyguardServiceLockNowInteractor.lockNowEvents)
+ .map { keyguardEnabledInteractor.isKeyguardSuppressed() }
+ // Default to false, so that flows that combine this one emit prior to the first
+ // wakefulness emission.
+ .onStart { emit(false) }
/**
* Whether we can wake from AOD/DOZING directly to GONE, bypassing LOCKSCREEN/BOUNCER states.
*
* This is possible in the following cases:
* - Keyguard is disabled, either from an app request or from security being set to "None".
+ * - Keyguard is suppressed, via adb locksettings.
* - We're wake and unlocking (fingerprint auth occurred while asleep).
* - We're allowed to ignore auth and return to GONE, due to timeouts not elapsing.
*/
val canWakeDirectlyToGone =
combine(
repository.isKeyguardEnabled,
- isLockscreenDisabled,
+ shouldSuppressKeyguard,
repository.biometricUnlockState,
repository.canIgnoreAuthAndReturnToGone,
) {
keyguardEnabled,
- isLockscreenDisabled,
+ shouldSuppressKeyguard,
biometricUnlockState,
canIgnoreAuthAndReturnToGone ->
- (!keyguardEnabled || isLockscreenDisabled) ||
+ (!keyguardEnabled || shouldSuppressKeyguard) ||
BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode) ||
canIgnoreAuthAndReturnToGone
}
@@ -186,9 +189,9 @@
.sample(
transitionInteractor.isCurrentlyIn(
Scenes.Gone,
- stateWithoutSceneContainer = KeyguardState.GONE
+ stateWithoutSceneContainer = KeyguardState.GONE,
),
- ::Pair
+ ::Pair,
)
.collect { (wakefulness, finishedInGone) ->
// Save isAwake for use in onDreamingStarted/onDreamingStopped.
@@ -260,7 +263,7 @@
delayedActionFilter,
SYSTEMUI_PERMISSION,
null /* scheduler */,
- Context.RECEIVER_EXPORTED_UNAUDITED
+ Context.RECEIVER_EXPORTED_UNAUDITED,
)
}
@@ -282,7 +285,7 @@
context,
0,
intent,
- PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
val time = systemClock.elapsedRealtime() + getCanIgnoreAuthAndReturnToGoneDuration()
@@ -311,16 +314,6 @@
}
/**
- * Returns whether the lockscreen is disabled, either because the keyguard service is disabled
- * or because an adb command has disabled the lockscreen.
- */
- private fun isLockscreenDisabled(
- userId: Int = selectedUserInteractor.getSelectedUserId()
- ): Boolean {
- return lockPatternUtils.isLockScreenDisabled(userId)
- }
-
- /**
* Returns the duration within which we can return to GONE without auth after a screen timeout
* (or power button press, if lock instantly is disabled).
*
@@ -336,7 +329,7 @@
.getIntForUser(
Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
KEYGUARD_CAN_IGNORE_AUTH_DURATION,
- userId
+ userId,
)
.toLong()
@@ -352,7 +345,7 @@
.getIntForUser(
Settings.System.SCREEN_OFF_TIMEOUT,
KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT,
- userId
+ userId,
)
.toLong()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 0b36e68..91ca2ca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -17,9 +17,10 @@
package com.android.systemui.shade
import android.content.Context
-import android.content.MutableContextWrapper
import android.content.res.Resources
import android.view.LayoutInflater
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE
import com.android.systemui.CoreStartable
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.common.ui.ConfigurationStateImpl
@@ -29,9 +30,12 @@
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl
+import com.android.systemui.shade.display.ShadeDisplayPolicyModule
import com.android.systemui.shade.domain.interactor.ShadeDisplaysInteractor
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
@@ -56,7 +60,7 @@
* By using this dedicated module, we ensure the notification shade window always utilizes the
* correct display context and resources, regardless of the display it's on.
*/
-@Module(includes = [OptionalShadeDisplayAwareBindings::class])
+@Module(includes = [OptionalShadeDisplayAwareBindings::class, ShadeDisplayPolicyModule::class])
object ShadeDisplayAwareModule {
/** Creates a new context for the shade window. */
@@ -65,7 +69,9 @@
@SysUISingleton
fun provideShadeDisplayAwareContext(context: Context): Context {
return if (ShadeWindowGoesAround.isEnabled) {
- MutableContextWrapper(context)
+ context
+ .createWindowContext(context.display, TYPE_NOTIFICATION_SHADE, /* options= */ null)
+ .apply { setTheme(R.style.Theme_SystemUI) }
} else {
context
}
@@ -74,6 +80,20 @@
@Provides
@ShadeDisplayAware
@SysUISingleton
+ fun provideShadeWindowManager(
+ defaultWindowManager: WindowManager,
+ @ShadeDisplayAware context: Context,
+ ): WindowManager {
+ return if (ShadeWindowGoesAround.isEnabled) {
+ context.getSystemService(WindowManager::class.java) as WindowManager
+ } else {
+ defaultWindowManager
+ }
+ }
+
+ @Provides
+ @ShadeDisplayAware
+ @SysUISingleton
fun provideShadeDisplayAwareResources(@ShadeDisplayAware context: Context): Resources {
return context.resources
}
@@ -163,6 +183,15 @@
return impl
}
+ @SysUISingleton
+ @Provides
+ fun provideMutableShadePositionRepository(
+ impl: ShadeDisplaysRepositoryImpl
+ ): MutableShadeDisplaysRepository {
+ ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+ return impl
+ }
+
@Provides
@IntoMap
@ClassKey(ShadePrimaryDisplayCommand::class)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
index a5d9e96..a54f6b9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
@@ -20,11 +20,14 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.display.data.repository.DisplayRepository
-import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
+import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
+import com.android.systemui.shade.display.ShadeDisplayPolicy
+import com.android.systemui.shade.display.SpecificDisplayIdPolicy
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import java.io.PrintWriter
import javax.inject.Inject
+import kotlin.text.toIntOrNull
@SysUISingleton
class ShadePrimaryDisplayCommand
@@ -32,7 +35,9 @@
constructor(
private val commandRegistry: CommandRegistry,
private val displaysRepository: DisplayRepository,
- private val positionRepository: ShadeDisplaysRepository,
+ private val positionRepository: MutableShadeDisplaysRepository,
+ private val policies: Set<@JvmSuppressWildcards ShadeDisplayPolicy>,
+ private val defaultPolicy: ShadeDisplayPolicy,
) : Command, CoreStartable {
override fun start() {
@@ -40,8 +45,11 @@
}
override fun help(pw: PrintWriter) {
- pw.println("shade_display_override <displayId> ")
- pw.println("Set the display which is holding the shade.")
+ pw.println("shade_display_override (<displayId>|<policyName>) ")
+ pw.println("Set the display which is holding the shade, or the policy that defines it.")
+ pw.println()
+ pw.println("shade_display_override policies")
+ pw.println("Lists available policies")
pw.println()
pw.println("shade_display_override reset ")
pw.println("Reset the display which is holding the shade.")
@@ -68,21 +76,27 @@
"reset" -> reset()
"list",
"status" -> printStatus()
+ "policies" -> printPolicies()
"any_external" -> anyExternal()
- else -> {
- val cmdAsInteger = command?.toIntOrNull()
- if (cmdAsInteger != null) {
- changeDisplay(displayId = cmdAsInteger)
- } else {
- help(pw)
- }
+ null -> help(pw)
+ else -> parsePolicy(command)
+ }
+ }
+
+ private fun parsePolicy(policyIdentifier: String) {
+ val displayId = policyIdentifier.toIntOrNull()
+ when {
+ displayId != null -> changeDisplay(displayId = displayId)
+ policies.any { it.name == policyIdentifier } -> {
+ positionRepository.policy.value = policies.first { it.name == policyIdentifier }
}
+ else -> help(pw)
}
}
private fun reset() {
- positionRepository.resetDisplayId()
- pw.println("Reset shade primary display id to ${Display.DEFAULT_DISPLAY}")
+ positionRepository.policy.value = defaultPolicy
+ pw.println("Reset shade display policy to default policy: ${defaultPolicy.name}")
}
private fun printStatus() {
@@ -95,6 +109,15 @@
}
}
+ private fun printPolicies() {
+ val currentPolicyName = positionRepository.policy.value.name
+ pw.println("Available policies: ")
+ policies.forEach {
+ pw.print(" - ${it.name}")
+ pw.println(if (currentPolicyName == it.name) " (Current policy)" else "")
+ }
+ }
+
private fun anyExternal() {
val anyExternalDisplay =
displaysRepository.displays.value.firstOrNull {
@@ -116,7 +139,7 @@
}
private fun setDisplay(id: Int) {
- positionRepository.setDisplayId(id)
+ positionRepository.policy.value = SpecificDisplayIdPolicy(id)
pw.println("New shade primary display id is $id")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
index 71c5658..732d4d1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
@@ -23,14 +23,14 @@
class FakeShadeDisplayRepository : ShadeDisplaysRepository {
private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
- override fun setDisplayId(displayId: Int) {
+ fun setDisplayId(displayId: Int) {
_displayId.value = displayId
}
override val displayId: StateFlow<Int>
get() = _displayId
- override fun resetDisplayId() {
+ fun resetDisplayId() {
_displayId.value = Display.DEFAULT_DISPLAY
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
index 4a95e33..756241e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
@@ -18,37 +18,40 @@
import android.view.Display
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shade.display.ShadeDisplayPolicy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.stateIn
+/** Source of truth for the display currently holding the shade. */
interface ShadeDisplaysRepository {
/** ID of the display which currently hosts the shade */
val displayId: StateFlow<Int>
-
- /**
- * Updates the value of the shade display id stored, emitting to the new display id to every
- * component dependent on the shade display id
- */
- fun setDisplayId(displayId: Int)
-
- /** Resets value of shade primary display to the default display */
- fun resetDisplayId()
}
-/** Source of truth for the display currently holding the shade. */
+/** Allows to change the policy that determines in which display the Shade window is visible. */
+interface MutableShadeDisplaysRepository : ShadeDisplaysRepository {
+ /** Updates the policy to select where the shade is visible. */
+ val policy: MutableStateFlow<ShadeDisplayPolicy>
+}
+
+/** Keeps the policy and propagates the display id for the shade from it. */
@SysUISingleton
-class ShadeDisplaysRepositoryImpl @Inject constructor() : ShadeDisplaysRepository {
- private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
+@OptIn(ExperimentalCoroutinesApi::class)
+class ShadeDisplaysRepositoryImpl
+@Inject
+constructor(defaultPolicy: ShadeDisplayPolicy, @Background bgScope: CoroutineScope) :
+ MutableShadeDisplaysRepository {
+ override val policy = MutableStateFlow<ShadeDisplayPolicy>(defaultPolicy)
- override val displayId: StateFlow<Int>
- get() = _displayId
-
- override fun setDisplayId(displayId: Int) {
- _displayId.value = displayId
- }
-
- override fun resetDisplayId() {
- _displayId.value = Display.DEFAULT_DISPLAY
- }
+ override val displayId: StateFlow<Int> =
+ policy
+ .flatMapLatest { it.displayId }
+ .stateIn(bgScope, SharingStarted.WhileSubscribed(), Display.DEFAULT_DISPLAY)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicy.kt
new file mode 100644
index 0000000..3f6c949
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicy.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.display
+
+import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Returns an external display if one exists, otherwise the default display.
+ *
+ * If there are multiple external displays, the one with minimum display ID is returned.
+ */
+@SysUISingleton
+class AnyExternalShadeDisplayPolicy
+@Inject
+constructor(displayRepository: DisplayRepository, @Background bgScope: CoroutineScope) :
+ ShadeDisplayPolicy {
+ override val name: String
+ get() = "any_external_display"
+
+ override val displayId: StateFlow<Int> =
+ displayRepository.displays
+ .map { displays ->
+ displays
+ .filter { it.displayId != DEFAULT_DISPLAY && it.type in ALLOWED_DISPLAY_TYPES }
+ .minOfOrNull { it.displayId } ?: DEFAULT_DISPLAY
+ }
+ .stateIn(bgScope, SharingStarted.WhileSubscribed(), DEFAULT_DISPLAY)
+
+ private companion object {
+ val ALLOWED_DISPLAY_TYPES =
+ setOf(Display.TYPE_EXTERNAL, Display.TYPE_OVERLAY, Display.TYPE_WIFI)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
new file mode 100644
index 0000000..1b22ee4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.display
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import kotlinx.coroutines.flow.StateFlow
+
+/** Describes the display the shade should be shown in. */
+interface ShadeDisplayPolicy {
+ val name: String
+
+ /** The display id the shade should be at, according to this policy. */
+ val displayId: StateFlow<Int>
+}
+
+@Module
+interface ShadeDisplayPolicyModule {
+ @IntoSet
+ @Binds
+ fun provideDefaultPolicyToSet(impl: DefaultShadeDisplayPolicy): ShadeDisplayPolicy
+
+ @IntoSet
+ @Binds
+ fun provideAnyExternalShadeDisplayPolicyToSet(
+ impl: AnyExternalShadeDisplayPolicy
+ ): ShadeDisplayPolicy
+
+ @Binds fun provideDefaultPolicy(impl: DefaultShadeDisplayPolicy): ShadeDisplayPolicy
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt
new file mode 100644
index 0000000..13e7664
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.display
+
+import android.view.Display
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Policy to specify a display id explicitly. */
+open class SpecificDisplayIdPolicy(displayId: Int) : ShadeDisplayPolicy {
+ override val name: String
+ get() = "display_${displayId}_policy"
+
+ override val displayId: StateFlow<Int> = MutableStateFlow(displayId)
+}
+
+class DefaultShadeDisplayPolicy @Inject constructor() :
+ SpecificDisplayIdPolicy(Display.DEFAULT_DISPLAY)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
index 432d5f5..fb2cbec 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
@@ -16,28 +16,21 @@
package com.android.systemui.shade.domain.interactor
-import android.content.ComponentCallbacks
import android.content.Context
-import android.content.MutableContextWrapper
-import android.content.res.Configuration
-import android.content.res.Resources
import android.util.Log
import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE
+import androidx.annotation.UiThread
import com.android.app.tracing.coroutines.launchTraced
import com.android.app.tracing.traceSection
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
-import com.android.systemui.display.shared.model.DisplayWindowProperties
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.ShadeWindowLayoutParams
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
-import com.android.systemui.statusbar.phone.ConfigurationForwarder
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
import javax.inject.Inject
@@ -53,13 +46,14 @@
optionalShadeRootView: Optional<WindowRootView>,
private val shadePositionRepository: ShadeDisplaysRepository,
@ShadeDisplayAware private val shadeContext: Context,
- @ShadeDisplayAware private val shadeResources: Resources,
- private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+ @ShadeDisplayAware private val wm: WindowManager,
@Background private val bgScope: CoroutineScope,
- @ShadeDisplayAware private val shadeConfigurationForwarder: ConfigurationForwarder,
@Main private val mainThreadContext: CoroutineContext,
) : CoreStartable {
+ private val shadeLayoutParams: WindowManager.LayoutParams =
+ ShadeWindowLayoutParams.create(shadeContext)
+
private val shadeRootView =
optionalShadeRootView.getOrNull()
?: error(
@@ -69,9 +63,6 @@
"""
.trimIndent()
)
- // TODO: b/362719719 - Get rid of this callback as the root view should automatically get the
- // correct configuration once it's moved to another window.
- private var unregisterConfigChangedCallbacks: (() -> Unit)? = null
override fun start() {
ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
@@ -94,7 +85,7 @@
return
}
try {
- moveShadeWindow(fromId = currentId, toId = destinationId)
+ withContext(mainThreadContext) { moveShadeWindow(toId = destinationId) }
} catch (e: IllegalStateException) {
Log.e(
TAG,
@@ -104,68 +95,26 @@
}
}
- private suspend fun moveShadeWindow(fromId: Int, toId: Int) {
- val (_, _, _, sourceWm) = getDisplayWindowProperties(fromId)
- val (_, _, destContext, destWm) = getDisplayWindowProperties(toId)
- withContext(mainThreadContext) {
- traceSection({ "MovingShadeWindow from $fromId to $toId" }) {
- removeShade(sourceWm)
- addShade(destWm)
- overrideContextAndResources(newContext = destContext)
- registerConfigurationChange(destContext)
- }
- traceSection("ShadeDisplaysInteractor#onConfigurationChanged") {
- dispatchConfigurationChanged(destContext.resources.configuration)
- }
+ @UiThread
+ private fun moveShadeWindow(toId: Int) {
+ traceSection({ "moveShadeWindow to $toId" }) {
+ removeShadeWindow()
+ updateContextDisplay(toId)
+ addShadeWindow()
}
}
- private fun removeShade(wm: WindowManager): Unit =
- traceSection("removeView") { wm.removeView(shadeRootView) }
+ @UiThread
+ private fun removeShadeWindow(): Unit =
+ traceSection("removeShadeWindow") { wm.removeView(shadeRootView) }
- private fun addShade(wm: WindowManager): Unit =
- traceSection("addView") {
- wm.addView(shadeRootView, ShadeWindowLayoutParams.create(shadeContext))
- }
+ @UiThread
+ private fun addShadeWindow(): Unit =
+ traceSection("addShadeWindow") { wm.addView(shadeRootView, shadeLayoutParams) }
- private fun overrideContextAndResources(newContext: Context) {
- val contextWrapper =
- shadeContext as? MutableContextWrapper
- ?: error("Shade context is not a MutableContextWrapper!")
- contextWrapper.baseContext = newContext
- // Override needed in case someone is keeping a reference to the resources from the old
- // context.
- // TODO: b/362719719 - This shouldn't be needed, as resources should be updated when the
- // window is moved to the new display automatically.
- shadeResources.impl = shadeContext.resources.impl
- }
-
- private fun dispatchConfigurationChanged(newConfig: Configuration) {
- shadeConfigurationForwarder.onConfigurationChanged(newConfig)
- shadeRootView.dispatchConfigurationChanged(newConfig)
- shadeRootView.requestLayout()
- }
-
- private fun registerConfigurationChange(context: Context) {
- // we should keep only one at the time.
- unregisterConfigChangedCallbacks?.invoke()
- val callback =
- object : ComponentCallbacks {
- override fun onConfigurationChanged(newConfig: Configuration) {
- dispatchConfigurationChanged(newConfig)
- }
-
- override fun onLowMemory() {}
- }
- context.registerComponentCallbacks(callback)
- unregisterConfigChangedCallbacks = {
- context.unregisterComponentCallbacks(callback)
- unregisterConfigChangedCallbacks = null
- }
- }
-
- private fun getDisplayWindowProperties(displayId: Int): DisplayWindowProperties {
- return displayWindowPropertiesRepository.get(displayId, TYPE_NOTIFICATION_SHADE)
+ @UiThread
+ private fun updateContextDisplay(newDisplayId: Int) {
+ traceSection("updateContextDisplay") { shadeContext.updateDisplay(newDisplayId) }
}
private companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index ee8c1ae..42b9d5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -207,5 +207,19 @@
singleDisplayLazy.get()
}
}
+
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(AutoHideControllerStore::class)
+ fun storeAsCoreStartable(
+ multiDisplayLazy: Lazy<MultiDisplayAutoHideControllerStore>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 78ea700..ddcc926 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -57,6 +57,10 @@
addDisplay(display(type, id = displayId))
}
+ suspend fun addDisplays(vararg displays: Display) {
+ displays.forEach { addDisplay(it) }
+ }
+
suspend fun addDisplay(display: Display) {
flow.value += display
displayAdditionEventFlow.emit(display)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
index 007d229..f88ed07 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
@@ -16,18 +16,24 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.internal.widget.lockPatternUtils
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
val Kosmos.keyguardEnabledInteractor by
Kosmos.Fixture {
KeyguardEnabledInteractor(
applicationCoroutineScope,
+ testDispatcher,
keyguardRepository,
biometricSettingsRepository,
- keyguardDismissTransitionInteractor,
+ selectedUserInteractor,
+ lockPatternUtils,
+ { keyguardDismissTransitionInteractor },
internalTransitionInteractor = internalKeyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt
index 39236c7..2423949 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt
@@ -24,5 +24,6 @@
KeyguardLockWhileAwakeInteractor(
biometricSettingsRepository = biometricSettingsRepository,
keyguardEnabledInteractor = keyguardEnabledInteractor,
+ keyguardServiceLockNowInteractor = keyguardServiceLockNowInteractor,
)
}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt
similarity index 68%
rename from ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt
index 7d3d8b9..29335c5 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt
@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.platform.test.ravenwood;
-/** Stub class. The actual implementation is in junit-impl-src. */
-public class RavenwoodConfigState {
- public RavenwoodConfigState(RavenwoodConfig config) {
- }
-}
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+
+val Kosmos.keyguardServiceLockNowInteractor by
+ Kosmos.Fixture { KeyguardServiceLockNowInteractor(backgroundScope = testScope) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
index 63e168d..4aa132c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
@@ -41,5 +41,7 @@
lockPatternUtils,
fakeSettings,
selectedUserInteractor,
+ keyguardEnabledInteractor,
+ keyguardServiceLockNowInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
new file mode 100644
index 0000000..dbaa0b1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.data.repository
+
+import android.view.Display
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.display.ShadeDisplayPolicy
+import com.android.systemui.shade.display.SpecificDisplayIdPolicy
+
+val Kosmos.defaultShadeDisplayPolicy: ShadeDisplayPolicy by
+ Kosmos.Fixture { SpecificDisplayIdPolicy(Display.DEFAULT_DISPLAY) }
+
+val Kosmos.shadeDisplaysRepository: MutableShadeDisplaysRepository by
+ Kosmos.Fixture {
+ ShadeDisplaysRepositoryImpl(
+ defaultPolicy = defaultShadeDisplayPolicy,
+ bgScope = testScope.backgroundScope,
+ )
+ }
diff --git a/packages/Vcn/service-b/Android.bp b/packages/Vcn/service-b/Android.bp
index a462297..03ef4e6 100644
--- a/packages/Vcn/service-b/Android.bp
+++ b/packages/Vcn/service-b/Android.bp
@@ -19,6 +19,19 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+filegroup {
+ name: "vcn-location-sources",
+ srcs: select(release_flag("RELEASE_MOVE_VCN_TO_MAINLINE"), {
+ true: [
+ "vcn-location-flag/module/com/android/server/vcn/VcnLocation.java",
+ ],
+ default: [
+ "vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java",
+ ],
+ }),
+ visibility: ["//frameworks/base/services/core"],
+}
+
java_library {
name: "service-connectivity-b-pre-jarjar",
sdk_version: "system_server_current",
diff --git a/packages/Vcn/service-b/vcn-location-flag/module/com/android/server/vcn/VcnLocation.java b/packages/Vcn/service-b/vcn-location-flag/module/com/android/server/vcn/VcnLocation.java
new file mode 100644
index 0000000..6c7d24d
--- /dev/null
+++ b/packages/Vcn/service-b/vcn-location-flag/module/com/android/server/vcn/VcnLocation.java
@@ -0,0 +1,31 @@
+/*
+ * 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.vcn;
+
+/**
+ * Class to represent that VCN is in a mainline module
+ *
+ * <p>This class is used to check whether VCN is in the non-updatable platform or in a mainline
+ * module.
+ */
+// When VCN is in a mainline module, this class (module/com/android/server/vcn/VcnLocation.java)
+// will be built in to the vcn-location-sources filegroup. When VCN is in the non-updatable
+// platform, platform/com/android/server/vcn/VcnLocation.java will be built in to the filegroup
+public class VcnLocation {
+ /** Indicate that VCN is the platform */
+ public static final boolean IS_VCN_IN_MAINLINE = true;
+}
diff --git a/packages/Vcn/service-b/vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java b/packages/Vcn/service-b/vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java
new file mode 100644
index 0000000..c6c82a5
--- /dev/null
+++ b/packages/Vcn/service-b/vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java
@@ -0,0 +1,32 @@
+/*
+ * 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.vcn;
+
+/**
+ * Class to represent that VCN is in the platform
+ *
+ * <p>This class is used to check whether VCN is in the non-updatable platform or in a mainline
+ * module.
+ */
+// When VCN is in a mainline module, module/com/android/server/vcn/VcnLocation.java
+// will be built in to the vcn-location-sources filegroup. When VCN is in the non-updatable
+// platform, this class (platform/com/android/server/vcn/VcnLocation.java) will be built in to the
+// filegroup
+public class VcnLocation {
+ /** Indicate that VCN is the platform */
+ public static final boolean IS_VCN_IN_MAINLINE = false;
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index 9b71f80..de3c5f2 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -133,9 +133,6 @@
Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName());
- // This is needed to make AndroidJUnit4ClassRunner happy.
- InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
-
// Hook point to allow more customization.
runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null);
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java
deleted file mode 100644
index 870a10a..0000000
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.platform.test.ravenwood;
-
-import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.annotation.Nullable;
-import android.app.ResourcesManager;
-import android.content.res.Resources;
-import android.view.DisplayAdjustments;
-
-import java.io.File;
-import java.util.HashMap;
-
-/**
- * Used to store various states associated with {@link RavenwoodConfig} that's inly needed
- * in junit-impl.
- *
- * We don't want to put it in junit-src to avoid having to recompile all the downstream
- * dependencies after changing this class.
- *
- * All members must be called from the runner's main thread.
- */
-public class RavenwoodConfigState {
- private static final String TAG = "RavenwoodConfigState";
-
- private final RavenwoodConfig mConfig;
-
- // TODO: Move the other contexts from RavenwoodConfig to here too? They're used by
- // RavenwoodRule too, but RavenwoodRule can probably use InstrumentationRegistry?
- RavenwoodContext mSystemServerContext;
-
- public RavenwoodConfigState(RavenwoodConfig config) {
- mConfig = config;
- }
-
- /** Map from path -> resources. */
- private final HashMap<File, Resources> mCachedResources = new HashMap<>();
-
- /**
- * Load {@link Resources} from an APK, with cache.
- */
- public Resources loadResources(@Nullable File apkPath) {
- var cached = mCachedResources.get(apkPath);
- if (cached != null) {
- return cached;
- }
-
- var fileToLoad = apkPath != null ? apkPath : new File(RAVENWOOD_EMPTY_RESOURCES_APK);
-
- assertTrue("File " + fileToLoad + " doesn't exist.", fileToLoad.isFile());
-
- final String path = fileToLoad.getAbsolutePath();
- final var emptyPaths = new String[0];
-
- ResourcesManager.getInstance().initializeApplicationPaths(path, emptyPaths);
-
- final var ret = ResourcesManager.getInstance().getResources(null, path,
- emptyPaths, emptyPaths, emptyPaths,
- emptyPaths, null, null,
- new DisplayAdjustments().getCompatibilityInfo(),
- RavenwoodRuntimeEnvironmentController.class.getClassLoader(), null);
-
- assertNotNull(ret);
-
- mCachedResources.put(apkPath, ret);
- return ret;
- }
-}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
index ec00e8f..4ab1fa1 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
@@ -53,11 +53,6 @@
}
/**
- * The RavenwoodConfig used to configure the current Ravenwood environment.
- * This can either come from mConfig or mRule.
- */
- private RavenwoodConfig mCurrentConfig;
- /**
* The RavenwoodConfig declared in the test class
*/
private RavenwoodConfig mConfig;
@@ -68,10 +63,6 @@
private boolean mHasRavenwoodRule;
private Description mMethodDescription;
- public RavenwoodConfig getConfig() {
- return mCurrentConfig;
- }
-
public void enterTestRunner() {
Log.i(TAG, "enterTestRunner: " + mRunner);
@@ -83,31 +74,19 @@
fail("RavenwoodConfig and RavenwoodRule cannot be used in the same class."
+ " Suggest migrating to RavenwoodConfig.");
}
- mCurrentConfig = mConfig;
- } else if (!mHasRavenwoodRule) {
- // If no RavenwoodConfig and no RavenwoodRule, use a default config
- mCurrentConfig = new RavenwoodConfig.Builder().build();
}
- if (mCurrentConfig != null) {
- RavenwoodRuntimeEnvironmentController.init(mRunner);
- }
+ RavenwoodRuntimeEnvironmentController.initForRunner();
}
public void enterTestClass() {
Log.i(TAG, "enterTestClass: " + mRunner.mTestJavaClass.getName());
-
- if (mCurrentConfig != null) {
- RavenwoodRuntimeEnvironmentController.init(mRunner);
- }
}
public void exitTestClass() {
Log.i(TAG, "exitTestClass: " + mRunner.mTestJavaClass.getName());
try {
- if (mCurrentConfig != null) {
- RavenwoodRuntimeEnvironmentController.reset();
- }
+ RavenwoodRuntimeEnvironmentController.exitTestClass();
} finally {
mConfig = null;
mRule = null;
@@ -116,11 +95,11 @@
public void enterTestMethod(Description description) {
mMethodDescription = description;
+ RavenwoodRuntimeEnvironmentController.initForMethod();
}
public void exitTestMethod() {
mMethodDescription = null;
- RavenwoodRuntimeEnvironmentController.reinit();
}
public void enterRavenwoodRule(RavenwoodRule rule) {
@@ -133,10 +112,7 @@
+ " which is not supported.");
}
mRule = rule;
- if (mCurrentConfig == null) {
- mCurrentConfig = rule.getConfiguration();
- }
- RavenwoodRuntimeEnvironmentController.init(mRunner);
+ RavenwoodRuntimeEnvironmentController.setSystemProperties(rule.mSystemProperties);
}
public void exitRavenwoodRule(RavenwoodRule rule) {
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 979076e..c2ed45d 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -16,8 +16,11 @@
package android.platform.test.ravenwood;
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.UserHandle.SYSTEM;
import static android.platform.test.ravenwood.RavenwoodSystemServer.ANDROID_PACKAGE_NAME;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_RESOURCE_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
@@ -25,7 +28,9 @@
import static com.android.ravenwood.common.RavenwoodCommonUtils.parseNullableInt;
import static com.android.ravenwood.common.RavenwoodCommonUtils.withDefault;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
@@ -53,6 +58,7 @@
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
+import android.view.DisplayAdjustments;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -62,7 +68,6 @@
import com.android.ravenwood.RavenwoodRuntimeNative;
import com.android.ravenwood.RavenwoodRuntimeState;
import com.android.ravenwood.common.RavenwoodCommonUtils;
-import com.android.ravenwood.common.RavenwoodRuntimeException;
import com.android.ravenwood.common.SneakyThrow;
import com.android.server.LocalServices;
import com.android.server.compat.PlatformCompat;
@@ -74,8 +79,10 @@
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
+import java.util.Random;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -85,8 +92,7 @@
import java.util.function.Supplier;
/**
- * Responsible for initializing and de-initializing the environment, according to a
- * {@link RavenwoodConfig}.
+ * Responsible for initializing and the environment.
*/
public class RavenwoodRuntimeEnvironmentController {
private static final String TAG = "RavenwoodRuntimeEnvironmentController";
@@ -113,8 +119,6 @@
private static ScheduledFuture<?> sPendingTimeout;
- private static long sOriginalIdentityToken = -1;
-
/**
* When enabled, attempt to detect uncaught exceptions from background threads.
*/
@@ -147,6 +151,10 @@
return res;
}
+ /** Map from path -> resources. */
+ private static final HashMap<File, Resources> sCachedResources = new HashMap<>();
+ private static Set<String> sAdoptedPermissions = Collections.emptySet();
+
private static final Object sInitializationLock = new Object();
@GuardedBy("sInitializationLock")
@@ -155,15 +163,18 @@
@GuardedBy("sInitializationLock")
private static Throwable sExceptionFromGlobalInit;
- private static RavenwoodAwareTestRunner sRunner;
private static RavenwoodSystemProperties sProps;
private static final int DEFAULT_TARGET_SDK_LEVEL = VERSION_CODES.CUR_DEVELOPMENT;
private static final String DEFAULT_PACKAGE_NAME = "com.android.ravenwoodtests.defaultname";
+ private static final int sMyPid = new Random().nextInt(100, 32768);
private static int sTargetSdkLevel;
private static String sTestPackageName;
private static String sTargetPackageName;
+ private static Instrumentation sInstrumentation;
+ private static final long sCallingIdentity =
+ packBinderIdentityToken(false, FIRST_APPLICATION_UID, sMyPid);
/**
* Initialize the global environment.
@@ -182,7 +193,7 @@
Log.e(TAG, "globalInit() failed", th);
sExceptionFromGlobalInit = th;
- throw th;
+ SneakyThrow.sneakyThrow(th);
}
} else {
// Subsequent calls. If the first call threw, just throw the same error, to prevent
@@ -197,10 +208,13 @@
}
}
- private static void globalInitInner() {
+ private static void globalInitInner() throws IOException {
if (RAVENWOOD_VERBOSE_LOGGING) {
Log.v(TAG, "globalInit() called here...", new RuntimeException("NOT A CRASH"));
}
+ if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
+ Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
+ }
// Some process-wide initialization. (maybe redirect stdout/stderr)
RavenwoodCommonUtils.loadJniLibrary(LIBRAVENWOOD_INITIALIZER_NAME);
@@ -251,6 +265,74 @@
loadRavenwoodProperties();
assertMockitoVersion();
+
+ Log.i(TAG, "TargetPackageName=" + sTargetPackageName);
+ Log.i(TAG, "TestPackageName=" + sTestPackageName);
+ Log.i(TAG, "TargetSdkLevel=" + sTargetSdkLevel);
+
+ RavenwoodRuntimeState.sUid = FIRST_APPLICATION_UID;
+ RavenwoodRuntimeState.sPid = sMyPid;
+ RavenwoodRuntimeState.sTargetSdkLevel = sTargetSdkLevel;
+
+ ServiceManager.init$ravenwood();
+ LocalServices.removeAllServicesForTest();
+
+ ActivityManager.init$ravenwood(SYSTEM.getIdentifier());
+
+ final var main = new HandlerThread(MAIN_THREAD_NAME);
+ main.start();
+ Looper.setMainLooperForTest(main.getLooper());
+
+ final boolean isSelfInstrumenting =
+ Objects.equals(sTestPackageName, sTargetPackageName);
+
+ // This will load the resources from the apk set to `resource_apk` in the build file.
+ // This is supposed to be the "target app"'s resources.
+ final Supplier<Resources> targetResourcesLoader = () -> {
+ var file = new File(RAVENWOOD_RESOURCE_APK);
+ return loadResources(file.exists() ? file : null);
+ };
+
+ // Set up test context's (== instrumentation context's) resources.
+ // If the target package name == test package name, then we use the main resources.
+ final Supplier<Resources> instResourcesLoader;
+ if (isSelfInstrumenting) {
+ instResourcesLoader = targetResourcesLoader;
+ } else {
+ instResourcesLoader = () -> {
+ var file = new File(RAVENWOOD_INST_RESOURCE_APK);
+ return loadResources(file.exists() ? file : null);
+ };
+ }
+
+ var instContext = new RavenwoodContext(
+ sTestPackageName, main, instResourcesLoader);
+ var targetContext = new RavenwoodContext(
+ sTargetPackageName, main, targetResourcesLoader);
+
+ // Set up app context.
+ var appContext = new RavenwoodContext(sTargetPackageName, main, targetResourcesLoader);
+ appContext.setApplicationContext(appContext);
+ if (isSelfInstrumenting) {
+ instContext.setApplicationContext(appContext);
+ targetContext.setApplicationContext(appContext);
+ } else {
+ // When instrumenting into another APK, the test context doesn't have an app context.
+ targetContext.setApplicationContext(appContext);
+ }
+
+ final Supplier<Resources> systemResourcesLoader = () -> loadResources(null);
+
+ var systemServerContext =
+ new RavenwoodContext(ANDROID_PACKAGE_NAME, main, systemResourcesLoader);
+
+ sInstrumentation = new Instrumentation();
+ sInstrumentation.basicInit(instContext, targetContext, null);
+ InstrumentationRegistry.registerInstance(sInstrumentation, Bundle.EMPTY);
+
+ RavenwoodSystemServer.init(systemServerContext);
+
+ initializeCompatIds();
}
private static void loadRavenwoodProperties() {
@@ -265,134 +347,38 @@
}
/**
- * Initialize the environment.
+ * Partially reset and initialize before each test class invocation
*/
- public static void init(RavenwoodAwareTestRunner runner) {
- if (RAVENWOOD_VERBOSE_LOGGING) {
- Log.v(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
- }
- if (sRunner == runner) {
- return;
- }
- if (sRunner != null) {
- reset();
- }
- sRunner = runner;
- try {
- initInner(runner.mState.getConfig());
- } catch (Exception th) {
- Log.e(TAG, "init() failed", th);
+ public static void initForRunner() {
+ var targetContext = sInstrumentation.getTargetContext();
+ var instContext = sInstrumentation.getContext();
+ // We need to recreate the mock UiAutomation for each test class, because sometimes tests
+ // will call Mockito.framework().clearInlineMocks() after execution.
+ sInstrumentation.basicInit(instContext, targetContext, createMockUiAutomation());
- RavenwoodCommonUtils.runIgnoringException(()-> reset());
-
- SneakyThrow.sneakyThrow(th);
- }
- }
-
- private static void initInner(RavenwoodConfig config) throws IOException {
- if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
- maybeThrowPendingUncaughtException(false);
- Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
- }
-
- config.mTargetPackageName = sTargetPackageName;
- config.mTestPackageName = sTestPackageName;
- config.mTargetSdkLevel = sTargetSdkLevel;
-
- Log.i(TAG, "TargetPackageName=" + sTargetPackageName);
- Log.i(TAG, "TestPackageName=" + sTestPackageName);
- Log.i(TAG, "TargetSdkLevel=" + sTargetSdkLevel);
-
- RavenwoodRuntimeState.sUid = config.mUid;
- RavenwoodRuntimeState.sPid = config.mPid;
- RavenwoodRuntimeState.sTargetSdkLevel = config.mTargetSdkLevel;
- sOriginalIdentityToken = Binder.clearCallingIdentity();
- reinit();
- setSystemProperties(config.mSystemProperties);
-
- ServiceManager.init$ravenwood();
- LocalServices.removeAllServicesForTest();
-
- ActivityManager.init$ravenwood(config.mCurrentUser);
-
- final var main = new HandlerThread(MAIN_THREAD_NAME);
- main.start();
- Looper.setMainLooperForTest(main.getLooper());
-
- final boolean isSelfInstrumenting =
- Objects.equals(config.mTestPackageName, config.mTargetPackageName);
-
- // This will load the resources from the apk set to `resource_apk` in the build file.
- // This is supposed to be the "target app"'s resources.
- final Supplier<Resources> targetResourcesLoader = () -> {
- var file = new File(RAVENWOOD_RESOURCE_APK);
- return config.mState.loadResources(file.exists() ? file : null);
- };
-
- // Set up test context's (== instrumentation context's) resources.
- // If the target package name == test package name, then we use the main resources.
- final Supplier<Resources> instResourcesLoader;
- if (isSelfInstrumenting) {
- instResourcesLoader = targetResourcesLoader;
- } else {
- instResourcesLoader = () -> {
- var file = new File(RAVENWOOD_INST_RESOURCE_APK);
- return config.mState.loadResources(file.exists() ? file : null);
- };
- }
-
- var instContext = new RavenwoodContext(
- config.mTestPackageName, main, instResourcesLoader);
- var targetContext = new RavenwoodContext(
- config.mTargetPackageName, main, targetResourcesLoader);
-
- // Set up app context.
- var appContext = new RavenwoodContext(
- config.mTargetPackageName, main, targetResourcesLoader);
- appContext.setApplicationContext(appContext);
- if (isSelfInstrumenting) {
- instContext.setApplicationContext(appContext);
- targetContext.setApplicationContext(appContext);
- } else {
- // When instrumenting into another APK, the test context doesn't have an app context.
- targetContext.setApplicationContext(appContext);
- }
- config.mInstContext = instContext;
- config.mTargetContext = targetContext;
-
- final Supplier<Resources> systemResourcesLoader = () -> config.mState.loadResources(null);
-
- config.mState.mSystemServerContext =
- new RavenwoodContext(ANDROID_PACKAGE_NAME, main, systemResourcesLoader);
-
- // Prepare other fields.
- config.mInstrumentation = new Instrumentation();
- config.mInstrumentation.basicInit(instContext, targetContext, createMockUiAutomation());
- InstrumentationRegistry.registerInstance(config.mInstrumentation, Bundle.EMPTY);
-
- RavenwoodSystemServer.init(config);
-
- initializeCompatIds(config);
+ Process_ravenwood.reset();
+ DeviceConfig_host.reset();
+ Binder.restoreCallingIdentity(sCallingIdentity);
if (ENABLE_TIMEOUT_STACKS) {
sPendingTimeout = sTimeoutExecutor.schedule(
RavenwoodRuntimeEnvironmentController::dumpStacks,
TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
- }
-
- /**
- * Partially re-initialize after each test method invocation
- */
- public static void reinit() {
- // sRunner could be null, if there was a failure in the initialization.
- if (sRunner != null) {
- var config = sRunner.mState.getConfig();
- Binder.restoreCallingIdentity(packBinderIdentityToken(false, config.mUid, config.mPid));
+ if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
+ maybeThrowPendingUncaughtException(false);
}
}
- private static void initializeCompatIds(RavenwoodConfig config) {
+ /**
+ * Partially reset and initialize before each test method invocation
+ */
+ public static void initForMethod() {
+ // TODO(b/375272444): this is a hacky workaround to ensure binder identity
+ Binder.restoreCallingIdentity(sCallingIdentity);
+ }
+
+ private static void initializeCompatIds() {
// Set up compat-IDs for the app side.
// TODO: Inside the system server, all the compat-IDs should be enabled,
// Due to the `AppCompatCallbacks.install(new long[0], new long[0])` call in
@@ -400,8 +386,8 @@
// Compat framework only uses the package name and the target SDK level.
ApplicationInfo appInfo = new ApplicationInfo();
- appInfo.packageName = config.mTargetPackageName;
- appInfo.targetSdkVersion = config.mTargetSdkLevel;
+ appInfo.packageName = sTargetPackageName;
+ appInfo.targetSdkVersion = sTargetSdkLevel;
PlatformCompat platformCompat = null;
try {
@@ -418,65 +404,42 @@
}
/**
- * De-initialize.
- *
- * Note, we call this method when init() fails too, so this method should deal with
- * any partially-initialized states.
+ * Load {@link Resources} from an APK, with cache.
*/
- public static void reset() {
- if (RAVENWOOD_VERBOSE_LOGGING) {
- Log.v(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
+ private static Resources loadResources(@Nullable File apkPath) {
+ var cached = sCachedResources.get(apkPath);
+ if (cached != null) {
+ return cached;
}
- if (sRunner == null) {
- throw new RavenwoodRuntimeException("Internal error: reset() already called");
- }
- var config = sRunner.mState.getConfig();
- sRunner = null;
+ var fileToLoad = apkPath != null ? apkPath : new File(RAVENWOOD_EMPTY_RESOURCES_APK);
+
+ assertTrue("File " + fileToLoad + " doesn't exist.", fileToLoad.isFile());
+
+ final String path = fileToLoad.getAbsolutePath();
+ final var emptyPaths = new String[0];
+
+ ResourcesManager.getInstance().initializeApplicationPaths(path, emptyPaths);
+
+ final var ret = ResourcesManager.getInstance().getResources(null, path,
+ emptyPaths, emptyPaths, emptyPaths,
+ emptyPaths, null, null,
+ new DisplayAdjustments().getCompatibilityInfo(),
+ RavenwoodRuntimeEnvironmentController.class.getClassLoader(), null);
+
+ assertNotNull(ret);
+
+ sCachedResources.put(apkPath, ret);
+ return ret;
+ }
+
+ /**
+ * A callback when a test class finishes its execution, mostly only for debugging.
+ */
+ public static void exitTestClass() {
if (ENABLE_TIMEOUT_STACKS) {
sPendingTimeout.cancel(false);
}
-
- RavenwoodSystemServer.reset(config);
-
- InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
- config.mInstrumentation = null;
- if (config.mInstContext != null) {
- ((RavenwoodContext) config.mInstContext).cleanUp();
- config.mInstContext = null;
- }
- if (config.mTargetContext != null) {
- ((RavenwoodContext) config.mTargetContext).cleanUp();
- config.mTargetContext = null;
- }
- if (config.mState.mSystemServerContext != null) {
- config.mState.mSystemServerContext.cleanUp();
- }
-
- if (Looper.getMainLooper() != null) {
- Looper.getMainLooper().quit();
- }
- Looper.clearMainLooperForTest();
-
- ActivityManager.reset$ravenwood();
-
- LocalServices.removeAllServicesForTest();
- ServiceManager.reset$ravenwood();
-
- setSystemProperties(null);
- if (sOriginalIdentityToken != -1) {
- Binder.restoreCallingIdentity(sOriginalIdentityToken);
- }
- RavenwoodRuntimeState.reset();
- Process_ravenwood.reset();
- DeviceConfig_host.reset();
-
- try {
- ResourcesManager.setInstance(null); // Better structure needed.
- } catch (Exception e) {
- // AOSP-CHANGE: AOSP doesn't support resources yet.
- }
-
if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
maybeThrowPendingUncaughtException(true);
}
@@ -524,7 +487,7 @@
/**
* Set the current configuration to the actual SystemProperties.
*/
- private static void setSystemProperties(@Nullable RavenwoodSystemProperties systemProperties) {
+ public static void setSystemProperties(@Nullable RavenwoodSystemProperties systemProperties) {
SystemProperties.clearChangeCallbacksForTest();
RavenwoodRuntimeNative.clearSystemProperties();
if (systemProperties == null) systemProperties = new RavenwoodSystemProperties();
@@ -558,28 +521,28 @@
// TODO: use the real UiAutomation class instead of a mock
private static UiAutomation createMockUiAutomation() {
- final Set[] adoptedPermission = { Collections.emptySet() };
+ sAdoptedPermissions = Collections.emptySet();
var mock = mock(UiAutomation.class, inv -> {
HostTestUtils.onThrowMethodCalled();
return null;
});
doAnswer(inv -> {
- adoptedPermission[0] = UiAutomation.ALL_PERMISSIONS;
+ sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS;
return null;
}).when(mock).adoptShellPermissionIdentity();
doAnswer(inv -> {
if (inv.getArgument(0) == null) {
- adoptedPermission[0] = UiAutomation.ALL_PERMISSIONS;
+ sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS;
} else {
- adoptedPermission[0] = Set.of(inv.getArguments());
+ sAdoptedPermissions = (Set) Set.of(inv.getArguments());
}
return null;
}).when(mock).adoptShellPermissionIdentity(any());
doAnswer(inv -> {
- adoptedPermission[0] = Collections.emptySet();
+ sAdoptedPermissions = Collections.emptySet();
return null;
}).when(mock).dropShellPermissionIdentity();
- doAnswer(inv -> adoptedPermission[0]).when(mock).getAdoptedShellPermissions();
+ doAnswer(inv -> sAdoptedPermissions).when(mock).getAdoptedShellPermissions();
return mock;
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
index 438a2bf..3346635 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
@@ -33,7 +33,7 @@
import com.android.server.compat.PlatformCompatNative;
import com.android.server.utils.TimingsTraceAndSlog;
-import java.util.List;
+import java.util.Collection;
import java.util.Set;
public class RavenwoodSystemServer {
@@ -68,27 +68,24 @@
private static TimingsTraceAndSlog sTimings;
private static SystemServiceManager sServiceManager;
- public static void init(RavenwoodConfig config) {
+ public static void init(Context systemServerContext) {
// Always start PlatformCompat, regardless of the requested services.
// PlatformCompat is not really a SystemService, so it won't receive boot phases / etc.
// This initialization code is copied from SystemServer.java.
- PlatformCompat platformCompat = new PlatformCompat(config.mState.mSystemServerContext);
+ PlatformCompat platformCompat = new PlatformCompat(systemServerContext);
ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE, platformCompat);
ServiceManager.addService(Context.PLATFORM_COMPAT_NATIVE_SERVICE,
new PlatformCompatNative(platformCompat));
- // Avoid overhead if no services required
- if (config.mServicesRequired.isEmpty()) return;
-
sStartedServices = new ArraySet<>();
sTimings = new TimingsTraceAndSlog();
- sServiceManager = new SystemServiceManager(config.mState.mSystemServerContext);
+ sServiceManager = new SystemServiceManager(systemServerContext);
sServiceManager.setStartInfo(false,
SystemClock.elapsedRealtime(),
SystemClock.uptimeMillis());
LocalServices.addService(SystemServiceManager.class, sServiceManager);
- startServices(config.mServicesRequired);
+ startServices(sKnownServices.keySet());
sServiceManager.sealStartedServices();
// TODO: expand to include additional boot phases when relevant
@@ -96,7 +93,7 @@
sServiceManager.startBootPhase(sTimings, SystemService.PHASE_BOOT_COMPLETED);
}
- public static void reset(RavenwoodConfig config) {
+ public static void reset() {
// TODO: consider introducing shutdown boot phases
LocalServices.removeServiceForTest(SystemServiceManager.class);
@@ -105,7 +102,7 @@
sStartedServices = null;
}
- private static void startServices(List<Class<?>> serviceClasses) {
+ private static void startServices(Collection<Class<?>> serviceClasses) {
for (Class<?> serviceClass : serviceClasses) {
// Quietly ignore duplicate requests if service already started
if (sStartedServices.contains(serviceClass)) continue;
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index 7ca9239..3ed0f50 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -15,21 +15,13 @@
*/
package android.platform.test.ravenwood;
-import static android.os.Process.FIRST_APPLICATION_UID;
-import static android.os.UserHandle.SYSTEM;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Instrumentation;
-import android.content.Context;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* @deprecated This class will be removed. Reach out to g/ravenwood if you need any features in it.
@@ -45,37 +37,10 @@
public @interface Config {
}
- private static final int NOBODY_UID = 9999;
-
- private static final AtomicInteger sNextPid = new AtomicInteger(100);
-
- int mCurrentUser = SYSTEM.getIdentifier();
-
- /**
- * Unless the test author requests differently, run as "nobody", and give each collection of
- * tests its own unique PID.
- */
- int mUid = FIRST_APPLICATION_UID;
- int mPid = sNextPid.getAndIncrement();
-
- String mTestPackageName;
- String mTargetPackageName;
-
- int mTargetSdkLevel;
-
- final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
-
- final List<Class<?>> mServicesRequired = new ArrayList<>();
-
- volatile Context mInstContext;
- volatile Context mTargetContext;
- volatile Instrumentation mInstrumentation;
-
/**
* Stores internal states / methods associated with this config that's only needed in
* junit-impl.
*/
- final RavenwoodConfigState mState = new RavenwoodConfigState(this);
private RavenwoodConfig() {
}
@@ -159,34 +124,11 @@
return this;
}
- Builder setSystemPropertyImmutableReal(@NonNull String key,
- @Nullable Object value) {
- mConfig.mSystemProperties.setValue(key, value);
- mConfig.mSystemProperties.setAccessReadOnly(key);
- return this;
- }
-
- Builder setSystemPropertyMutableReal(@NonNull String key,
- @Nullable Object value) {
- mConfig.mSystemProperties.setValue(key, value);
- mConfig.mSystemProperties.setAccessReadWrite(key);
- return this;
- }
-
/**
- * Configure the set of system services that are required for this test to operate.
- *
- * For example, passing {@code android.hardware.SerialManager.class} as an argument will
- * ensure that the underlying service is created, initialized, and ready to use for the
- * duration of the test. The {@code SerialManager} instance can be obtained via
- * {@code RavenwoodRule.getContext()} and {@code Context.getSystemService()}, and
- * {@code SerialManagerInternal} can be obtained via {@code LocalServices.getService()}.
+ * @deprecated no longer used. All supported services are available.
*/
+ @Deprecated
public Builder setServicesRequired(@NonNull Class<?>... services) {
- mConfig.mServicesRequired.clear();
- for (Class<?> service : services) {
- mConfig.mServicesRequired.add(service);
- }
return this;
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 5681a90..6262ad1 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -92,19 +92,11 @@
}
}
- private final RavenwoodConfig mConfiguration;
-
- public RavenwoodRule() {
- mConfiguration = new RavenwoodConfig.Builder().build();
- }
-
- private RavenwoodRule(RavenwoodConfig config) {
- mConfiguration = config;
- }
+ final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
public static class Builder {
- private final RavenwoodConfig.Builder mBuilder =
- new RavenwoodConfig.Builder();
+
+ private final RavenwoodRule mRule = new RavenwoodRule();
public Builder() {
}
@@ -152,7 +144,8 @@
* Has no effect on non-Ravenwood environments.
*/
public Builder setSystemPropertyImmutable(@NonNull String key, @Nullable Object value) {
- mBuilder.setSystemPropertyImmutableReal(key, value);
+ mRule.mSystemProperties.setValue(key, value);
+ mRule.mSystemProperties.setAccessReadOnly(key);
return this;
}
@@ -167,26 +160,21 @@
* Has no effect on non-Ravenwood environments.
*/
public Builder setSystemPropertyMutable(@NonNull String key, @Nullable Object value) {
- mBuilder.setSystemPropertyMutableReal(key, value);
+ mRule.mSystemProperties.setValue(key, value);
+ mRule.mSystemProperties.setAccessReadWrite(key);
return this;
}
/**
- * Configure the set of system services that are required for this test to operate.
- *
- * For example, passing {@code android.hardware.SerialManager.class} as an argument will
- * ensure that the underlying service is created, initialized, and ready to use for the
- * duration of the test. The {@code SerialManager} instance can be obtained via
- * {@code RavenwoodRule.getContext()} and {@code Context.getSystemService()}, and
- * {@code SerialManagerInternal} can be obtained via {@code LocalServices.getService()}.
+ * @deprecated no longer used. All supported services are available.
*/
+ @Deprecated
public Builder setServicesRequired(@NonNull Class<?>... services) {
- mBuilder.setServicesRequired(services);
return this;
}
public RavenwoodRule build() {
- return new RavenwoodRule(mBuilder.build());
+ return mRule;
}
}
@@ -227,7 +215,7 @@
@Override
public Statement apply(Statement base, Description description) {
- if (!RavenwoodConfig.isOnRavenwood()) {
+ if (!IS_ON_RAVENWOOD) {
return base;
}
return new Statement() {
@@ -296,8 +284,4 @@
public static RavenwoodPrivate private$ravenwood() {
return sRavenwoodPrivate;
}
-
- RavenwoodConfig getConfiguration() {
- return mConfiguration;
- }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index d6fc6e4..7ef6aac 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -1180,18 +1180,8 @@
}
private boolean anyServiceWantsGenericMotionEvent(MotionEvent event) {
- if (Flags.alwaysAllowObservingTouchEvents()) {
- final boolean isTouchEvent = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN);
- if (isTouchEvent && !canShareGenericTouchEvent()) {
- return false;
- }
- final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
- return (mCombinedGenericMotionEventSources & eventSourceWithoutClass) != 0;
- }
- // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing
- // touch exploration.
- if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)
- && (mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
+ final boolean isTouchEvent = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN);
+ if (isTouchEvent && !canShareGenericTouchEvent()) {
return false;
}
final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
@@ -1199,21 +1189,8 @@
}
private boolean anyServiceWantsToObserveMotionEvent(MotionEvent event) {
- if (Flags.alwaysAllowObservingTouchEvents()) {
- final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
- return (mCombinedMotionEventObservedSources & eventSourceWithoutClass) != 0;
- }
- // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing
- // touch exploration.
- if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)
- && (mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
- return false;
- }
final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
- return (mCombinedGenericMotionEventSources
- & mCombinedMotionEventObservedSources
- & eventSourceWithoutClass)
- != 0;
+ return (mCombinedMotionEventObservedSources & eventSourceWithoutClass) != 0;
}
private boolean canShareGenericTouchEvent() {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 47e0b0d..71a0fc4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -6541,8 +6541,7 @@
// Only continue setting up the packages if the service has been initialized.
// See: b/340927041
- if (Flags.skipPackageChangeBeforeUserSwitch()
- && !mManagerService.isServiceInitializedLocked()) {
+ if (!mManagerService.isServiceInitializedLocked()) {
Slog.w(LOG_TAG,
"onSomePackagesChanged: service not initialized, skip the callback.");
return;
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index e273c68..45106f5 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -257,6 +257,11 @@
Flags::displayListenerPerformanceImprovements
);
+ private final FlagState mSubscribeGranularDisplayEvents = new FlagState(
+ Flags.FLAG_SUBSCRIBE_GRANULAR_DISPLAY_EVENTS,
+ Flags::subscribeGranularDisplayEvents
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -552,6 +557,13 @@
}
/**
+ * @return {@code true} if the flag for subscribing to granular display events is enabled
+ */
+ public boolean isSubscribeGranularDisplayEventsEnabled() {
+ return mSubscribeGranularDisplayEvents.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -605,6 +617,7 @@
pw.println(" " + mGetSupportedRefreshRatesFlagState);
pw.println(" " + mEnablePluginManagerFlagState);
pw.println(" " + mDisplayListenerPerformanceImprovementsFlagState);
+ pw.println(" " + mSubscribeGranularDisplayEvents);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index e7ea868..3976d01 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -478,3 +478,14 @@
bug: "378385869"
is_fixed_read_only: true
}
+
+flag {
+ name: "subscribe_granular_display_events"
+ namespace: "display_manager"
+ description: "Enable subscription to granular display change events."
+ bug: "379250634"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 71f67d8..aba15c8 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -34,7 +34,9 @@
import android.content.pm.PackageManager;
import android.hardware.power.ChannelConfig;
import android.hardware.power.CpuHeadroomParams;
+import android.hardware.power.CpuHeadroomResult;
import android.hardware.power.GpuHeadroomParams;
+import android.hardware.power.GpuHeadroomResult;
import android.hardware.power.IPower;
import android.hardware.power.SessionConfig;
import android.hardware.power.SessionTag;
@@ -79,6 +81,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
@@ -96,10 +99,10 @@
private static final int EVENT_CLEAN_UP_UID = 3;
@VisibleForTesting static final int CLEAN_UP_UID_DELAY_MILLIS = 1000;
+ // The minimum interval between the headroom calls as rate limiting.
private static final int DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS = 1000;
private static final int DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS = 1000;
private static final int HEADROOM_INTERVAL_UNSUPPORTED = -1;
- @VisibleForTesting static final int DEFAULT_HEADROOM_PID = -1;
@VisibleForTesting final long mHintSessionPreferredRate;
@@ -184,73 +187,77 @@
private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint";
private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
private static final String PROPERTY_USE_HAL_HEADROOMS = "persist.hms.use_hal_headrooms";
+ private static final String PROPERTY_CHECK_HEADROOM_TID = "persist.hms.check_headroom_tid";
private Boolean mFMQUsesIntegratedEventFlag = false;
private final Object mCpuHeadroomLock = new Object();
- private static class CpuHeadroomCacheItem {
- long mExpiredTimeMillis;
- CpuHeadroomParamsInternal mParams;
- float[] mHeadroom;
- long mPid;
- CpuHeadroomCacheItem(long expiredTimeMillis, CpuHeadroomParamsInternal params,
- float[] headroom, long pid) {
- mExpiredTimeMillis = expiredTimeMillis;
- mParams = params;
- mPid = pid;
- mHeadroom = headroom;
- }
+ // this cache tracks the expiration time of the items and performs cleanup on lookup
+ private static class HeadroomCache<K, V> {
+ final List<HeadroomCacheItem> mItemList;
+ final Map<K, HeadroomCacheItem> mKeyItemMap;
+ final long mItemExpDurationMillis;
- private boolean match(CpuHeadroomParamsInternal params, long pid) {
- if (mParams == null && params == null) return true;
- if (mParams != null) {
- return mParams.equals(params) && pid == mPid;
+ class HeadroomCacheItem {
+ long mExpTime;
+ K mKey;
+ V mValue;
+
+ HeadroomCacheItem(K k, V v) {
+ mExpTime = System.currentTimeMillis() + mItemExpDurationMillis;
+ mKey = k;
+ mValue = v;
}
- return false;
+
+ boolean isExpired() {
+ return mExpTime <= System.currentTimeMillis();
+ }
}
- private boolean isExpired() {
- return System.currentTimeMillis() > mExpiredTimeMillis;
+ void add(K key, V value) {
+ if (mKeyItemMap.containsKey(key)) {
+ final HeadroomCacheItem item = mKeyItemMap.get(key);
+ mItemList.remove(item);
+ }
+ final HeadroomCacheItem item = new HeadroomCacheItem(key, value);
+ mItemList.add(item);
+ mKeyItemMap.put(key, item);
+ }
+
+ V get(K key) {
+ while (!mItemList.isEmpty() && mItemList.getFirst().isExpired()) {
+ mKeyItemMap.remove(mItemList.removeFirst().mKey);
+ }
+ final HeadroomCacheItem item = mKeyItemMap.get(key);
+ if (item == null) {
+ return null;
+ }
+ return item.mValue;
+ }
+
+ HeadroomCache(int size, long expDurationMillis) {
+ mItemList = new LinkedList<>();
+ mKeyItemMap = new ArrayMap<>(size);
+ mItemExpDurationMillis = expDurationMillis;
}
}
@GuardedBy("mCpuHeadroomLock")
- private final List<CpuHeadroomCacheItem> mCpuHeadroomCache;
+ private final HeadroomCache<CpuHeadroomParams, CpuHeadroomResult> mCpuHeadroomCache;
private final long mCpuHeadroomIntervalMillis;
private final Object mGpuHeadroomLock = new Object();
- private static class GpuHeadroomCacheItem {
- long mExpiredTimeMillis;
- GpuHeadroomParamsInternal mParams;
- float mHeadroom;
-
- GpuHeadroomCacheItem(long expiredTimeMillis, GpuHeadroomParamsInternal params,
- float headroom) {
- mExpiredTimeMillis = expiredTimeMillis;
- mParams = params;
- mHeadroom = headroom;
- }
-
- private boolean match(GpuHeadroomParamsInternal params) {
- if (mParams == null && params == null) return true;
- if (mParams != null) {
- return mParams.equals(params);
- }
- return false;
- }
-
- private boolean isExpired() {
- return System.currentTimeMillis() > mExpiredTimeMillis;
- }
- }
-
@GuardedBy("mGpuHeadroomLock")
- private final List<GpuHeadroomCacheItem> mGpuHeadroomCache;
+ private final HeadroomCache<GpuHeadroomParams, GpuHeadroomResult> mGpuHeadroomCache;
private final long mGpuHeadroomIntervalMillis;
+ // these are set to default values in CpuHeadroomParamsInternal and GpuHeadroomParamsInternal
+ private final int mDefaultCpuHeadroomCalculationWindowMillis;
+ private final int mDefaultGpuHeadroomCalculationWindowMillis;
+
@VisibleForTesting
final IHintManager.Stub mService = new BinderService();
@@ -303,26 +310,31 @@
}
}
mCpuHeadroomIntervalMillis = cpuHeadroomIntervalMillis;
+ mDefaultCpuHeadroomCalculationWindowMillis =
+ new CpuHeadroomParamsInternal().calculationWindowMillis;
+ mDefaultGpuHeadroomCalculationWindowMillis =
+ new GpuHeadroomParamsInternal().calculationWindowMillis;
mGpuHeadroomIntervalMillis = gpuHeadroomIntervalMillis;
if (mCpuHeadroomIntervalMillis > 0) {
- mCpuHeadroomCache = new ArrayList<>(4);
+ mCpuHeadroomCache = new HeadroomCache<>(2, mCpuHeadroomIntervalMillis);
} else {
mCpuHeadroomCache = null;
}
if (mGpuHeadroomIntervalMillis > 0) {
- mGpuHeadroomCache = new ArrayList<>(2);
+ mGpuHeadroomCache = new HeadroomCache<>(2, mGpuHeadroomIntervalMillis);
} else {
mGpuHeadroomCache = null;
}
}
private long checkCpuHeadroomSupport() {
+ final CpuHeadroomParams params = new CpuHeadroomParams();
+ params.tids = new int[]{Process.myPid()};
try {
synchronized (mCpuHeadroomLock) {
- final CpuHeadroomParams defaultParams = new CpuHeadroomParams();
- defaultParams.pid = Process.myPid();
- float[] ret = mPowerHal.getCpuHeadroom(defaultParams);
- if (ret != null && ret.length > 0) {
+ final CpuHeadroomResult ret = mPowerHal.getCpuHeadroom(params);
+ if (ret != null && ret.getTag() == CpuHeadroomResult.globalHeadroom
+ && !Float.isNaN(ret.getGlobalHeadroom())) {
return Math.max(
DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS,
mPowerHal.getCpuHeadroomMinIntervalMillis());
@@ -330,27 +342,29 @@
}
} catch (UnsupportedOperationException e) {
- Slog.w(TAG, "getCpuHeadroom HAL API is not supported", e);
+ Slog.w(TAG, "getCpuHeadroom HAL API is not supported, params: " + params, e);
} catch (RemoteException e) {
- Slog.e(TAG, "getCpuHeadroom HAL API fails, disabling the API", e);
+ Slog.e(TAG, "getCpuHeadroom HAL API fails, disabling the API, params: " + params, e);
}
return HEADROOM_INTERVAL_UNSUPPORTED;
}
private long checkGpuHeadroomSupport() {
+ final GpuHeadroomParams params = new GpuHeadroomParams();
try {
synchronized (mGpuHeadroomLock) {
- float ret = mPowerHal.getGpuHeadroom(new GpuHeadroomParams());
- if (!Float.isNaN(ret)) {
+ final GpuHeadroomResult ret = mPowerHal.getGpuHeadroom(params);
+ if (ret != null && ret.getTag() == GpuHeadroomResult.globalHeadroom && !Float.isNaN(
+ ret.getGlobalHeadroom())) {
return Math.max(
DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS,
mPowerHal.getGpuHeadroomMinIntervalMillis());
}
}
} catch (UnsupportedOperationException e) {
- Slog.w(TAG, "getGpuHeadroom HAL API is not supported", e);
+ Slog.w(TAG, "getGpuHeadroom HAL API is not supported, params: " + params, e);
} catch (RemoteException e) {
- Slog.e(TAG, "getGpuHeadroom HAL API fails, disabling the API", e);
+ Slog.e(TAG, "getGpuHeadroom HAL API fails, disabling the API, params: " + params, e);
}
return HEADROOM_INTERVAL_UNSUPPORTED;
}
@@ -1445,109 +1459,98 @@
}
@Override
- public float[] getCpuHeadroom(@Nullable CpuHeadroomParamsInternal params) {
+ public CpuHeadroomResult getCpuHeadroom(@NonNull CpuHeadroomParamsInternal params) {
if (mCpuHeadroomIntervalMillis <= 0) {
throw new UnsupportedOperationException();
}
- CpuHeadroomParams halParams = new CpuHeadroomParams();
- halParams.pid = Binder.getCallingPid();
- if (params != null) {
- halParams.calculationType = params.calculationType;
- halParams.selectionType = params.selectionType;
- if (params.usesDeviceHeadroom) {
- halParams.pid = DEFAULT_HEADROOM_PID;
+ final CpuHeadroomParams halParams = new CpuHeadroomParams();
+ halParams.tids = new int[]{Binder.getCallingPid()};
+ halParams.calculationType = params.calculationType;
+ halParams.calculationWindowMillis = params.calculationWindowMillis;
+ halParams.selectionType = params.selectionType;
+ if (params.usesDeviceHeadroom) {
+ halParams.tids = new int[]{};
+ } else if (params.tids != null && params.tids.length > 0) {
+ if (params.tids.length > 5) {
+ throw new IllegalArgumentException(
+ "More than 5 TIDs is requested: " + params.tids.length);
}
+ if (SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_TID, true)) {
+ final int tgid = Process.getThreadGroupLeader(Binder.getCallingPid());
+ for (int tid : params.tids) {
+ if (Process.getThreadGroupLeader(tid) != tgid) {
+ throw new SecurityException("TID " + tid
+ + " doesn't belong to the calling process with pid "
+ + tgid);
+ }
+ }
+ }
+ halParams.tids = params.tids;
}
- synchronized (mCpuHeadroomLock) {
- while (!mCpuHeadroomCache.isEmpty()) {
- if (mCpuHeadroomCache.getFirst().isExpired()) {
- mCpuHeadroomCache.removeFirst();
- } else {
- break;
- }
- }
- for (int i = 0; i < mCpuHeadroomCache.size(); ++i) {
- final CpuHeadroomCacheItem item = mCpuHeadroomCache.get(i);
- if (item.match(params, halParams.pid)) {
- item.mExpiredTimeMillis =
- System.currentTimeMillis() + mCpuHeadroomIntervalMillis;
- mCpuHeadroomCache.remove(i);
- mCpuHeadroomCache.add(item);
- return item.mHeadroom;
- }
+ if (halParams.calculationWindowMillis
+ == mDefaultCpuHeadroomCalculationWindowMillis) {
+ synchronized (mCpuHeadroomLock) {
+ final CpuHeadroomResult res = mCpuHeadroomCache.get(halParams);
+ if (res != null) return res;
}
}
// return from HAL directly
try {
- float[] headroom = mPowerHal.getCpuHeadroom(halParams);
- if (headroom == null || headroom.length == 0) {
- Slog.wtf(TAG,
- "CPU headroom from Power HAL is invalid: " + Arrays.toString(headroom));
- return new float[]{Float.NaN};
+ final CpuHeadroomResult result = mPowerHal.getCpuHeadroom(halParams);
+ if (result == null) {
+ Slog.wtf(TAG, "CPU headroom from Power HAL is invalid");
+ return null;
}
- synchronized (mCpuHeadroomLock) {
- mCpuHeadroomCache.add(new CpuHeadroomCacheItem(
- System.currentTimeMillis() + mCpuHeadroomIntervalMillis,
- params, headroom, halParams.pid
- ));
+ if (halParams.calculationWindowMillis
+ == mDefaultCpuHeadroomCalculationWindowMillis) {
+ synchronized (mCpuHeadroomLock) {
+ mCpuHeadroomCache.add(halParams, result);
+ }
}
- return headroom;
-
+ return result;
} catch (RemoteException e) {
- return new float[]{Float.NaN};
+ Slog.e(TAG, "Failed to get CPU headroom from Power HAL", e);
+ return null;
}
}
@Override
- public float getGpuHeadroom(@Nullable GpuHeadroomParamsInternal params) {
+ public GpuHeadroomResult getGpuHeadroom(@NonNull GpuHeadroomParamsInternal params) {
if (mGpuHeadroomIntervalMillis <= 0) {
throw new UnsupportedOperationException();
}
- GpuHeadroomParams halParams = new GpuHeadroomParams();
- if (params != null) {
- halParams.calculationType = params.calculationType;
- }
- synchronized (mGpuHeadroomLock) {
- while (!mGpuHeadroomCache.isEmpty()) {
- if (mGpuHeadroomCache.getFirst().isExpired()) {
- mGpuHeadroomCache.removeFirst();
- } else {
- break;
- }
- }
- for (int i = 0; i < mGpuHeadroomCache.size(); ++i) {
- final GpuHeadroomCacheItem item = mGpuHeadroomCache.get(i);
- if (item.match(params)) {
- item.mExpiredTimeMillis =
- System.currentTimeMillis() + mGpuHeadroomIntervalMillis;
- mGpuHeadroomCache.remove(i);
- mGpuHeadroomCache.add(item);
- return item.mHeadroom;
- }
+ final GpuHeadroomParams halParams = new GpuHeadroomParams();
+ halParams.calculationType = params.calculationType;
+ halParams.calculationWindowMillis = params.calculationWindowMillis;
+ if (halParams.calculationWindowMillis
+ == mDefaultGpuHeadroomCalculationWindowMillis) {
+ synchronized (mGpuHeadroomLock) {
+ final GpuHeadroomResult res = mGpuHeadroomCache.get(halParams);
+ if (res != null) return res;
}
}
// return from HAL directly
try {
- float headroom = mPowerHal.getGpuHeadroom(halParams);
- if (Float.isNaN(headroom)) {
- Slog.wtf(TAG,
- "GPU headroom from Power HAL is NaN");
- return Float.NaN;
+ final GpuHeadroomResult headroom = mPowerHal.getGpuHeadroom(halParams);
+ if (headroom == null) {
+ Slog.wtf(TAG, "GPU headroom from Power HAL is invalid");
+ return null;
}
- synchronized (mGpuHeadroomLock) {
- mGpuHeadroomCache.add(new GpuHeadroomCacheItem(
- System.currentTimeMillis() + mGpuHeadroomIntervalMillis,
- params, headroom
- ));
+ if (halParams.calculationWindowMillis
+ == mDefaultGpuHeadroomCalculationWindowMillis) {
+ synchronized (mGpuHeadroomLock) {
+ mGpuHeadroomCache.add(halParams, headroom);
+ }
}
return headroom;
} catch (RemoteException e) {
- return Float.NaN;
+ Slog.e(TAG, "Failed to get GPU headroom from Power HAL", e);
+ return null;
}
}
@Override
- public long getCpuHeadroomMinIntervalMillis() throws RemoteException {
+ public long getCpuHeadroomMinIntervalMillis() {
if (mCpuHeadroomIntervalMillis <= 0) {
throw new UnsupportedOperationException();
}
@@ -1555,7 +1558,7 @@
}
@Override
- public long getGpuHeadroomMinIntervalMillis() throws RemoteException {
+ public long getGpuHeadroomMinIntervalMillis() {
if (mGpuHeadroomIntervalMillis <= 0) {
throw new UnsupportedOperationException();
}
@@ -1591,17 +1594,23 @@
CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal();
params.selectionType = CpuHeadroomParams.SelectionType.ALL;
params.usesDeviceHeadroom = true;
- pw.println("CPU headroom: " + Arrays.toString(getCpuHeadroom(params)));
+ CpuHeadroomResult ret = getCpuHeadroom(params);
+ pw.println("CPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom()));
params = new CpuHeadroomParamsInternal();
params.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
params.usesDeviceHeadroom = true;
- pw.println("CPU headroom per core: " + Arrays.toString(getCpuHeadroom(params)));
+ ret = getCpuHeadroom(params);
+ pw.println("CPU headroom per core: " + (ret == null ? "N/A"
+ : Arrays.toString(ret.getPerCoreHeadroom())));
} catch (Exception e) {
+ Slog.d(TAG, "Failed to dump CPU headroom", e);
pw.println("CPU headroom: N/A");
}
try {
- pw.println("GPU headroom: " + getGpuHeadroom(null));
+ GpuHeadroomResult ret = getGpuHeadroom(new GpuHeadroomParamsInternal());
+ pw.println("GPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom()));
} catch (Exception e) {
+ Slog.d(TAG, "Failed to dump GPU headroom", e);
pw.println("GPU headroom: N/A");
}
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 79c9ec5..63e8d99 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -26,6 +26,7 @@
import android.os.Handler;
import android.os.Process;
import android.util.Log;
+import android.util.LogWriter;
import android.util.Slog;
import android.util.SparseArray;
@@ -34,6 +35,7 @@
import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -44,6 +46,8 @@
*/
public class BatteryUsageStatsProvider {
private static final String TAG = "BatteryUsageStatsProv";
+ private static final boolean DEBUG = false;
+
private final Context mContext;
private final PowerAttributor mPowerAttributor;
private final PowerStatsStore mPowerStatsStore;
@@ -262,17 +266,25 @@
private BatteryUsageStats getBatteryUsageStats(BatteryStatsImpl stats,
BatteryUsageStatsQuery query, long currentTimeMs) {
+ BatteryUsageStats batteryUsageStats;
if ((query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_ACCUMULATED) != 0) {
- return getAccumulatedBatteryUsageStats(stats, query, currentTimeMs);
+ batteryUsageStats = getAccumulatedBatteryUsageStats(stats, query, currentTimeMs);
} else if (query.getAggregatedToTimestamp() == 0) {
BatteryUsageStats.Builder builder = computeBatteryUsageStats(stats, query,
query.getMonotonicStartTime(),
query.getMonotonicEndTime(), currentTimeMs);
- return builder.build();
+ batteryUsageStats = builder.build();
} else {
- return getAggregatedBatteryUsageStats(stats, query);
+ batteryUsageStats = getAggregatedBatteryUsageStats(stats, query);
}
+ if (DEBUG) {
+ Slog.d(TAG, "query = " + query);
+ PrintWriter pw = new PrintWriter(new LogWriter(Log.DEBUG, TAG));
+ batteryUsageStats.dump(pw, "");
+ pw.flush();
+ }
+ return batteryUsageStats;
}
private BatteryUsageStats getAccumulatedBatteryUsageStats(BatteryStatsImpl stats,
diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
index c9655c6..2cc08c3 100644
--- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
+++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
@@ -46,6 +46,7 @@
import com.android.server.pm.UserManagerInternal;
import com.android.server.security.advancedprotection.features.AdvancedProtectionHook;
import com.android.server.security.advancedprotection.features.AdvancedProtectionProvider;
+import com.android.server.security.advancedprotection.features.DisallowCellular2GAdvancedProtectionHook;
import com.android.server.security.advancedprotection.features.DisallowInstallUnknownSourcesAdvancedProtectionHook;
import com.android.server.security.advancedprotection.features.MemoryTaggingExtensionHook;
@@ -84,6 +85,9 @@
if (android.security.Flags.aapmFeatureMemoryTaggingExtension()) {
mHooks.add(new MemoryTaggingExtensionHook(mContext, enabled));
}
+ if (android.security.Flags.aapmFeatureDisableCellular2g()) {
+ mHooks.add(new DisallowCellular2GAdvancedProtectionHook(mContext, enabled));
+ }
}
// Only for tests
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
new file mode 100644
index 0000000..b9c8d3d
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
@@ -0,0 +1,86 @@
+/*
+ * 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.security.advancedprotection.features;
+
+import static android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY;
+import static android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_CELLULAR_2G;
+
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserManager;
+import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+
+/** @hide */
+public final class DisallowCellular2GAdvancedProtectionHook extends AdvancedProtectionHook {
+ private static final String TAG = "AdvancedProtectionDisallowCellular2G";
+
+ private final AdvancedProtectionFeature mFeature =
+ new AdvancedProtectionFeature(FEATURE_ID_DISALLOW_CELLULAR_2G);
+ private final DevicePolicyManager mDevicePolicyManager;
+ private final TelephonyManager mTelephonyManager;
+
+ public DisallowCellular2GAdvancedProtectionHook(@NonNull Context context, boolean enabled) {
+ super(context, enabled);
+ mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+
+ setPolicy(enabled);
+ }
+
+ @NonNull
+ @Override
+ public AdvancedProtectionFeature getFeature() {
+ return mFeature;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mTelephonyManager.isDataCapable();
+ }
+
+ private void setPolicy(boolean enabled) {
+ Slog.i(TAG, "setPolicy called with " + enabled);
+
+ if (enabled) {
+ Slog.d(TAG, "Setting DISALLOW_CELLULAR_2G_GLOBALLY restriction");
+ mDevicePolicyManager.addUserRestrictionGlobally(
+ ADVANCED_PROTECTION_SYSTEM_ENTITY, UserManager.DISALLOW_CELLULAR_2G);
+ } else {
+ Slog.d(TAG, "Clearing DISALLOW_CELLULAR_2G_GLOBALLY restriction");
+ mDevicePolicyManager.clearUserRestrictionGlobally(
+ ADVANCED_PROTECTION_SYSTEM_ENTITY, UserManager.DISALLOW_CELLULAR_2G);
+ }
+ }
+
+ @Override
+ public void onAdvancedProtectionChanged(boolean enabled) {
+ setPolicy(enabled);
+
+ // Leave 2G disabled even if APM is disabled.
+ if (!enabled) {
+ long oldAllowedTypes =
+ mTelephonyManager.getAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+ long newAllowedTypes = oldAllowedTypes & ~TelephonyManager.NETWORK_CLASS_BITMASK_2G;
+ mTelephonyManager.setAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, newAllowedTypes);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/utils/WatchableImpl.java b/services/core/java/com/android/server/utils/WatchableImpl.java
index 8a04ccf..fec4351 100644
--- a/services/core/java/com/android/server/utils/WatchableImpl.java
+++ b/services/core/java/com/android/server/utils/WatchableImpl.java
@@ -33,6 +33,7 @@
/**
* The list of observers.
*/
+ @GuardedBy("mObservers")
protected final ArrayList<Watcher> mObservers = new ArrayList<>();
/**
@@ -83,7 +84,9 @@
* @return The number of registered observers.
*/
public int registeredObserverCount() {
- return mObservers.size();
+ synchronized (mObservers) {
+ return mObservers.size();
+ }
}
/**
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 82d49fc..112414e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -422,6 +422,8 @@
"com.android.server.wifi.aware.WifiAwareService";
private static final String WIFI_P2P_SERVICE_CLASS =
"com.android.server.wifi.p2p.WifiP2pService";
+ private static final String WIFI_USD_SERVICE_CLASS =
+ "com.android.server.wifi.usd.UsdService";
private static final String CONNECTIVITY_SERVICE_APEX_PATH =
"/apex/com.android.tethering/javalib/service-connectivity.jar";
private static final String CONNECTIVITY_SERVICE_INITIALIZER_CLASS =
@@ -2145,6 +2147,13 @@
mSystemServiceManager.startServiceFromJar(
WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
t.traceEnd();
+ // Start USD service
+ if (android.net.wifi.flags.Flags.usd()) {
+ t.traceBegin("StartUsd");
+ mSystemServiceManager.startServiceFromJar(
+ WIFI_USD_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
+ t.traceEnd();
+ }
}
if (context.getPackageManager().hasSystemFeature(
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index dd7ce21..c831475 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.job;
+import static android.app.job.Flags.FLAG_HANDLE_ABANDONED_JOBS;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
@@ -82,6 +83,7 @@
import android.os.SystemClock;
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -1056,6 +1058,75 @@
/**
* Confirm that
* {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
+ * returns a job with the correct delay for abandoned jobs.
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ public void testGetRescheduleJobForFailure_abandonedJob() {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ final long initialBackoffMs = MINUTE_IN_MILLIS;
+ mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO = 3;
+
+ JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure",
+ createJobInfo()
+ .setBackoffCriteria(initialBackoffMs, JobInfo.BACKOFF_POLICY_LINEAR));
+ assertEquals(JobStatus.NO_EARLIEST_RUNTIME, originalJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, originalJob.getLatestRunTimeElapsed());
+
+ // failure = 1, systemStop = 0, abandoned = 1
+ JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+ assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 2, systemstop = 0, abandoned = 2
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+ assertEquals(nowElapsed + (2 * initialBackoffMs), rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 3, systemstop = 0, abandoned = 3
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+ assertEquals(nowElapsed + (3 * initialBackoffMs), rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 4, systemstop = 0, abandoned = 4
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+ assertEquals(
+ nowElapsed + ((long) Math.scalb((float) initialBackoffMs, 3)),
+ rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 4, systemstop = 1, abandoned = 4
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
+ assertEquals(
+ nowElapsed + ((long) Math.scalb((float) initialBackoffMs, 3)),
+ rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 4, systemStop = 4 / SYSTEM_STOP_TO_FAILURE_RATIO, abandoned = 4
+ for (int i = 0; i < mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO; ++i) {
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.STOP_REASON_SYSTEM_PROCESSING,
+ JobParameters.INTERNAL_STOP_REASON_RTC_UPDATED);
+ }
+ assertEquals(
+ nowElapsed + ((long) Math.scalb((float) initialBackoffMs, 4)),
+ rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+ }
+
+ /**
+ * Confirm that
+ * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
* returns a job that is correctly marked as demoted by the user.
*/
@Test
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 c6a6865..c64973a 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
@@ -706,14 +706,14 @@
// "True" start is nowElapsed + HOUR_IN_MILLIS
nowElapsed + HOUR_IN_MILLIS + adjustmentMs,
nowElapsed + 2 * HOUR_IN_MILLIS,
- 0 /* numFailures */, 0 /* numSystemStops */,
+ 0 /* numFailures */, 0 /* numAbandonedFailures */, 0 /* numSystemStops */,
JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
0, 0);
jsFlex = new JobStatus(jsFlex,
// "True" start is nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS
nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS + adjustmentMs,
nowElapsed + 2 * HOUR_IN_MILLIS,
- 0 /* numFailures */, 0 /* numSystemStops */,
+ 0 /* numFailures */, 0 /* numAbandonedFailures */, 0 /* numSystemStops */,
JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
0, 0);
@@ -726,13 +726,13 @@
jsBasic = new JobStatus(jsBasic,
nowElapsed + 30 * MINUTE_IN_MILLIS,
NO_LATEST_RUNTIME,
- 1 /* numFailures */, 1 /* numSystemStops */,
+ 1 /* numFailures */, 0 /* numAbandonedFailures */, 1 /* numSystemStops */,
JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
0, 0);
jsFlex = new JobStatus(jsFlex,
nowElapsed + 30 * MINUTE_IN_MILLIS,
NO_LATEST_RUNTIME,
- 1 /* numFailures */, 1 /* numSystemStops */,
+ 1 /* numFailures */, 0 /* numAbandonedFailures */, 1 /* numSystemStops */,
JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
0, 0);
@@ -847,21 +847,24 @@
JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
JobStatus js = createJobStatus("time", jb);
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 0,
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2,
+ 0 /* numAbandonedFailures */, /* numSystemStops */ 0,
0, FROZEN_TIME, FROZEN_TIME);
assertEquals(mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 1,
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2,
+ 0 /* numAbandonedFailures */, /* numSystemStops */ 1,
0, FROZEN_TIME, FROZEN_TIME);
assertEquals(2 * mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 10,
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0,
+ 0 /* numAbandonedFailures */, /* numSystemStops */ 10,
0, FROZEN_TIME, FROZEN_TIME);
assertEquals(mFcConfig.MAX_RESCHEDULED_DEADLINE_MS,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
@@ -1092,11 +1095,13 @@
JobInfo.Builder jb = createJob(0);
JobStatus js = createJobStatus("time", jb);
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1, /* numSystemStops */ 0,
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1,
+ /* numAbandonedFailures */ 0, /* numSystemStops */ 0,
0, FROZEN_TIME, FROZEN_TIME);
assertFalse(js.hasFlexibilityConstraint());
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 1,
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0,
+ /* numAbandonedFailures */ 0, /* numSystemStops */ 1,
0, FROZEN_TIME, FROZEN_TIME);
assertFalse(js.hasFlexibilityConstraint());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 2d0f4b6..86101cf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -459,35 +459,35 @@
int numFailures = 1;
int numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// 2+ failures, priority should be lowered as much as possible.
numFailures = 2;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
numFailures = 5;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
numFailures = 8;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
// System stops shouldn't factor in the downgrade.
numSystemStops = 10;
numFailures = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// Less than 2 failures, but job is downgraded.
numFailures = 1;
numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
}
@@ -505,44 +505,44 @@
int numFailures = 1;
int numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
// Failures in [2,4), priority should be lowered slightly.
numFailures = 2;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
numFailures = 3;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
// Failures in [4,6), priority should be lowered more.
numFailures = 4;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
numFailures = 5;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
// 6+ failures, priority should be lowered as much as possible.
numFailures = 6;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
numFailures = 12;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
// System stops shouldn't factor in the downgrade.
numSystemStops = 10;
numFailures = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
}
@@ -563,32 +563,32 @@
int numFailures = 1;
int numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
numFailures = 4;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
numFailures = 5;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
// 6+ failures, priority should be lowered as much as possible.
numFailures = 6;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
numFailures = 12;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
// System stops shouldn't factor in the downgrade.
numSystemStops = 10;
numFailures = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
}
@@ -606,28 +606,28 @@
int numFailures = 1;
int numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// 2+ failures, priority shouldn't be affected while job is still a UI job
numFailures = 2;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
numFailures = 5;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
numFailures = 8;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// System stops shouldn't factor in the downgrade.
numSystemStops = 10;
numFailures = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// Job can no long run as user-initiated. Downgrades should be effective.
@@ -641,28 +641,28 @@
numFailures = 1;
numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
// 2+ failures, priority should start getting lower
numFailures = 2;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
numFailures = 5;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
numFailures = 8;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
// System stops shouldn't factor in the downgrade.
numSystemStops = 10;
numFailures = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
}
@@ -772,14 +772,14 @@
assertTrue(job.shouldTreatAsUserInitiatedJob());
JobStatus rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
- 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, 0);
assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
assertFalse(job.shouldTreatAsUserInitiatedJob());
rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
- 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, 0);
assertFalse(rescheduledJob.shouldTreatAsUserInitiatedJob());
rescheduledJob.removeInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
@@ -797,14 +797,14 @@
assertTrue(job.shouldTreatAsUserInitiatedJob());
JobStatus rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
- 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, 0);
assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
assertFalse(job.shouldTreatAsUserInitiatedJob());
rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
- 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, 0);
assertFalse(rescheduledJob.shouldTreatAsUserInitiatedJob());
rescheduledJob.removeInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
index e631cb6..313c01d 100644
--- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -18,7 +18,6 @@
import static com.android.server.power.hint.HintManagerService.CLEAN_UP_UID_DELAY_MILLIS;
-import static com.android.server.power.hint.HintManagerService.DEFAULT_HEADROOM_PID;
import static com.google.common.truth.Truth.assertThat;
@@ -53,7 +52,9 @@
import android.hardware.power.ChannelConfig;
import android.hardware.power.ChannelMessage;
import android.hardware.power.CpuHeadroomParams;
+import android.hardware.power.CpuHeadroomResult;
import android.hardware.power.GpuHeadroomParams;
+import android.hardware.power.GpuHeadroomResult;
import android.hardware.power.IPower;
import android.hardware.power.SessionConfig;
import android.hardware.power.SessionTag;
@@ -1251,67 +1252,98 @@
CpuHeadroomParams halParams1 = new CpuHeadroomParams();
halParams1.calculationType = CpuHeadroomParams.CalculationType.MIN;
halParams1.selectionType = CpuHeadroomParams.SelectionType.ALL;
- halParams1.pid = Process.myPid();
+ halParams1.tids = new int[]{Process.myPid()};
CpuHeadroomParamsInternal params2 = new CpuHeadroomParamsInternal();
params2.usesDeviceHeadroom = true;
- params2.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+ params2.calculationType = CpuHeadroomParams.CalculationType.MIN;
params2.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
CpuHeadroomParams halParams2 = new CpuHeadroomParams();
- halParams2.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+ halParams2.calculationType = CpuHeadroomParams.CalculationType.MIN;
halParams2.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
- halParams2.pid = DEFAULT_HEADROOM_PID;
+ halParams2.tids = new int[]{};
- float[] headroom1 = new float[] {0.1f};
- when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(headroom1);
- float[] headroom2 = new float[] {0.1f, 0.5f};
- when(mIPowerMock.getCpuHeadroom(eq(halParams2))).thenReturn(headroom2);
+ CpuHeadroomParamsInternal params3 = new CpuHeadroomParamsInternal();
+ params3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+ params3.selectionType = CpuHeadroomParams.SelectionType.ALL;
+ CpuHeadroomParams halParams3 = new CpuHeadroomParams();
+ halParams3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+ halParams3.selectionType = CpuHeadroomParams.SelectionType.ALL;
+ halParams3.tids = new int[]{Process.myPid()};
+
+ // this params should not be cached as the window is not default
+ CpuHeadroomParamsInternal params4 = new CpuHeadroomParamsInternal();
+ params4.calculationWindowMillis = 123;
+ CpuHeadroomParams halParams4 = new CpuHeadroomParams();
+ halParams4.calculationType = CpuHeadroomParams.CalculationType.MIN;
+ halParams4.selectionType = CpuHeadroomParams.SelectionType.ALL;
+ halParams4.calculationWindowMillis = 123;
+ halParams4.tids = new int[]{Process.myPid()};
+
+ float headroom1 = 0.1f;
+ CpuHeadroomResult halRet1 = CpuHeadroomResult.globalHeadroom(headroom1);
+ when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(halRet1);
+ float[] headroom2 = new float[] {0.2f, 0.2f};
+ CpuHeadroomResult halRet2 = CpuHeadroomResult.perCoreHeadroom(headroom2);
+ when(mIPowerMock.getCpuHeadroom(eq(halParams2))).thenReturn(halRet2);
+ float headroom3 = 0.3f;
+ CpuHeadroomResult halRet3 = CpuHeadroomResult.globalHeadroom(headroom3);
+ when(mIPowerMock.getCpuHeadroom(eq(halParams3))).thenReturn(halRet3);
+ float headroom4 = 0.4f;
+ CpuHeadroomResult halRet4 = CpuHeadroomResult.globalHeadroom(headroom4);
+ when(mIPowerMock.getCpuHeadroom(eq(halParams4))).thenReturn(halRet4);
HintManagerService service = createService();
clearInvocations(mIPowerMock);
service.getBinderServiceInstance().getCpuHeadroomMinIntervalMillis();
verify(mIPowerMock, times(0)).getCpuHeadroomMinIntervalMillis();
- service.getBinderServiceInstance().getCpuHeadroom(params1);
+ assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
- service.getBinderServiceInstance().getCpuHeadroom(params2);
+ assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2));
+ assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams3));
+ assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
// verify cache is working
clearInvocations(mIPowerMock);
- assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
- 0.01f);
- assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
- 0.01f);
- verify(mIPowerMock, times(0)).getCpuHeadroom(any());
+ assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
+ assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+ assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(any());
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams2));
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
// after 1 more second it should be served with cache still
Thread.sleep(1000);
clearInvocations(mIPowerMock);
- assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
- 0.01f);
- assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
- 0.01f);
- verify(mIPowerMock, times(0)).getCpuHeadroom(any());
-
- // after 1.5 more second it should be served with cache still as timer reset
- Thread.sleep(1500);
- clearInvocations(mIPowerMock);
- assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
- 0.01f);
- assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
- 0.01f);
- verify(mIPowerMock, times(0)).getCpuHeadroom(any());
+ assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
+ assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+ assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(any());
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams2));
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
// after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
- Thread.sleep(2100);
+ Thread.sleep(1100);
clearInvocations(mIPowerMock);
- assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
- 0.01f);
- assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
- 0.01f);
+ assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
+ assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+ assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+ verify(mIPowerMock, times(4)).getCpuHeadroom(any());
verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams3));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
}
@Test
@@ -1322,59 +1354,52 @@
halParams1.calculationType = GpuHeadroomParams.CalculationType.MIN;
GpuHeadroomParamsInternal params2 = new GpuHeadroomParamsInternal();
+ params2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE;
+ params2.calculationWindowMillis = 123;
GpuHeadroomParams halParams2 = new GpuHeadroomParams();
- params2.calculationType =
- halParams2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE;
+ halParams2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE;
+ halParams2.calculationWindowMillis = 123;
float headroom1 = 0.1f;
- when(mIPowerMock.getGpuHeadroom(eq(halParams1))).thenReturn(headroom1);
+ GpuHeadroomResult halRet1 = GpuHeadroomResult.globalHeadroom(headroom1);
+ when(mIPowerMock.getGpuHeadroom(eq(halParams1))).thenReturn(halRet1);
float headroom2 = 0.2f;
- when(mIPowerMock.getGpuHeadroom(eq(halParams2))).thenReturn(headroom2);
+ GpuHeadroomResult halRet2 = GpuHeadroomResult.globalHeadroom(headroom2);
+ when(mIPowerMock.getGpuHeadroom(eq(halParams2))).thenReturn(halRet2);
HintManagerService service = createService();
clearInvocations(mIPowerMock);
service.getBinderServiceInstance().getGpuHeadroomMinIntervalMillis();
verify(mIPowerMock, times(0)).getGpuHeadroomMinIntervalMillis();
- assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
- 0.01f);
- assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
- 0.01f);
+ assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+ verify(mIPowerMock, times(2)).getGpuHeadroom(any());
verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1));
verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
// verify cache is working
clearInvocations(mIPowerMock);
- assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
- 0.01f);
- assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
- 0.01f);
- verify(mIPowerMock, times(0)).getGpuHeadroom(any());
+ assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(any());
+ verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
// after 1 more second it should be served with cache still
Thread.sleep(1000);
clearInvocations(mIPowerMock);
- assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
- 0.01f);
- assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
- 0.01f);
- verify(mIPowerMock, times(0)).getGpuHeadroom(any());
-
- // after 1.5 more second it should be served with cache still as timer reset
- Thread.sleep(1500);
- clearInvocations(mIPowerMock);
- assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
- 0.01f);
- assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
- 0.01f);
- verify(mIPowerMock, times(0)).getGpuHeadroom(any());
+ assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(any());
+ verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
// after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
- Thread.sleep(2100);
+ Thread.sleep(1100);
clearInvocations(mIPowerMock);
- assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
- 0.01f);
- assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
- 0.01f);
+ assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+ verify(mIPowerMock, times(2)).getGpuHeadroom(any());
verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1));
verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
}