Merge "Run reveal animation from relative top of window" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 0fdd880..cd991c7 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -69,6 +69,7 @@
":com.android.hardware.input-aconfig-java{.generated_srcjars}",
":com.android.input.flags-aconfig-java{.generated_srcjars}",
":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.internal.pm.pkg.component.flags-aconfig-java{.generated_srcjars}",
":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
":com.android.media.flags.editing-aconfig-java{.generated_srcjars}",
":com.android.net.thread.flags-aconfig-java{.generated_srcjars}",
@@ -1138,3 +1139,22 @@
aconfig_declarations: "android.app.wearable.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+aconfig_declarations {
+ name: "com.android.internal.pm.pkg.component.flags-aconfig",
+ package: "com.android.internal.pm.pkg.component.flags",
+ srcs: ["core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "com.android.internal.pm.pkg.component.flags-aconfig-java",
+ aconfig_declarations: "com.android.internal.pm.pkg.component.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+java_aconfig_library {
+ name: "com.android.internal.pm.pkg.component.flags-aconfig-java-host",
+ aconfig_declarations: "com.android.internal.pm.pkg.component.flags-aconfig",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 5d65d9d..8a5206f 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -1,6 +1,20 @@
package: "com.android.server.job"
flag {
+ name: "batch_active_bucket_jobs"
+ namespace: "backstage_power"
+ description: "Include jobs in the ACTIVE bucket in the job batching effort. Don't let them run as freely as they're ready."
+ bug: "299329948"
+}
+
+flag {
+ name: "batch_connectivity_jobs_per_network"
+ namespace: "backstage_power"
+ description: "Have JobScheduler attempt to delay the start of some connectivity jobs until there are several ready or the network is active"
+ bug: "28382445"
+}
+
+flag {
name: "do_not_force_rush_execution_at_boot"
namespace: "backstage_power"
description: "Don't force rush job execution right after boot completion"
@@ -20,10 +34,3 @@
description: "Throw an exception if an unsupported app uses JobInfo.setBias"
bug: "300477393"
}
-
-flag {
- name: "batch_jobs_on_network_activation"
- namespace: "backstage_power"
- description: "Have JobScheduler attempt to delay the start of some connectivity jobs until the network is actually active"
- bug: "318394184"
-}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 6550f26..012ede2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -96,7 +96,6 @@
static final String CONFIG_KEY_PREFIX_CONCURRENCY = "concurrency_";
private static final String KEY_CONCURRENCY_LIMIT = CONFIG_KEY_PREFIX_CONCURRENCY + "limit";
- @VisibleForTesting
static final int DEFAULT_CONCURRENCY_LIMIT;
static {
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 57467e3..cea16d6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -65,6 +65,7 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
@@ -89,6 +90,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
+import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -538,7 +540,9 @@
apiQuotaScheduleUpdated = true;
}
break;
+ case Constants.KEY_MIN_READY_CPU_ONLY_JOBS_COUNT:
case Constants.KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT:
+ case Constants.KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS:
case Constants.KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS:
mConstants.updateBatchingConstantsLocked();
break;
@@ -554,6 +558,8 @@
case Constants.KEY_CONN_CONGESTION_DELAY_FRAC:
case Constants.KEY_CONN_PREFETCH_RELAX_FRAC:
case Constants.KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC:
+ case Constants.KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS:
+ case Constants.KEY_CONN_TRANSPORT_BATCH_THRESHOLD:
case Constants.KEY_CONN_USE_CELL_SIGNAL_STRENGTH:
case Constants.KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS:
mConstants.updateConnectivityConstantsLocked();
@@ -602,6 +608,8 @@
sc.onConstantsUpdatedLocked();
}
}
+
+ mHandler.sendEmptyMessage(MSG_CHECK_JOB);
}
@Override
@@ -646,8 +654,12 @@
*/
public static class Constants {
// Key names stored in the settings value.
+ private static final String KEY_MIN_READY_CPU_ONLY_JOBS_COUNT =
+ "min_ready_cpu_only_jobs_count";
private static final String KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT =
"min_ready_non_active_jobs_count";
+ private static final String KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS =
+ "max_cpu_only_job_batch_delay_ms";
private static final String KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS =
"max_non_active_job_batch_delay_ms";
private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor";
@@ -665,6 +677,10 @@
"conn_update_all_jobs_min_interval_ms";
private static final String KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
"conn_low_signal_strength_relax_frac";
+ private static final String KEY_CONN_TRANSPORT_BATCH_THRESHOLD =
+ "conn_transport_batch_threshold";
+ private static final String KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS =
+ "conn_max_connectivity_job_batch_delay_ms";
private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
"prefetch_force_batch_relax_threshold_ms";
// This has been enabled for 3+ full releases. We're unlikely to disable it.
@@ -713,7 +729,11 @@
private static final String KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS =
"max_num_persisted_job_work_items";
- private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
+ private static final int DEFAULT_MIN_READY_CPU_ONLY_JOBS_COUNT =
+ Math.min(3, JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3);
+ private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT =
+ Math.min(5, JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3);
+ private static final long DEFAULT_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
private static final float DEFAULT_MODERATE_USE_FACTOR = .5f;
@@ -725,6 +745,15 @@
private static final boolean DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH = true;
private static final long DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = MINUTE_IN_MILLIS;
private static final float DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = 0.5f;
+ private static final SparseIntArray DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD =
+ new SparseIntArray();
+ private static final long DEFAULT_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS =
+ 31 * MINUTE_IN_MILLIS;
+ static {
+ DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.put(
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ Math.min(3, JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3));
+ }
private static final long DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = HOUR_IN_MILLIS;
private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
@@ -762,11 +791,23 @@
static final int DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 100_000;
/**
- * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early.
+ * Minimum # of jobs that have to be ready for JS to be happy running work.
+ * Only valid if {@link Flags#batchActiveBucketJobs()} is true.
+ */
+ int MIN_READY_CPU_ONLY_JOBS_COUNT = DEFAULT_MIN_READY_CPU_ONLY_JOBS_COUNT;
+
+ /**
+ * Minimum # of non-ACTIVE jobs that have to be ready for JS to be happy running work.
*/
int MIN_READY_NON_ACTIVE_JOBS_COUNT = DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT;
/**
+ * Don't batch a CPU-only job if it's been delayed due to force batching attempts for
+ * at least this amount of time.
+ */
+ long MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = DEFAULT_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS;
+
+ /**
* Don't batch a non-ACTIVE job if it's been delayed due to force batching attempts for
* at least this amount of time.
*/
@@ -822,6 +863,17 @@
*/
public float CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC;
+ /**
+ * The minimum batch requirement per each transport type before allowing a network to run
+ * on a network with that transport.
+ */
+ public SparseIntArray CONN_TRANSPORT_BATCH_THRESHOLD = new SparseIntArray();
+ /**
+ * Don't batch a connectivity job if it's been delayed due to force batching attempts for
+ * at least this amount of time.
+ */
+ public long CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS =
+ DEFAULT_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS;
/**
* The amount of time within which we would consider the app to be launching relatively soon
@@ -972,11 +1024,31 @@
public boolean USE_TARE_POLICY = EconomyManager.DEFAULT_ENABLE_POLICY_JOB_SCHEDULER
&& EconomyManager.DEFAULT_ENABLE_TARE_MODE == EconomyManager.ENABLED_MODE_ON;
+ public Constants() {
+ copyTransportBatchThresholdDefaults();
+ }
+
private void updateBatchingConstantsLocked() {
- MIN_READY_NON_ACTIVE_JOBS_COUNT = DeviceConfig.getInt(
+ // The threshold should be in the range
+ // [0, DEFAULT_CONCURRENCY_LIMIT / 3].
+ MIN_READY_CPU_ONLY_JOBS_COUNT =
+ Math.max(0, Math.min(JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3,
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_MIN_READY_CPU_ONLY_JOBS_COUNT,
+ DEFAULT_MIN_READY_CPU_ONLY_JOBS_COUNT)));
+ // The threshold should be in the range
+ // [0, DEFAULT_CONCURRENCY_LIMIT / 3].
+ MIN_READY_NON_ACTIVE_JOBS_COUNT =
+ Math.max(0, Math.min(JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3,
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
+ DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT)));
+ MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
- KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
- DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT);
+ KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS,
+ DEFAULT_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS);
MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS,
@@ -1024,6 +1096,46 @@
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC,
DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC);
+ final String batchThresholdConfigString = DeviceConfig.getString(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_CONN_TRANSPORT_BATCH_THRESHOLD,
+ null);
+ final KeyValueListParser parser = new KeyValueListParser(',');
+ CONN_TRANSPORT_BATCH_THRESHOLD.clear();
+ try {
+ parser.setString(batchThresholdConfigString);
+
+ for (int t = parser.size() - 1; t >= 0; --t) {
+ final String transportString = parser.keyAt(t);
+ try {
+ final int transport = Integer.parseInt(transportString);
+ // The threshold should be in the range
+ // [0, DEFAULT_CONCURRENCY_LIMIT / 3].
+ CONN_TRANSPORT_BATCH_THRESHOLD.put(transport, Math.max(0,
+ Math.min(JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3,
+ parser.getInt(transportString, 1))));
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Bad transport string", e);
+ }
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Bad string for " + KEY_CONN_TRANSPORT_BATCH_THRESHOLD, e);
+ // Use the defaults.
+ copyTransportBatchThresholdDefaults();
+ }
+ CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS = Math.max(0, Math.min(24 * HOUR_IN_MILLIS,
+ DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS,
+ DEFAULT_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS)));
+ }
+
+ private void copyTransportBatchThresholdDefaults() {
+ for (int i = DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.size() - 1; i >= 0; --i) {
+ CONN_TRANSPORT_BATCH_THRESHOLD.put(
+ DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.keyAt(i),
+ DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.valueAt(i));
+ }
}
private void updatePersistingConstantsLocked() {
@@ -1168,8 +1280,11 @@
void dump(IndentingPrintWriter pw) {
pw.println("Settings:");
pw.increaseIndent();
+ pw.print(KEY_MIN_READY_CPU_ONLY_JOBS_COUNT, MIN_READY_CPU_ONLY_JOBS_COUNT).println();
pw.print(KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
MIN_READY_NON_ACTIVE_JOBS_COUNT).println();
+ pw.print(KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS,
+ MAX_CPU_ONLY_JOB_BATCH_DELAY_MS).println();
pw.print(KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS,
MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS).println();
pw.print(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
@@ -1185,6 +1300,10 @@
.println();
pw.print(KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC, CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC)
.println();
+ pw.print(KEY_CONN_TRANSPORT_BATCH_THRESHOLD, CONN_TRANSPORT_BATCH_THRESHOLD.toString())
+ .println();
+ pw.print(KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS,
+ CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS).println();
pw.print(KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS).println();
@@ -2835,9 +2954,9 @@
mJobPackageTracker.notePending(job);
}
- void noteJobsPending(List<JobStatus> jobs) {
- for (int i = jobs.size() - 1; i >= 0; i--) {
- noteJobPending(jobs.get(i));
+ void noteJobsPending(ArraySet<JobStatus> jobs) {
+ for (int i = jobs.size() - 1; i >= 0; --i) {
+ noteJobPending(jobs.valueAt(i));
}
}
@@ -3463,7 +3582,7 @@
}
final class ReadyJobQueueFunctor implements Consumer<JobStatus> {
- final ArrayList<JobStatus> newReadyJobs = new ArrayList<>();
+ final ArraySet<JobStatus> newReadyJobs = new ArraySet<>();
@Override
public void accept(JobStatus job) {
@@ -3491,9 +3610,27 @@
* policies on when we want to execute jobs.
*/
final class MaybeReadyJobQueueFunctor implements Consumer<JobStatus> {
- int forceBatchedCount;
- int unbatchedCount;
+ /**
+ * Set of jobs that will be force batched, mapped by network. A {@code null} network is
+ * reserved/intended for CPU-only (non-networked) jobs.
+ * The set may include already running jobs.
+ */
+ @VisibleForTesting
+ final ArrayMap<Network, ArraySet<JobStatus>> mBatches = new ArrayMap<>();
+ /** List of all jobs that could run if allowed. Already running jobs are excluded. */
+ @VisibleForTesting
final List<JobStatus> runnableJobs = new ArrayList<>();
+ /**
+ * Convenience holder of all jobs ready to run that won't be force batched.
+ * Already running jobs are excluded.
+ */
+ final ArraySet<JobStatus> mUnbatchedJobs = new ArraySet<>();
+ /**
+ * Count of jobs that won't be force batched, mapped by network. A {@code null} network is
+ * reserved/intended for CPU-only (non-networked) jobs.
+ * The set may include already running jobs.
+ */
+ final ArrayMap<Network, Integer> mUnbatchedJobCount = new ArrayMap<>();
public MaybeReadyJobQueueFunctor() {
reset();
@@ -3540,27 +3677,77 @@
shouldForceBatchJob = false;
} else {
final long nowElapsed = sElapsedRealtimeClock.millis();
- final boolean batchDelayExpired = job.getFirstForceBatchedTimeElapsed() > 0
- && nowElapsed - job.getFirstForceBatchedTimeElapsed()
- >= mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
- shouldForceBatchJob =
- mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
- && job.getEffectiveStandbyBucket() != ACTIVE_INDEX
- && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX
- && !batchDelayExpired;
+ final long timeUntilDeadlineMs = job.hasDeadlineConstraint()
+ ? job.getLatestRunTimeElapsed() - nowElapsed
+ : Long.MAX_VALUE;
+ // Differentiate behavior based on whether the job needs network or not.
+ if (Flags.batchConnectivityJobsPerNetwork()
+ && job.hasConnectivityConstraint()) {
+ // For connectivity jobs, let them run immediately if the network is already
+ // active (in a state for job run), otherwise, only run them if there are
+ // enough to meet the batching requirement or the job has been waiting
+ // long enough.
+ final boolean batchDelayExpired =
+ job.getFirstForceBatchedTimeElapsed() > 0
+ && nowElapsed - job.getFirstForceBatchedTimeElapsed()
+ >= mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS;
+ shouldForceBatchJob = !batchDelayExpired
+ && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX
+ && timeUntilDeadlineMs
+ > mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS / 2
+ && !mConnectivityController.isNetworkInStateForJobRunLocked(job);
+ } else {
+ final boolean batchDelayExpired;
+ final boolean batchingEnabled;
+ if (Flags.batchActiveBucketJobs()) {
+ batchingEnabled = mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT > 1
+ && timeUntilDeadlineMs
+ > mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS / 2
+ // Active UIDs' jobs were by default treated as in the ACTIVE
+ // bucket, so we must explicitly exclude them when batching
+ // ACTIVE jobs.
+ && !job.uidActive
+ && !job.getJob().isExemptedFromAppStandby();
+ batchDelayExpired = job.getFirstForceBatchedTimeElapsed() > 0
+ && nowElapsed - job.getFirstForceBatchedTimeElapsed()
+ >= mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS;
+ } else {
+ batchingEnabled = mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
+ && job.getEffectiveStandbyBucket() != ACTIVE_INDEX;
+ batchDelayExpired = job.getFirstForceBatchedTimeElapsed() > 0
+ && nowElapsed - job.getFirstForceBatchedTimeElapsed()
+ >= mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
+ }
+ shouldForceBatchJob = batchingEnabled
+ && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX
+ && !batchDelayExpired;
+ }
}
+ // If connectivity job batching isn't enabled, treat every job as
+ // a non-connectivity job since that mimics the old behavior.
+ final Network network =
+ Flags.batchConnectivityJobsPerNetwork() ? job.network : null;
+ ArraySet<JobStatus> batch = mBatches.get(network);
+ if (batch == null) {
+ batch = new ArraySet<>();
+ mBatches.put(network, batch);
+ }
+ batch.add(job);
+
if (shouldForceBatchJob) {
- // Force batching non-ACTIVE jobs. Don't include them in the other counts.
- forceBatchedCount++;
if (job.getFirstForceBatchedTimeElapsed() == 0) {
job.setFirstForceBatchedTimeElapsed(sElapsedRealtimeClock.millis());
}
} else {
- unbatchedCount++;
+ mUnbatchedJobCount.put(network,
+ mUnbatchedJobCount.getOrDefault(job.network, 0) + 1);
}
if (!isRunning) {
runnableJobs.add(job);
+ if (!shouldForceBatchJob) {
+ mUnbatchedJobs.add(job);
+ }
}
} else {
if (isRunning) {
@@ -3600,34 +3787,131 @@
@GuardedBy("mLock")
@VisibleForTesting
void postProcessLocked() {
- if (unbatchedCount > 0
- || forceBatchedCount >= mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT) {
- if (DEBUG) {
- Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running jobs.");
+ final ArraySet<JobStatus> jobsToRun = mUnbatchedJobs;
+
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: "
+ + mUnbatchedJobs.size() + " unbatched jobs.");
+ }
+
+ int unbatchedCount = 0;
+
+ for (int n = mBatches.size() - 1; n >= 0; --n) {
+ final Network network = mBatches.keyAt(n);
+
+ // Count all of the unbatched jobs, including the ones without a network.
+ final Integer unbatchedJobCountObj = mUnbatchedJobCount.get(network);
+ final int unbatchedJobCount;
+ if (unbatchedJobCountObj != null) {
+ unbatchedJobCount = unbatchedJobCountObj;
+ unbatchedCount += unbatchedJobCount;
+ } else {
+ unbatchedJobCount = 0;
}
- noteJobsPending(runnableJobs);
- mPendingJobQueue.addAll(runnableJobs);
+
+ // Skip the non-networked jobs here. They'll be handled after evaluating
+ // everything else.
+ if (network == null) {
+ continue;
+ }
+
+ final ArraySet<JobStatus> batchedJobs = mBatches.valueAt(n);
+ if (unbatchedJobCount > 0) {
+ // Some job is going to activate the network anyway. Might as well run all
+ // the other jobs that will use this network.
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: piggybacking "
+ + batchedJobs.size() + " jobs on " + network
+ + " because of unbatched job");
+ }
+ jobsToRun.addAll(batchedJobs);
+ continue;
+ }
+
+ final NetworkCapabilities networkCapabilities =
+ mConnectivityController.getNetworkCapabilities(network);
+ if (networkCapabilities == null) {
+ Slog.e(TAG, "Couldn't get NetworkCapabilities for network " + network);
+ continue;
+ }
+
+ final int[] transports = networkCapabilities.getTransportTypes();
+ int maxNetworkBatchReq = 1;
+ for (int transport : transports) {
+ maxNetworkBatchReq = Math.max(maxNetworkBatchReq,
+ mConstants.CONN_TRANSPORT_BATCH_THRESHOLD.get(transport));
+ }
+
+ if (batchedJobs.size() >= maxNetworkBatchReq) {
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: "
+ + batchedJobs.size()
+ + " batched network jobs meet requirement for " + network);
+ }
+ jobsToRun.addAll(batchedJobs);
+ }
+ }
+
+ final ArraySet<JobStatus> batchedNonNetworkedJobs = mBatches.get(null);
+ if (batchedNonNetworkedJobs != null) {
+ final int minReadyCount = Flags.batchActiveBucketJobs()
+ ? mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT
+ : mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT;
+ if (jobsToRun.size() > 0) {
+ // Some job is going to use the CPU anyway. Might as well run all the other
+ // CPU-only jobs.
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: piggybacking "
+ + batchedNonNetworkedJobs.size() + " non-network jobs");
+ }
+ jobsToRun.addAll(batchedNonNetworkedJobs);
+ } else if (batchedNonNetworkedJobs.size() >= minReadyCount) {
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: adding "
+ + batchedNonNetworkedJobs.size() + " batched non-network jobs.");
+ }
+ jobsToRun.addAll(batchedNonNetworkedJobs);
+ }
+ }
+
+ // In order to properly determine an accurate batch count, the running jobs must be
+ // included in the earlier lists and can only be removed after checking if the batch
+ // count requirement is satisfied.
+ jobsToRun.removeIf(JobSchedulerService.this::isCurrentlyRunningLocked);
+
+ if (unbatchedCount > 0 || jobsToRun.size() > 0) {
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running "
+ + jobsToRun + " jobs.");
+ }
+ noteJobsPending(jobsToRun);
+ mPendingJobQueue.addAll(jobsToRun);
} else {
if (DEBUG) {
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
}
- final int numRunnableJobs = runnableJobs.size();
- if (numRunnableJobs > 0) {
- synchronized (mPendingJobReasonCache) {
- for (int i = 0; i < numRunnableJobs; ++i) {
- final JobStatus job = runnableJobs.get(i);
- SparseIntArray reasons =
- mPendingJobReasonCache.get(job.getUid(), job.getNamespace());
- if (reasons == null) {
- reasons = new SparseIntArray();
- mPendingJobReasonCache
- .add(job.getUid(), job.getNamespace(), reasons);
- }
- // We're force batching these jobs, so consider it an optimization
- // policy reason.
- reasons.put(job.getJobId(),
- JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
+ }
+
+ // Update the pending reason for any jobs that aren't going to be run.
+ final int numRunnableJobs = runnableJobs.size();
+ if (numRunnableJobs > 0 && numRunnableJobs != jobsToRun.size()) {
+ synchronized (mPendingJobReasonCache) {
+ for (int i = 0; i < numRunnableJobs; ++i) {
+ final JobStatus job = runnableJobs.get(i);
+ if (jobsToRun.contains(job)) {
+ // We're running this job. Skip updating the pending reason.
+ continue;
}
+ SparseIntArray reasons =
+ mPendingJobReasonCache.get(job.getUid(), job.getNamespace());
+ if (reasons == null) {
+ reasons = new SparseIntArray();
+ mPendingJobReasonCache.add(job.getUid(), job.getNamespace(), reasons);
+ }
+ // We're force batching these jobs, so consider it an optimization
+ // policy reason.
+ reasons.put(job.getJobId(),
+ JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
}
}
}
@@ -3638,9 +3922,10 @@
@VisibleForTesting
void reset() {
- forceBatchedCount = 0;
- unbatchedCount = 0;
runnableJobs.clear();
+ mBatches.clear();
+ mUnbatchedJobs.clear();
+ mUnbatchedJobCount.clear();
}
}
@@ -5468,8 +5753,14 @@
pw.println("Aconfig flags:");
pw.increaseIndent();
+ pw.print(Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS, Flags.batchActiveBucketJobs());
+ pw.println();
+ pw.print(Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK,
+ Flags.batchConnectivityJobsPerNetwork());
+ pw.println();
pw.print(Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT,
Flags.doNotForceRushExecutionAtBoot());
+ pw.println();
pw.print(Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE,
Flags.throwOnUnsupportedBiasUsage());
pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 0cf6a7a..90b4630 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -350,6 +350,12 @@
case android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS:
pw.println(android.app.job.Flags.jobDebugInfoApis());
break;
+ case com.android.server.job.Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS:
+ pw.println(com.android.server.job.Flags.batchActiveBucketJobs());
+ break;
+ case com.android.server.job.Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK:
+ pw.println(com.android.server.job.Flags.batchConnectivityJobsPerNetwork());
+ break;
case com.android.server.job.Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT:
pw.println(com.android.server.job.Flags.doNotForceRushExecutionAtBoot());
break;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
index 4f4096f..813cf87 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.ArraySet;
import android.util.Pools;
import android.util.SparseArray;
@@ -96,10 +97,10 @@
}
}
- void addAll(@NonNull List<JobStatus> jobs) {
+ void addAll(@NonNull ArraySet<JobStatus> jobs) {
final SparseArray<List<JobStatus>> jobsByUid = new SparseArray<>();
for (int i = jobs.size() - 1; i >= 0; --i) {
- final JobStatus job = jobs.get(i);
+ final JobStatus job = jobs.valueAt(i);
List<JobStatus> appJobs = jobsByUid.get(job.getSourceUid());
if (appJobs == null) {
appJobs = new ArrayList<>();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index f405083..6ed42ec 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -22,11 +22,11 @@
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.Flags.FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER;
-import static com.android.server.job.Flags.relaxPrefetchConnectivityConstraintOnlyOnCharger;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -66,6 +66,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.AppSchedulingModuleThread;
import com.android.server.LocalServices;
+import com.android.server.job.Flags;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
import com.android.server.job.StateControllerProto;
@@ -166,6 +167,10 @@
@GuardedBy("mLock")
private final ArrayMap<Network, CachedNetworkMetadata> mAvailableNetworks = new ArrayMap<>();
+ @GuardedBy("mLock")
+ @Nullable
+ private Network mSystemDefaultNetwork;
+
private final SparseArray<UidDefaultNetworkCallback> mCurrentDefaultNetworkCallbacks =
new SparseArray<>();
private final Comparator<UidStats> mUidStatsComparator = new Comparator<UidStats>() {
@@ -286,6 +291,7 @@
private static final int MSG_UPDATE_ALL_TRACKED_JOBS = 1;
private static final int MSG_DATA_SAVER_TOGGLED = 2;
private static final int MSG_UID_POLICIES_CHANGED = 3;
+ private static final int MSG_PROCESS_ACTIVE_NETWORK = 4;
private final Handler mHandler;
@@ -313,6 +319,14 @@
}
}
+ @Override
+ public void startTrackingLocked() {
+ if (Flags.batchConnectivityJobsPerNetwork()) {
+ mConnManager.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
+ mConnManager.addDefaultNetworkActiveListener(this);
+ }
+ }
+
@GuardedBy("mLock")
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
@@ -911,8 +925,8 @@
return true;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
- // Exclude VPNs because it's currently not possible to determine the VPN's underlying
- // network, and thus the correct signal strength of the VPN's network.
+ // VPNs may have multiple underlying networks and determining the correct strength
+ // may not be straightforward.
// Transmitting data over a VPN is generally more battery-expensive than on the
// underlying network, so:
// TODO: find a good way to reduce job use of VPN when it'll be very expensive
@@ -1007,7 +1021,7 @@
// Need to at least know the estimated download bytes for a prefetch job.
return false;
}
- if (relaxPrefetchConnectivityConstraintOnlyOnCharger()) {
+ if (Flags.relaxPrefetchConnectivityConstraintOnlyOnCharger()) {
// Since the constraint relaxation isn't required by the job, only do it when the
// device is charging and the battery level is above the "low battery" threshold.
if (!mService.isBatteryCharging() || !mService.isBatteryNotLow()) {
@@ -1309,7 +1323,7 @@
}
@Nullable
- private NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
+ public NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
final CachedNetworkMetadata metadata = getNetworkMetadata(network);
return metadata == null ? null : metadata.networkCapabilities;
}
@@ -1527,26 +1541,138 @@
}
/**
+ * Returns {@code true} if the job's assigned network is active or otherwise considered to be
+ * in a good state to run the job now.
+ */
+ @GuardedBy("mLock")
+ public boolean isNetworkInStateForJobRunLocked(@NonNull JobStatus jobStatus) {
+ if (jobStatus.network == null) {
+ return false;
+ }
+ if (jobStatus.shouldTreatAsExpeditedJob() || jobStatus.shouldTreatAsUserInitiatedJob()
+ || mService.getUidProcState(jobStatus.getSourceUid())
+ <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+ // EJs, UIJs, and BFGS+ jobs should be able to activate the network.
+ return true;
+ }
+ return isNetworkInStateForJobRunLocked(jobStatus.network);
+ }
+
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ boolean isNetworkInStateForJobRunLocked(@NonNull Network network) {
+ if (!Flags.batchConnectivityJobsPerNetwork()) {
+ // Active network batching isn't enabled. We don't care about the network state.
+ return true;
+ }
+
+ CachedNetworkMetadata cachedNetworkMetadata = mAvailableNetworks.get(network);
+ if (cachedNetworkMetadata == null) {
+ return false;
+ }
+
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ if (cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed
+ + mCcConfig.NETWORK_ACTIVATION_EXPIRATION_MS > nowElapsed) {
+ // Network is still presumed to be active.
+ return true;
+ }
+
+ final boolean inactiveForTooLong =
+ cachedNetworkMetadata.capabilitiesFirstAcquiredTimeElapsed
+ < nowElapsed - mCcConfig.NETWORK_ACTIVATION_MAX_WAIT_TIME_MS
+ && cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed
+ < nowElapsed - mCcConfig.NETWORK_ACTIVATION_MAX_WAIT_TIME_MS;
+ // We can only know the state of the system default network. If that's not available
+ // or the network in question isn't the system default network,
+ // then return true if we haven't gotten an active signal in a long time.
+ if (mSystemDefaultNetwork == null) {
+ return inactiveForTooLong;
+ }
+
+ if (!mSystemDefaultNetwork.equals(network)) {
+ final NetworkCapabilities capabilities = cachedNetworkMetadata.networkCapabilities;
+ if (capabilities != null
+ && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
+ // VPNs won't have an active signal sent for them. Check their underlying networks
+ // instead, prioritizing the system default if it's one of them.
+ final List<Network> underlyingNetworks = capabilities.getUnderlyingNetworks();
+ if (underlyingNetworks == null) {
+ return inactiveForTooLong;
+ }
+
+ if (underlyingNetworks.contains(mSystemDefaultNetwork)) {
+ if (DEBUG) {
+ Slog.i(TAG, "Substituting system default network "
+ + mSystemDefaultNetwork + " for VPN " + network);
+ }
+ return isNetworkInStateForJobRunLocked(mSystemDefaultNetwork);
+ }
+
+ for (int i = underlyingNetworks.size() - 1; i >= 0; --i) {
+ if (isNetworkInStateForJobRunLocked(underlyingNetworks.get(i))) {
+ return true;
+ }
+ }
+ }
+ return inactiveForTooLong;
+ }
+
+ if (cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed
+ + mCcConfig.NETWORK_ACTIVATION_EXPIRATION_MS < nowElapsed) {
+ // We haven't checked the state recently enough. Let's check if the network is active.
+ // However, if we checked after the last confirmed active time and it wasn't active,
+ // then the network is still not active (we would be told when it becomes active
+ // via onNetworkActive()).
+ if (cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed
+ > cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed) {
+ return inactiveForTooLong;
+ }
+ // We need to explicitly check because there's no callback telling us when the network
+ // leaves the high power state.
+ cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed = nowElapsed;
+ final boolean isActive = mConnManager.isDefaultNetworkActive();
+ if (isActive) {
+ cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed = nowElapsed;
+ return true;
+ }
+ return inactiveForTooLong;
+ }
+
+ // We checked the state recently enough, but the network wasn't active. Assume it still
+ // isn't active.
+ return false;
+ }
+
+ /**
* We know the network has just come up. We want to run any jobs that are ready.
*/
@Override
public void onNetworkActive() {
synchronized (mLock) {
- for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
- final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
- for (int j = jobs.size() - 1; j >= 0; j--) {
- final JobStatus js = jobs.valueAt(j);
- if (js.isReady()) {
- if (DEBUG) {
- Slog.d(TAG, "Running " + js + " due to network activity.");
- }
- mStateChangedListener.onRunJobNow(js);
- }
- }
+ if (mSystemDefaultNetwork == null) {
+ Slog.wtf(TAG, "System default network is unknown but active");
+ return;
}
+
+ CachedNetworkMetadata cachedNetworkMetadata =
+ mAvailableNetworks.get(mSystemDefaultNetwork);
+ if (cachedNetworkMetadata == null) {
+ Slog.wtf(TAG, "System default network capabilities are unknown but active");
+ return;
+ }
+
+ // This method gets called on the system's main thread (not the
+ // AppSchedulingModuleThread), so shift the processing work to a handler to avoid
+ // blocking important operations on the main thread.
+ cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed =
+ cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed =
+ sElapsedRealtimeClock.millis();
+ mHandler.sendEmptyMessage(MSG_PROCESS_ACTIVE_NETWORK);
}
}
+ /** NetworkCallback to track all network changes. */
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
@Override
public void onAvailable(Network network) {
@@ -1565,6 +1691,7 @@
CachedNetworkMetadata cnm = mAvailableNetworks.get(network);
if (cnm == null) {
cnm = new CachedNetworkMetadata();
+ cnm.capabilitiesFirstAcquiredTimeElapsed = sElapsedRealtimeClock.millis();
mAvailableNetworks.put(network, cnm);
} else {
final NetworkCapabilities oldCaps = cnm.networkCapabilities;
@@ -1700,6 +1827,29 @@
}
};
+ /** NetworkCallback to track only changes to the default network. */
+ private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ if (DEBUG) Slog.v(TAG, "systemDefault-onAvailable: " + network);
+ synchronized (mLock) {
+ mSystemDefaultNetwork = network;
+ }
+ }
+
+ @Override
+ public void onLost(Network network) {
+ if (DEBUG) {
+ Slog.v(TAG, "systemDefault-onLost: " + network);
+ }
+ synchronized (mLock) {
+ if (network.equals(mSystemDefaultNetwork)) {
+ mSystemDefaultNetwork = null;
+ }
+ }
+ }
+ };
+
private final INetworkPolicyListener mNetPolicyListener = new NetworkPolicyManager.Listener() {
@Override
public void onRestrictBackgroundChanged(boolean restrictBackground) {
@@ -1762,6 +1912,66 @@
}
}
break;
+
+ case MSG_PROCESS_ACTIVE_NETWORK:
+ removeMessages(MSG_PROCESS_ACTIVE_NETWORK);
+ synchronized (mLock) {
+ if (mSystemDefaultNetwork == null) {
+ break;
+ }
+ if (!Flags.batchConnectivityJobsPerNetwork()) {
+ break;
+ }
+ if (!isNetworkInStateForJobRunLocked(mSystemDefaultNetwork)) {
+ break;
+ }
+
+ final ArrayMap<Network, Boolean> includeInProcessing = new ArrayMap<>();
+ // Try to get the jobs to piggyback on the active network.
+ for (int u = mTrackedJobs.size() - 1; u >= 0; --u) {
+ final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(u);
+ for (int j = jobs.size() - 1; j >= 0; --j) {
+ final JobStatus js = jobs.valueAt(j);
+ if (!mSystemDefaultNetwork.equals(js.network)) {
+ final NetworkCapabilities capabilities =
+ getNetworkCapabilities(js.network);
+ if (capabilities == null
+ || !capabilities.hasTransport(
+ NetworkCapabilities.TRANSPORT_VPN)) {
+ includeInProcessing.put(js.network, Boolean.FALSE);
+ continue;
+ }
+ if (includeInProcessing.containsKey(js.network)) {
+ if (!includeInProcessing.get(js.network)) {
+ continue;
+ }
+ } else {
+ // VPNs most likely use the system default network as
+ // their underlying network. If so, process the job.
+ final List<Network> underlyingNetworks =
+ capabilities.getUnderlyingNetworks();
+ final boolean isSystemDefaultInUnderlying =
+ underlyingNetworks != null
+ && underlyingNetworks.contains(
+ mSystemDefaultNetwork);
+ includeInProcessing.put(js.network,
+ isSystemDefaultInUnderlying);
+ if (!isSystemDefaultInUnderlying) {
+ continue;
+ }
+ }
+ }
+ if (js.isReady()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Potentially running " + js
+ + " due to network activity");
+ }
+ mStateChangedListener.onRunJobNow(js);
+ }
+ }
+ }
+ }
+ break;
}
}
}
@@ -1782,8 +1992,15 @@
@VisibleForTesting
static final String KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY =
CC_CONFIG_PREFIX + "avoid_undefined_transport_affinity";
+ private static final String KEY_NETWORK_ACTIVATION_EXPIRATION_MS =
+ CC_CONFIG_PREFIX + "network_activation_expiration_ms";
+ private static final String KEY_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS =
+ CC_CONFIG_PREFIX + "network_activation_max_wait_time_ms";
private static final boolean DEFAULT_AVOID_UNDEFINED_TRANSPORT_AFFINITY = false;
+ private static final long DEFAULT_NETWORK_ACTIVATION_EXPIRATION_MS = 10000L;
+ private static final long DEFAULT_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS =
+ 31 * MINUTE_IN_MILLIS;
/**
* If true, will avoid network transports that don't have an explicitly defined affinity.
@@ -1791,6 +2008,19 @@
public boolean AVOID_UNDEFINED_TRANSPORT_AFFINITY =
DEFAULT_AVOID_UNDEFINED_TRANSPORT_AFFINITY;
+ /**
+ * Amount of time that needs to pass before needing to determine if the network is still
+ * active.
+ */
+ public long NETWORK_ACTIVATION_EXPIRATION_MS = DEFAULT_NETWORK_ACTIVATION_EXPIRATION_MS;
+
+ /**
+ * Max time to wait since the network was last activated before deciding to allow jobs to
+ * run even if the network isn't active
+ */
+ public long NETWORK_ACTIVATION_MAX_WAIT_TIME_MS =
+ DEFAULT_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS;
+
@GuardedBy("mLock")
public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
@NonNull String key) {
@@ -1803,6 +2033,22 @@
mShouldReprocessNetworkCapabilities = true;
}
break;
+ case KEY_NETWORK_ACTIVATION_EXPIRATION_MS:
+ final long gracePeriodMs = properties.getLong(key,
+ DEFAULT_NETWORK_ACTIVATION_EXPIRATION_MS);
+ if (NETWORK_ACTIVATION_EXPIRATION_MS != gracePeriodMs) {
+ NETWORK_ACTIVATION_EXPIRATION_MS = gracePeriodMs;
+ // This doesn't need to trigger network capability reprocessing.
+ }
+ break;
+ case KEY_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS:
+ final long maxWaitMs = properties.getLong(key,
+ DEFAULT_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS);
+ if (NETWORK_ACTIVATION_MAX_WAIT_TIME_MS != maxWaitMs) {
+ NETWORK_ACTIVATION_MAX_WAIT_TIME_MS = maxWaitMs;
+ mShouldReprocessNetworkCapabilities = true;
+ }
+ break;
}
}
@@ -1814,6 +2060,10 @@
pw.print(KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY,
AVOID_UNDEFINED_TRANSPORT_AFFINITY).println();
+ pw.print(KEY_NETWORK_ACTIVATION_EXPIRATION_MS,
+ NETWORK_ACTIVATION_EXPIRATION_MS).println();
+ pw.print(KEY_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS,
+ NETWORK_ACTIVATION_MAX_WAIT_TIME_MS).println();
pw.decreaseIndent();
}
@@ -1925,11 +2175,24 @@
private static class CachedNetworkMetadata {
public NetworkCapabilities networkCapabilities;
public boolean satisfiesTransportAffinities;
+ /**
+ * Track the first time ConnectivityController was informed about the capabilities of the
+ * network after it became available.
+ */
+ public long capabilitiesFirstAcquiredTimeElapsed;
+ public long defaultNetworkActivationLastCheckTimeElapsed;
+ public long defaultNetworkActivationLastConfirmedTimeElapsed;
public String toString() {
return "CNM{"
+ networkCapabilities.toString()
+ ", satisfiesTransportAffinities=" + satisfiesTransportAffinities
+ + ", capabilitiesFirstAcquiredTimeElapsed="
+ + capabilitiesFirstAcquiredTimeElapsed
+ + ", defaultNetworkActivationLastCheckTimeElapsed="
+ + defaultNetworkActivationLastCheckTimeElapsed
+ + ", defaultNetworkActivationLastConfirmedTimeElapsed="
+ + defaultNetworkActivationLastConfirmedTimeElapsed
+ "}";
}
}
@@ -2017,7 +2280,7 @@
pw.println("Aconfig flags:");
pw.increaseIndent();
pw.print(FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER,
- relaxPrefetchConnectivityConstraintOnlyOnCharger());
+ Flags.relaxPrefetchConnectivityConstraintOnlyOnCharger());
pw.println();
pw.decreaseIndent();
pw.println();
diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java
index 787055c..25d3a34 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Device.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Device.java
@@ -172,8 +172,10 @@
RuntimeException ex = new RuntimeException(
"Could not create uinput device \"" + name + "\"");
Log.e(TAG, "Couldn't create uinput device, exiting.", ex);
+ args.recycle();
throw ex;
}
+ args.recycle();
break;
case MSG_INJECT_EVENT:
if (mPtr != 0) {
diff --git a/core/api/current.txt b/core/api/current.txt
index e2c2f27..d410686 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -506,6 +506,7 @@
field public static final int autoSizeTextType = 16844085; // 0x1010535
field public static final int autoStart = 16843445; // 0x10102b5
field @Deprecated public static final int autoText = 16843114; // 0x101016a
+ field @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") public static final int autoTransact;
field public static final int autoUrlDetect = 16843404; // 0x101028c
field public static final int autoVerify = 16844014; // 0x10104ee
field public static final int autofillHints = 16844118; // 0x1010556
@@ -53905,6 +53906,7 @@
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
+ field @FlaggedApi("com.android.window.flags.untrusted_embedding_state_sharing") public static final String PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING = "android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING";
field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
field public static final String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 42b2ef6..66e03db 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10168,6 +10168,7 @@
@FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable {
ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String);
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilterToAutoTransact(@NonNull String);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
@@ -10181,6 +10182,7 @@
method @FlaggedApi("android.nfc.nfc_read_polling_loop") @NonNull public java.util.List<java.lang.String> getPollingLoopFilters();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getPrefixAids();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSettingsActivityName();
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean getShouldAutoTransact(@NonNull String);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getSubsetAids();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getUid();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean hasCategory(@NonNull String);
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 5e55268..6357a20 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -298,6 +298,33 @@
return result;
}
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("ClientTransaction{");
+ if (mTransactionItems != null) {
+ // #addTransactionItem
+ sb.append("\n transactionItems=[");
+ final int size = mTransactionItems.size();
+ for (int i = 0; i < size; i++) {
+ sb.append("\n ").append(mTransactionItems.get(i));
+ }
+ sb.append("\n ]");
+ } else {
+ // #addCallback
+ sb.append("\n callbacks=[");
+ final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0;
+ for (int i = 0; i < size; i++) {
+ sb.append("\n ").append(mActivityCallbacks.get(i));
+ }
+ sb.append("\n ]");
+ // #setLifecycleStateRequest
+ sb.append("\n stateRequest=").append(mLifecycleStateRequest);
+ }
+ sb.append("\n}");
+ return sb.toString();
+ }
+
/** Dump transaction items callback items and final lifecycle state request. */
void dump(@NonNull String prefix, @NonNull PrintWriter pw,
@NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index ba94077..406e00a 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -97,6 +97,10 @@
executeCallbacks(transaction);
executeLifecycleState(transaction);
}
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to execute the transaction: "
+ + transactionToString(transaction, mTransactionHandler));
+ throw e;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/core/java/android/content/pm/ProcessInfo.java b/core/java/android/content/pm/ProcessInfo.java
index 632c0f5..f84b46d 100644
--- a/core/java/android/content/pm/ProcessInfo.java
+++ b/core/java/android/content/pm/ProcessInfo.java
@@ -18,10 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.pm.ApplicationInfo;
import android.os.Parcel;
import android.os.Parcelable;
-import android.text.TextUtils;
import android.util.ArraySet;
import com.android.internal.util.DataClass;
@@ -64,6 +62,12 @@
*/
public @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized;
+ /**
+ * Enable use of embedded dex in the APK, rather than extracted or locally compiled variants.
+ * If false (default), the parent app's configuration determines behavior.
+ */
+ public boolean useEmbeddedDex;
+
@Deprecated
public ProcessInfo(@NonNull ProcessInfo orig) {
this.name = orig.name;
@@ -71,11 +75,12 @@
this.gwpAsanMode = orig.gwpAsanMode;
this.memtagMode = orig.memtagMode;
this.nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized;
+ this.useEmbeddedDex = orig.useEmbeddedDex;
}
- // Code below generated by codegen v1.0.22.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -102,6 +107,9 @@
* disabled, or left unspecified.
* @param nativeHeapZeroInitialized
* Enable automatic zero-initialization of native heap memory allocations.
+ * @param useEmbeddedDex
+ * Enable use of embedded dex in the APK, rather than extracted or locally compiled variants.
+ * If false (default), the parent app's configuration determines behavior.
*/
@DataClass.Generated.Member
public ProcessInfo(
@@ -109,7 +117,8 @@
@Nullable ArraySet<String> deniedPermissions,
@ApplicationInfo.GwpAsanMode int gwpAsanMode,
@ApplicationInfo.MemtagMode int memtagMode,
- @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) {
+ @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized,
+ boolean useEmbeddedDex) {
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
@@ -123,6 +132,7 @@
this.nativeHeapZeroInitialized = nativeHeapZeroInitialized;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+ this.useEmbeddedDex = useEmbeddedDex;
// onConstructed(); // You can define this method to get a callback
}
@@ -145,6 +155,7 @@
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
+ if (useEmbeddedDex) flg |= 0x20;
if (deniedPermissions != null) flg |= 0x2;
dest.writeByte(flg);
dest.writeString(name);
@@ -166,6 +177,7 @@
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
+ boolean _useEmbeddedDex = (flg & 0x20) != 0;
String _name = in.readString();
ArraySet<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
int _gwpAsanMode = in.readInt();
@@ -185,6 +197,7 @@
this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+ this.useEmbeddedDex = _useEmbeddedDex;
// onConstructed(); // You can define this method to get a callback
}
@@ -204,10 +217,10 @@
};
@DataClass.Generated(
- time = 1615850184524L,
- codegenVersion = "1.0.22",
+ time = 1706177470784L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/ProcessInfo.java",
- inputSignatures = "public @android.annotation.NonNull java.lang.String name\npublic @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArraySet.class) android.util.ArraySet<java.lang.String> deniedPermissions\npublic @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\npublic @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\npublic @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\nclass ProcessInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
+ inputSignatures = "public @android.annotation.NonNull java.lang.String name\npublic @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArraySet.class) android.util.ArraySet<java.lang.String> deniedPermissions\npublic @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\npublic @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\npublic @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic boolean useEmbeddedDex\nclass ProcessInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index d7e64b6..4b27953 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -122,3 +122,10 @@
description: "Handle listing of private space apps in settings pages with interleaved content"
bug: "323212460"
}
+
+flag {
+ name: "enable_hiding_profiles"
+ namespace: "profile_experiences"
+ description: "Allow the use of a profileApiAvailability user property to exclude HIDDEN profiles in API results"
+ bug: "316362775"
+}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 4626679..69f9a7d 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -39,6 +39,7 @@
import android.util.Pair;
import android.util.Slog;
+import com.android.internal.pm.pkg.component.flags.Flags;
import com.android.internal.util.ArrayUtils;
import libcore.io.IoUtils;
@@ -89,6 +90,8 @@
private static final String TAG_SDK_LIBRARY = "sdk-library";
private static final int SDK_VERSION = Build.VERSION.SDK_INT;
private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
+ private static final String TAG_PROCESSES = "processes";
+ private static final String TAG_PROCESS = "process";
/**
* Parse only lightweight details about the package at the given location.
@@ -518,6 +521,28 @@
case TAG_SDK_LIBRARY:
isSdkLibrary = true;
break;
+ case TAG_PROCESSES:
+ final int processesDepth = parser.getDepth();
+ int processesType;
+ while ((processesType = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (processesType != XmlPullParser.END_TAG
+ || parser.getDepth() > processesDepth)) {
+ if (processesType == XmlPullParser.END_TAG
+ || processesType == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (parser.getDepth() != processesDepth + 1) {
+ // Search only under <processes>.
+ continue;
+ }
+
+ if (parser.getName().equals(TAG_PROCESS)
+ && Flags.enablePerProcessUseEmbeddedDexAttr()) {
+ useEmbeddedDex |= parser.getAttributeBooleanValue(
+ ANDROID_RES_NAMESPACE, "useEmbeddedDex", false);
+ }
+ }
}
}
} else if (TAG_OVERLAY.equals(parser.getName())) {
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index c0d1fb9..800ba6d 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -150,4 +150,5 @@
void setBootUser(int userId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS})")
int getBootUser();
+ int[] getProfileIdsExcludingHidden(int userId, boolean enabledOnly);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ad0f940..0da19df 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5357,6 +5357,25 @@
}
/**
+ * @return A list of ids of profiles associated with the specified user excluding those with
+ * {@link UserProperties#getProfileApiVisibility()} set to hidden. The returned list includes
+ * the user itself.
+ * @hide
+ * @see #getProfileIds(int, boolean)
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS}, conditional = true)
+ public int[] getProfileIdsExcludingHidden(@UserIdInt int userId, boolean enabled) {
+ try {
+ return mService.getProfileIdsExcludingHidden(userId, enabled);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the device credential owner id of the profile from
* which this method is called, or userId if called from a user that
* is not a profile.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 38cf490..ac2a66e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1472,6 +1472,34 @@
"android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
/**
+ * Activity-level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * that declares whether this (embedded) activity allows the system to share its state with the
+ * host app when it is embedded in a different process in
+ * {@link android.R.attr#allowUntrustedActivityEmbedding untrusted mode}.
+ *
+ * <p>If this property is "true", the host app may receive event callbacks for the activity
+ * state change, including the reparent event and the component name of the activity, which are
+ * required to restore the embedding state after the embedded activity exits picture-in-picture
+ * mode. This property does not share any of the activity content with the host. Note that, for
+ * {@link android.R.attr#knownActivityEmbeddingCerts trusted embedding}, the reparent event and
+ * the component name are always shared with the host regardless of the value of this property.
+ *
+ * <p>The default value is {@code false}.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <activity>
+ * <property
+ * android:name="android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING"
+ * android:value="true|false"/>
+ * </activity>
+ * </pre>
+ */
+ @FlaggedApi(Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING)
+ String PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING =
+ "android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING";
+
+ /**
* Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
* that an app can specify to inform the system that the app is activity embedding split feature
* enabled.
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index cc875ad..c76c7a4 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -21,6 +21,7 @@
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
@@ -93,7 +94,8 @@
| FLAG_WATCH_OUTSIDE_TOUCH
| FLAG_SPLIT_TOUCH
| FLAG_SCALED
- | FLAG_SECURE;
+ | FLAG_SECURE
+ | FLAG_DIM_BEHIND;
private static final RectF sTmpSnapshotSize = new RectF();
private static final RectF sTmpDstFrame = new RectF();
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index e32c8e5..739cf0e 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -17,6 +17,7 @@
package android.window;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.view.Surface.ROTATION_0;
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@@ -31,6 +32,7 @@
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.view.Display;
+import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.InsetsState;
import android.view.WindowInsets;
@@ -157,10 +159,15 @@
new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
currentDisplayInfo.getNaturalHeight()), isScreenRound,
ACTIVITY_TYPE_UNDEFINED);
- // Set the hardware-provided insets.
+ // Set the hardware-provided insets. Always with the ROTATION_0 result.
+ DisplayCutout cutout = currentDisplayInfo.displayCutout;
+ if (cutout != null && currentDisplayInfo.rotation != ROTATION_0) {
+ cutout = cutout.getRotated(
+ currentDisplayInfo.logicalWidth, currentDisplayInfo.logicalHeight,
+ currentDisplayInfo.rotation, ROTATION_0);
+ }
windowInsets = new WindowInsets.Builder(windowInsets).setRoundedCorners(
- currentDisplayInfo.roundedCorners)
- .setDisplayCutout(currentDisplayInfo.displayCutout).build();
+ currentDisplayInfo.roundedCorners).setDisplayCutout(cutout).build();
// Multiply default density scale because WindowMetrics provide the density value with
// the scaling factor for the Density Independent Pixel unit, which is the same unit
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 65075ae..baefe7b 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -226,6 +226,9 @@
setTopOnBackInvokedCallback(null);
}
+ // We should also stop running animations since all callbacks have been removed.
+ // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
+ Handler.getMain().post(mProgressAnimator::reset);
mAllCallbacks.clear();
mOnBackInvokedCallbacks.clear();
}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 2e20cce..bc63881 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -58,6 +58,14 @@
flag {
namespace: "windowing_sdk"
+ name: "untrusted_embedding_state_sharing"
+ description: "Feature flag to enable state sharing in untrusted embedding when apps opt in."
+ bug: "293647332"
+ is_fixed_read_only: true
+}
+
+flag {
+ namespace: "windowing_sdk"
name: "embedded_activity_back_nav_flag"
description: "Refines embedded activity back navigation behavior"
bug: "293642394"
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java
index e5247f9..852ed1c 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java
@@ -51,4 +51,6 @@
@ApplicationInfo.NativeHeapZeroInitialized
int getNativeHeapZeroInitialized();
+
+ boolean isUseEmbeddedDex();
}
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
index 212fb86..ff9b11a 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
@@ -54,6 +54,8 @@
@ApplicationInfo.NativeHeapZeroInitialized
private int nativeHeapZeroInitialized = ApplicationInfo.ZEROINIT_DEFAULT;
+ private boolean useEmbeddedDex;
+
public ParsedProcessImpl() {
}
@@ -65,6 +67,7 @@
gwpAsanMode = other.getGwpAsanMode();
memtagMode = other.getMemtagMode();
nativeHeapZeroInitialized = other.getNativeHeapZeroInitialized();
+ useEmbeddedDex = other.isUseEmbeddedDex();
}
public void addStateFrom(@NonNull ParsedProcess other) {
@@ -72,6 +75,7 @@
gwpAsanMode = other.getGwpAsanMode();
memtagMode = other.getMemtagMode();
nativeHeapZeroInitialized = other.getNativeHeapZeroInitialized();
+ useEmbeddedDex = other.isUseEmbeddedDex();
final ArrayMap<String, String> oacn = other.getAppClassNamesByPackage();
for (int i = 0; i < oacn.size(); i++) {
@@ -115,7 +119,8 @@
@NonNull Set<String> deniedPermissions,
@ApplicationInfo.GwpAsanMode int gwpAsanMode,
@ApplicationInfo.MemtagMode int memtagMode,
- @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) {
+ @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized,
+ boolean useEmbeddedDex) {
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
@@ -134,6 +139,7 @@
this.nativeHeapZeroInitialized = nativeHeapZeroInitialized;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+ this.useEmbeddedDex = useEmbeddedDex;
// onConstructed(); // You can define this method to get a callback
}
@@ -172,6 +178,11 @@
}
@DataClass.Generated.Member
+ public boolean isUseEmbeddedDex() {
+ return useEmbeddedDex;
+ }
+
+ @DataClass.Generated.Member
public @NonNull ParsedProcessImpl setName(@NonNull String value) {
name = value;
com.android.internal.util.AnnotationValidations.validate(
@@ -223,6 +234,12 @@
}
@DataClass.Generated.Member
+ public @NonNull ParsedProcessImpl setUseEmbeddedDex( boolean value) {
+ useEmbeddedDex = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
static Parcelling<Set<String>> sParcellingForDeniedPermissions =
Parcelling.Cache.get(
Parcelling.BuiltIn.ForInternedStringSet.class);
@@ -239,6 +256,9 @@
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
+ byte flg = 0;
+ if (useEmbeddedDex) flg |= 0x40;
+ dest.writeByte(flg);
dest.writeString(name);
dest.writeMap(appClassNamesByPackage);
sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags);
@@ -258,6 +278,8 @@
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
+ byte flg = in.readByte();
+ boolean _useEmbeddedDex = (flg & 0x40) != 0;
String _name = in.readString();
ArrayMap<String,String> _appClassNamesByPackage = new ArrayMap();
in.readMap(_appClassNamesByPackage, String.class.getClassLoader());
@@ -284,6 +306,7 @@
this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+ this.useEmbeddedDex = _useEmbeddedDex;
// onConstructed(); // You can define this method to get a callback
}
@@ -303,10 +326,10 @@
};
@DataClass.Generated(
- time = 1701445656489L,
+ time = 1706177189475L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java",
- inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic void addStateFrom(com.android.internal.pm.pkg.component.ParsedProcess)\npublic void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedProcess, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
+ inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\nprivate boolean useEmbeddedDex\npublic void addStateFrom(com.android.internal.pm.pkg.component.ParsedProcess)\npublic void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedProcess, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java
index 3b2056e..dd58815f 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java
@@ -27,6 +27,7 @@
import android.util.ArraySet;
import com.android.internal.R;
+import com.android.internal.pm.pkg.component.flags.Flags;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
import com.android.internal.pm.pkg.parsing.ParsingUtils;
import com.android.internal.util.CollectionUtils;
@@ -111,6 +112,12 @@
proc.setNativeHeapZeroInitialized(
v ? ApplicationInfo.ZEROINIT_ENABLED : ApplicationInfo.ZEROINIT_DISABLED);
}
+ if (Flags.enablePerProcessUseEmbeddedDexAttr()) {
+ proc.setUseEmbeddedDex(
+ sa.getBoolean(R.styleable.AndroidManifestProcess_useEmbeddedDex, false));
+ } else {
+ proc.setUseEmbeddedDex(false);
+ }
} finally {
sa.recycle();
}
diff --git a/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig b/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig
new file mode 100644
index 0000000..ea9abdb
--- /dev/null
+++ b/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.internal.pm.pkg.component.flags"
+
+flag {
+ name: "enable_per_process_use_embedded_dex_attr"
+ namespace: "machine_learning"
+ description: "This flag enables support for parsing per-process useEmbeddedDex attribute."
+ is_fixed_read_only: true
+ bug: "295870718"
+}
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index f154b57..e7b1d09 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4375,9 +4375,14 @@
<!-- Specify one or more <code>polling-loop-filter</code> elements inside a
<code>host-apdu-service</code> to indicate polling loop frames that
your service can handle. -->
+ <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") -->
<declare-styleable name="PollingLoopFilter">
<!-- The polling loop frame. This attribute is mandatory. -->
<attr name="name" />
+ <!-- Whether or not the system should automatically start a transaction when this polling
+ loop filter matches. If not set, default value is false. -->
+ <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") -->
+ <attr name="autoTransact" format="boolean"/>
</declare-styleable>
<!-- Use <code>host-nfcf-service</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 6884fc0..65c4d9f 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1273,11 +1273,17 @@
null to indicate no split types are offered. -->
<attr name="splitTypes" format="string" />
- <!-- Flag to specify if this app wants to run the dex within its APK but not extracted or
- locally compiled variants. This keeps the dex code protected by the APK signature. Such
- apps will always run in JIT mode (same when they are first installed), and the system will
- never generate ahead-of-time compiled code for them. Depending on the app's workload,
- there may be some run time performance change, noteably the cold start time. -->
+ <!-- Flag to specify if this app (or process) wants to run the dex within its APK but not
+ extracted or locally compiled variants. This keeps the dex code protected by the APK
+ signature. Such apps (or processes) will always run in JIT mode (same when they are first
+ installed). If enabled at the app level, the system will never generate ahead-of-time
+ compiled code for the app. Depending on the app's workload, there may be some run time
+ performance change, noteably the cold start time.
+
+ <p>This attribute can be applied to either
+ {@link android.R.styleable#AndroidManifestProcess process} or
+ {@link android.R.styleable#AndroidManifestApplication application} tags. If enabled at the
+ app level, any process level attribute is effectively ignored. -->
<attr name="useEmbeddedDex" format="boolean" />
<!-- Extra options for an activity's UI. Applies to either the {@code <activity>} or
@@ -2793,6 +2799,7 @@
<attr name="gwpAsanMode" />
<attr name="memtagMode" />
<attr name="nativeHeapZeroInitialized" />
+ <attr name="useEmbeddedDex" />
</declare-styleable>
<!-- The <code>deny-permission</code> tag specifies that a permission is to be denied
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 72bfa8a..81a8908 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -145,6 +145,8 @@
<public name="fragmentSuffix"/>
<!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") -->
<public name="useBoundsForWidth"/>
+ <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") -->
+ <public name="autoTransact"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 52ff0d4..a709d7b 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -20,7 +20,6 @@
import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeast;
@@ -359,7 +358,7 @@
}
@Test
- public void onDetachFromWindow_cancelsBackAnimation() throws RemoteException {
+ public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
@@ -369,12 +368,13 @@
waitForIdle();
verify(mCallback1).onBackStarted(any(BackEvent.class));
- // This should trigger mCallback1.onBackCancelled() and unset the callback in WM
+ // This should trigger mCallback1.onBackCancelled()
mDispatcher.detachFromWindow();
+ // This should be ignored by mCallback1
+ callbackInfo.getCallback().onBackInvoked();
- OnBackInvokedCallbackInfo callbackInfo1 = assertSetCallbackInfo();
- assertNull(callbackInfo1);
waitForIdle();
+ verify(mCallback1, never()).onBackInvoked();
verify(mCallback1).onBackCancelled();
}
}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 9f94ef9..28cf250 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -73,6 +73,7 @@
method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcListenerDeviceInfo getWlcListenerDeviceInfo();
method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
method public boolean isEnabled();
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeEnabled();
method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled();
method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 293e5d1..c444740 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -88,6 +88,7 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
boolean enableReaderOption(boolean enable);
boolean isObserveModeSupported();
+ boolean isObserveModeEnabled();
boolean setObserveMode(boolean enabled);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 3d74980..782af5f 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1208,6 +1208,22 @@
}
/**
+ * Returns whether Observe Mode is currently enabled or not.
+ *
+ * @return true if observe mode is enabled, false otherwise.
+ */
+
+ @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+ public boolean isObserveModeEnabled() {
+ try {
+ return sService.isObserveModeEnabled();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
+ }
+
+ /**
* Controls whether the NFC adapter will allow transactions to proceed or be in observe mode
* and simply observe and notify the APDU service of polling loop frames. See
* {@link #isObserveModeSupported()} for a description of observe mode.
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index 426c5aa..3254a39 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -105,6 +105,8 @@
private final ArrayList<String> mPollingLoopFilters;
+ private final Map<String, Boolean> mAutoTransact;
+
/**
* Whether this service should only be started when the device is unlocked.
*/
@@ -173,6 +175,7 @@
this.mStaticAidGroups = new HashMap<String, AidGroup>();
this.mDynamicAidGroups = new HashMap<String, AidGroup>();
this.mPollingLoopFilters = new ArrayList<String>();
+ this.mAutoTransact = new HashMap<String, Boolean>();
this.mOffHostName = offHost;
this.mStaticOffHostName = staticOffHost;
this.mOnHost = onHost;
@@ -287,6 +290,7 @@
mStaticAidGroups = new HashMap<String, AidGroup>();
mDynamicAidGroups = new HashMap<String, AidGroup>();
mPollingLoopFilters = new ArrayList<String>();
+ mAutoTransact = new HashMap<String, Boolean>();
mOnHost = onHost;
final int depth = parser.getDepth();
@@ -377,6 +381,10 @@
a.getString(com.android.internal.R.styleable.PollingLoopFilter_name)
.toUpperCase(Locale.ROOT);
mPollingLoopFilters.add(plf);
+ boolean autoTransact = a.getBoolean(
+ com.android.internal.R.styleable.PollingLoopFilter_autoTransact,
+ false);
+ mAutoTransact.put(plf, autoTransact);
a.recycle();
}
}
@@ -444,6 +452,17 @@
}
/**
+ * Returns whether this service would like to automatically transact for a given plf.
+ *
+ * @param plf the polling loop filter to query.
+ * @return {@code true} indicating to auto transact, {@code false} indicating to not.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public boolean getShouldAutoTransact(@NonNull String plf) {
+ return mAutoTransact.getOrDefault(plf.toUpperCase(Locale.ROOT), false);
+ }
+
+ /**
* Returns a consolidated list of AIDs with prefixes from the AID groups
* registered by this service. Note that if a service has both
* a static (manifest-based) AID group for a category and a dynamic
@@ -630,6 +649,21 @@
}
/**
+ * Add a Polling Loop Filter. Custom NFC polling frames that match this filter will cause the
+ * device to exit observe mode, just as if
+ * {@link android.nfc.NfcAdapter#setTransactionAllowed(boolean)} had been called with true,
+ * allowing transactions to proceed. The matching frame will also be delivered to
+ * {@link HostApduService#processPollingFrames(List)}.
+ *
+ * @param pollingLoopFilter this polling loop filter to add.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void addPollingLoopFilterToAutoTransact(@NonNull String pollingLoopFilter) {
+ mPollingLoopFilters.add(pollingLoopFilter.toUpperCase(Locale.ROOT));
+ mAutoTransact.put(pollingLoopFilter.toUpperCase(Locale.ROOT), true);
+ }
+
+ /**
* Remove a Polling Loop Filter. Custom NFC polling frames that match this filter will no
* longer be delivered to {@link HostApduService#processPollingFrames(List)}.
* @param pollingLoopFilter this polling loop filter to add.
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 6e4a4ea..2ad7192 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -168,6 +168,9 @@
"Note that, even after this callback is called, we're waiting for all windows to finish "
" drawing."
bug: "295873557"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 4cc7332..51d2a03 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -30,12 +30,13 @@
import androidx.lifecycle.LifecycleOwner
import com.android.compose.theme.PlatformTheme
import com.android.compose.ui.platform.DensityAwareComposeView
+import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.composable.BouncerContent
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
-import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
+import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
@@ -129,8 +130,9 @@
return ComposeView(context).apply {
setContent {
PlatformTheme {
- DisplayCutoutProvider(
- displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets)
+ ScreenDecorProvider(
+ displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets),
+ screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
) {
SceneContainer(
viewModel = viewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutoutProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
similarity index 70%
rename from packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutoutProvider.kt
rename to packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
index ed393c0..76bd4ec 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutoutProvider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
@@ -21,17 +21,28 @@
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
import kotlinx.coroutines.flow.StateFlow
/** The bounds and [CutoutLocation] of the current display. */
val LocalDisplayCutout = staticCompositionLocalOf { DisplayCutout() }
+/** The corner radius in px of the current display. */
+val LocalScreenCornerRadius = staticCompositionLocalOf { 0.dp }
+
@Composable
-fun DisplayCutoutProvider(
+fun ScreenDecorProvider(
displayCutout: StateFlow<DisplayCutout>,
+ screenCornerRadius: Float,
content: @Composable () -> Unit,
) {
val cutout by displayCutout.collectAsState()
-
- CompositionLocalProvider(LocalDisplayCutout provides cutout) { content() }
+ val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() }
+ CompositionLocalProvider(
+ LocalScreenCornerRadius provides screenCornerRadiusDp,
+ LocalDisplayCutout provides cutout
+ ) {
+ content()
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 0e08a19..3fb8254 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -51,11 +51,13 @@
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
@@ -63,10 +65,15 @@
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
+import com.android.compose.ui.util.lerp
+import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.notifications.ui.composable.Notifications.Form
+import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS
+import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
import com.android.systemui.scene.ui.composable.Gone
import com.android.systemui.scene.ui.composable.Shade
import com.android.systemui.shade.ui.composable.ShadeHeader
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder.SCRIM_CORNER_RADIUS
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import kotlin.math.roundToInt
@@ -77,6 +84,13 @@
val ShelfSpace = ElementKey("ShelfSpace")
}
+ // Expansion fraction thresholds (between 0-1f) at which the corresponding value should be
+ // at its maximum, given they are at their minimum value at expansion = 0f.
+ object TransitionThresholds {
+ const val EXPANSION_FOR_MAX_CORNER_RADIUS = 0.1f
+ const val EXPANSION_FOR_MAX_SCRIM_ALPHA = 0.3f
+ }
+
enum class Form {
HunFromTop,
Stack,
@@ -125,19 +139,19 @@
modifier: Modifier = Modifier,
) {
val density = LocalDensity.current
- val cornerRadius by viewModel.cornerRadiusDp.collectAsState()
+ val screenCornerRadius = LocalScreenCornerRadius.current
val expansionFraction by viewModel.expandFraction.collectAsState(0f)
val navBarHeight =
with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
- val statusBarHeight =
- with(density) { WindowInsets.systemBars.asPaddingValues().calculateTopPadding().toPx() }
- val displayCutoutHeight =
- with(density) { WindowInsets.displayCutout.asPaddingValues().calculateTopPadding().toPx() }
+ val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
+ val displayCutoutHeight = WindowInsets.displayCutout.asPaddingValues().calculateTopPadding()
val screenHeight =
- with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } +
- navBarHeight +
- maxOf(statusBarHeight, displayCutoutHeight)
+ with(density) {
+ (LocalConfiguration.current.screenHeightDp.dp +
+ maxOf(statusBarHeight, displayCutoutHeight))
+ .toPx()
+ } + navBarHeight
val contentHeight = viewModel.intrinsicContentHeight.collectAsState()
@@ -171,26 +185,53 @@
.collect { shouldCollapse -> if (shouldCollapse) scrimOffset.value = 0f }
}
- Box(modifier = modifier.element(Notifications.Elements.NotificationScrim)) {
+ Box(
+ modifier =
+ modifier
+ .element(Notifications.Elements.NotificationScrim)
+ .offset {
+ // if scrim is expanded while transitioning to Gone scene, increase the offset
+ // in step with the transition so that it is 0 when it completes.
+ if (
+ scrimOffset.value < 0 &&
+ layoutState.isTransitioning(from = Shade, to = Gone)
+ ) {
+ IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt())
+ } else {
+ IntOffset(x = 0, y = scrimOffset.value.roundToInt())
+ }
+ }
+ .graphicsLayer {
+ shape =
+ calculateCornerRadius(
+ screenCornerRadius,
+ { expansionFraction },
+ layoutState.isTransitioningBetween(Gone, Shade)
+ )
+ .let {
+ RoundedCornerShape(
+ topStart = it,
+ topEnd = it,
+ )
+ }
+ clip = true
+ }
+ ) {
+ // Creates a cutout in the background scrim in the shape of the notifications scrim.
+ // Only visible when notif scrim alpha < 1, during shade expansion.
Spacer(
modifier =
- Modifier.fillMaxSize()
- .graphicsLayer {
- shape = RoundedCornerShape(cornerRadius.dp)
- clip = true
- }
- .drawBehind { drawRect(Color.Black, blendMode = BlendMode.DstOut) }
+ Modifier.fillMaxSize().drawBehind {
+ drawRect(Color.Black, blendMode = BlendMode.DstOut)
+ }
)
Box(
modifier =
Modifier.fillMaxSize()
- .offset { IntOffset(0, scrimOffset.value.roundToInt()) }
.graphicsLayer {
- shape = RoundedCornerShape(cornerRadius.dp)
- clip = true
alpha =
if (layoutState.isTransitioningBetween(Gone, Shade)) {
- (expansionFraction / 0.3f).coerceAtMost(1f)
+ (expansionFraction / EXPANSION_FOR_MAX_SCRIM_ALPHA).coerceAtMost(1f)
} else 1f
}
.background(MaterialTheme.colorScheme.surface)
@@ -278,10 +319,10 @@
.onSizeChanged { size: IntSize ->
debugLog(viewModel) { "STACK onSizeChanged: size=$size" }
}
- .onPlaced { coordinates: LayoutCoordinates ->
+ .onGloballyPositioned { coordinates: LayoutCoordinates ->
viewModel.onContentTopChanged(coordinates.positionInWindow().y)
debugLog(viewModel) {
- "STACK onPlaced:" +
+ "STACK onGloballyPositioned:" +
" size=${coordinates.size}" +
" position=${coordinates.positionInWindow()}" +
" bounds=${coordinates.boundsInWindow()}"
@@ -310,6 +351,23 @@
}
}
+private fun calculateCornerRadius(
+ screenCornerRadius: Dp,
+ expansionFraction: () -> Float,
+ transitioning: Boolean,
+): Dp {
+ return if (transitioning) {
+ lerp(
+ start = screenCornerRadius.value,
+ stop = SCRIM_CORNER_RADIUS,
+ fraction = (expansionFraction() / EXPANSION_FOR_MAX_CORNER_RADIUS).coerceAtMost(1f),
+ )
+ .dp
+ } else {
+ SCRIM_CORNER_RADIUS.dp
+ }
+}
+
private inline fun debugLog(
viewModel: NotificationsPlaceholderViewModel,
msg: () -> Any,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 5531f9c..969dec3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -36,7 +36,6 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
@@ -47,11 +46,6 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.boundsInWindow
-import androidx.compose.ui.layout.onPlaced
-import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
@@ -62,7 +56,6 @@
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.scene.shared.model.SceneKey
@@ -122,8 +115,6 @@
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
- val cornerRadius by viewModel.notifications.cornerRadiusDp.collectAsState()
-
// TODO(b/280887232): implement the real UI.
Box(modifier = modifier.fillMaxSize()) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
@@ -155,9 +146,9 @@
// a background that extends to the edges.
Spacer(
modifier =
- Modifier.element(Shade.Elements.ScrimBackground)
+ Modifier.element(Shade.Elements.BackgroundScrim)
.fillMaxSize()
- .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
+ .background(MaterialTheme.colorScheme.scrim)
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@@ -242,32 +233,5 @@
}
}
}
- // Scrim with height 0 aligned to bottom of the screen to facilitate shared element
- // transition from Shade scene.
- Box(
- modifier =
- Modifier.element(Notifications.Elements.NotificationScrim)
- .fillMaxWidth()
- .height(0.dp)
- .graphicsLayer {
- shape = RoundedCornerShape(cornerRadius.dp)
- clip = true
- alpha = 1f
- }
- .background(MaterialTheme.colorScheme.surface)
- .align(Alignment.BottomCenter)
- .onPlaced { coordinates: LayoutCoordinates ->
- viewModel.notifications.onContentTopChanged(
- coordinates.positionInWindow().y
- )
- val boundsInWindow = coordinates.boundsInWindow()
- viewModel.notifications.onBoundsChanged(
- left = boundsInWindow.left,
- top = boundsInWindow.top,
- right = boundsInWindow.right,
- bottom = boundsInWindow.bottom,
- )
- }
- )
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 770d654..736ee1f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -16,18 +16,18 @@
package com.android.systemui.scene.ui.composable
-import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -63,6 +63,6 @@
override fun SceneScope.Content(
modifier: Modifier,
) {
- Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim))
+ Spacer(modifier.fillMaxSize())
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 0c2c519..1223ace 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -3,6 +3,7 @@
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.shade.ui.composable.ShadeHeader
@@ -10,5 +11,6 @@
spec = tween(durationMillis = 500)
fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContent) }
- translate(QuickSettings.Elements.Content, Edge.Top, true)
+ translate(QuickSettings.Elements.Content, y = -ShadeHeader.Dimensions.CollapsedHeight * .66f)
+ translate(Notifications.Elements.NotificationScrim, Edge.Top, false)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
index ebc343d..2d5cf5c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
@@ -10,9 +10,8 @@
fun TransitionBuilder.lockscreenToShadeTransition() {
spec = tween(durationMillis = 500)
- translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
fractionRange(end = 0.5f) {
- fade(Shade.Elements.ScrimBackground)
+ fade(Shade.Elements.BackgroundScrim)
translate(
QuickSettings.Elements.CollapsedGrid,
Edge.Top,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 497fe87..677df7e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -38,6 +38,7 @@
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
@@ -74,8 +75,8 @@
object Elements {
val QuickSettings = ElementKey("ShadeQuickSettings")
val MediaCarousel = ElementKey("ShadeMediaCarousel")
- val Scrim = ElementKey("ShadeScrim")
- val ScrimBackground = ElementKey("ShadeScrimBackground")
+ val BackgroundScrim =
+ ElementKey("ShadeBackgroundScrim", scenePicker = LowestZIndexScenePicker)
}
object Dimensions {
@@ -162,7 +163,9 @@
Box(
modifier =
- modifier.element(Shade.Elements.Scrim).background(MaterialTheme.colorScheme.scrim),
+ modifier
+ .element(Shade.Elements.BackgroundScrim)
+ .background(MaterialTheme.colorScheme.scrim),
)
Box {
Layout(
@@ -236,17 +239,7 @@
check(measurables[1].size == 1)
val quickSettingsPlaceable = measurables[0][0].measure(constraints)
-
- val notificationsMeasurable = measurables[1][0]
- val notificationsScrimMaxHeight =
- constraints.maxHeight - ShadeHeader.Dimensions.CollapsedHeight.roundToPx()
- val notificationsPlaceable =
- notificationsMeasurable.measure(
- constraints.copy(
- minHeight = notificationsScrimMaxHeight,
- maxHeight = notificationsScrimMaxHeight
- )
- )
+ val notificationsPlaceable = measurables[1][0].measure(constraints)
maxNotifScrimTop.value = quickSettingsPlaceable.height.toFloat()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
similarity index 62%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index 84b2c4b..53b262b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -21,7 +21,11 @@
import android.graphics.Rect
import android.view.Display
import android.view.DisplayCutout
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.CameraProtectionInfo
+import com.android.systemui.SysUICutoutInformation
+import com.android.systemui.SysUICutoutProvider
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -38,30 +42,30 @@
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
+@RunWith(AndroidJUnit4::class)
@SmallTest
class StatusBarContentInsetsProviderTest : SysuiTestCase() {
- @Mock private lateinit var dc: DisplayCutout
- @Mock private lateinit var contextMock: Context
- @Mock private lateinit var display: Display
- private lateinit var configurationController: ConfigurationController
-
+ private val sysUICutout = mock<SysUICutoutInformation>()
+ private val dc = mock<DisplayCutout>()
+ private val contextMock = mock<Context>()
+ private val display = mock<Display>()
private val configuration = Configuration()
+ private lateinit var configurationController: ConfigurationController
+
@Before
fun setup() {
- MockitoAnnotations.initMocks(this)
- `when`(contextMock.display).thenReturn(display)
+ whenever(sysUICutout.cutout).thenReturn(dc)
+ whenever(contextMock.display).thenReturn(display)
context.ensureTestableResources()
- `when`(contextMock.resources).thenReturn(context.resources)
- `when`(contextMock.resources.configuration).thenReturn(configuration)
- `when`(contextMock.createConfigurationContext(any())).thenAnswer {
+ whenever(contextMock.resources).thenReturn(context.resources)
+ whenever(contextMock.resources.configuration).thenReturn(configuration)
+ whenever(contextMock.createConfigurationContext(any())).thenAnswer {
context.createConfigurationContext(it.arguments[0] as Configuration)
}
configurationController = ConfigurationControllerImpl(contextMock)
@@ -117,7 +121,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -161,7 +165,7 @@
}
@Test
- fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout() {
+ fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout_noCameraProtection() {
// GIVEN a device in portrait mode with width < height and a display cutout in the top-left
val screenBounds = Rect(0, 0, 1080, 2160)
val dcBounds = Rect(0, 0, 100, 100)
@@ -174,7 +178,7 @@
val dotWidth = 10
val statusBarContentHeight = 15
- `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
// THEN rotations which share a short side should use the greater value between rounded
// corner padding and the display cutout's size
@@ -187,7 +191,7 @@
var bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -208,7 +212,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -231,7 +235,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -253,7 +257,335 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+ }
+
+ @Test
+ fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout_withCameraProtection() {
+ // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+ val screenBounds = Rect(0, 0, 1080, 2160)
+ val dcBounds = Rect(0, 0, 100, 100)
+ val protectionBounds = Rect(10, 10, 110, 110)
+ val minLeftPadding = 20
+ val minRightPadding = 20
+ val sbHeightPortrait = 100
+ val sbHeightLandscape = 60
+ val currentRotation = ROTATION_NONE
+ val isRtl = false
+ val dotWidth = 10
+ val statusBarContentHeight = 15
+
+ val protectionInfo = mock<CameraProtectionInfo> {
+ whenever(this.cutoutBounds).thenReturn(protectionBounds)
+ }
+ whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+ // THEN rotations which share a short side should use the greater value between rounded
+ // corner padding, the display cutout's size, and the camera protections' size.
+ var targetRotation = ROTATION_NONE
+ var expectedBounds = Rect(protectionBounds.right,
+ 0,
+ screenBounds.right - minRightPadding,
+ sbHeightPortrait)
+
+ var bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ targetRotation = ROTATION_LANDSCAPE
+ expectedBounds = Rect(protectionBounds.bottom,
+ 0,
+ screenBounds.height() - minRightPadding,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // THEN the side that does NOT share a short side with the display cutout ignores the
+ // display cutout bounds
+ targetRotation = ROTATION_UPSIDE_DOWN
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.width() - minRightPadding,
+ sbHeightPortrait)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // Phone in portrait, seascape (rot_270) bounds
+ targetRotation = ROTATION_SEASCAPE
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.height() - protectionBounds.bottom - dotWidth,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+ }
+
+ @Test
+ fun testCalculateInsetsForRotationWithRotatedResources_topRightCutout_noCameraProtection() {
+ // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+ val screenBounds = Rect(0, 0, 1000, 2000)
+ val dcBounds = Rect(900, 0, 1000, 100)
+ val minLeftPadding = 20
+ val minRightPadding = 20
+ val sbHeightPortrait = 100
+ val sbHeightLandscape = 60
+ val currentRotation = ROTATION_NONE
+ val isRtl = false
+ val dotWidth = 10
+ val statusBarContentHeight = 15
+
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+ // THEN rotations which share a short side should use the greater value between rounded
+ // corner padding and the display cutout's size
+ var targetRotation = ROTATION_NONE
+ var expectedBounds = Rect(minLeftPadding,
+ 0,
+ dcBounds.left - dotWidth,
+ sbHeightPortrait)
+
+ var bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ targetRotation = ROTATION_LANDSCAPE
+ expectedBounds = Rect(dcBounds.height(),
+ 0,
+ screenBounds.height() - minRightPadding,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // THEN the side that does NOT share a short side with the display cutout ignores the
+ // display cutout bounds
+ targetRotation = ROTATION_UPSIDE_DOWN
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.width() - minRightPadding,
+ sbHeightPortrait)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // Phone in portrait, seascape (rot_270) bounds
+ targetRotation = ROTATION_SEASCAPE
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.height() - dcBounds.height() - dotWidth,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+ }
+
+ @Test
+ fun testCalculateInsetsForRotationWithRotatedResources_topRightCutout_withCameraProtection() {
+ // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+ val screenBounds = Rect(0, 0, 1000, 2000)
+ val dcBounds = Rect(900, 0, 1000, 100)
+ val protectionBounds = Rect(890, 10, 990, 110)
+ val minLeftPadding = 20
+ val minRightPadding = 20
+ val sbHeightPortrait = 100
+ val sbHeightLandscape = 60
+ val currentRotation = ROTATION_NONE
+ val isRtl = false
+ val dotWidth = 10
+ val statusBarContentHeight = 15
+
+ val protectionInfo = mock<CameraProtectionInfo> {
+ whenever(this.cutoutBounds).thenReturn(protectionBounds)
+ }
+ whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+ // THEN rotations which share a short side should use the greater value between rounded
+ // corner padding, the display cutout's size, and the camera protections' size.
+ var targetRotation = ROTATION_NONE
+ var expectedBounds = Rect(minLeftPadding,
+ 0,
+ protectionBounds.left - dotWidth,
+ sbHeightPortrait)
+
+ var bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ targetRotation = ROTATION_LANDSCAPE
+ expectedBounds = Rect(protectionBounds.bottom,
+ 0,
+ screenBounds.height() - minRightPadding,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // THEN the side that does NOT share a short side with the display cutout ignores the
+ // display cutout bounds
+ targetRotation = ROTATION_UPSIDE_DOWN
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.width() - minRightPadding,
+ sbHeightPortrait)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // Phone in portrait, seascape (rot_270) bounds
+ targetRotation = ROTATION_SEASCAPE
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.height() - protectionBounds.bottom - dotWidth,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -273,7 +605,7 @@
val bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation = ROTATION_NONE,
targetRotation = ROTATION_NONE,
- displayCutout = dc,
+ sysUICutout = sysUICutout,
maxBounds = Rect(0, 0, 1080, 2160),
statusBarHeight = 100,
minLeft = 0,
@@ -293,7 +625,7 @@
val bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation = ROTATION_NONE,
targetRotation = ROTATION_NONE,
- displayCutout = dc,
+ sysUICutout = sysUICutout,
maxBounds = Rect(0, 0, 1080, 2160),
statusBarHeight = 100,
minLeft = 0,
@@ -321,6 +653,7 @@
val screenBounds = Rect(0, 0, 1080, 2160)
// cutout centered at the top
val dcBounds = Rect(490, 0, 590, 100)
+ val protectionBounds = Rect(480, 10, 600, 90)
val minLeftPadding = 20
val minRightPadding = 20
val sbHeightPortrait = 100
@@ -330,7 +663,11 @@
val dotWidth = 10
val statusBarContentHeight = 15
- `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+ val protectionInfo = mock<CameraProtectionInfo> {
+ whenever(this.cutoutBounds).thenReturn(protectionBounds)
+ }
+ whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
// THEN only the landscape/seascape rotations should avoid the cutout area because of the
// potential letterboxing
@@ -343,7 +680,7 @@
var bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -364,7 +701,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -385,7 +722,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -406,7 +743,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -528,7 +865,7 @@
val dotWidth = 10
val statusBarContentHeight = 15
- `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
// THEN left should be set to the display cutout width, and right should use the minRight
val targetRotation = ROTATION_NONE
@@ -540,7 +877,7 @@
val bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -557,7 +894,7 @@
fun testDisplayChanged_returnsUpdatedInsets() {
// GIVEN: get insets on the first display and switch to the second display
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -576,7 +913,7 @@
// GIVEN: get insets on the first display, switch to the second display,
// get insets and switch back
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
val firstDisplayInsetsFirstCall = provider
@@ -602,7 +939,7 @@
configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
configurationController.onConfigurationChanged(configuration)
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
val listener = object : StatusBarContentInsetsChangedListener {
var triggered = false
@@ -624,7 +961,7 @@
fun onDensityOrFontScaleChanged_listenerNotified() {
configuration.densityDpi = 12
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
val listener = object : StatusBarContentInsetsChangedListener {
var triggered = false
@@ -645,7 +982,7 @@
@Test
fun onThemeChanged_listenerNotified() {
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
val listener = object : StatusBarContentInsetsChangedListener {
var triggered = false
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7a83070..65c69f7 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -542,6 +542,9 @@
<string translatable="false" name="config_protectedCameraId"></string>
<!-- Physical ID for the camera of outer display that needs extra protection -->
<string translatable="false" name="config_protectedPhysicalCameraId"></string>
+ <!-- Unique ID of the outer display that contains the camera that needs protection. -->
+ <string translatable="false" name="config_protectedScreenUniqueId"></string>
+
<!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
<string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>
@@ -550,6 +553,8 @@
<string translatable="false" name="config_protectedInnerCameraId"></string>
<!-- Physical ID for the camera of inner display that needs extra protection -->
<string translatable="false" name="config_protectedInnerPhysicalCameraId"></string>
+ <!-- Unique ID of the inner display that contains the camera that needs protection. -->
+ <string translatable="false" name="config_protectedInnerScreenUniqueId"></string>
<!-- Comma-separated list of packages to exclude from camera protection e.g.
"com.android.systemui,com.android.xyz" -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 33bdca3..51012a4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -318,9 +318,6 @@
notification panel collapses -->
<dimen name="shelf_appear_translation">42dp</dimen>
- <!-- Vertical translation of pulsing notification animations -->
- <dimen name="pulsing_notification_appear_translation">10dp</dimen>
-
<!-- The amount the content shifts upwards when transforming into the shelf -->
<dimen name="shelf_transform_content_shift">32dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
index bbab4de..6314bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
@@ -24,4 +24,5 @@
val physicalCameraId: String?,
val cutoutProtectionPath: Path,
val cutoutBounds: Rect,
+ val displayUniqueId: String?,
)
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
index 8fe9389..6cee28b 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
@@ -25,15 +25,21 @@
import javax.inject.Inject
import kotlin.math.roundToInt
-class CameraProtectionLoader @Inject constructor(private val context: Context) {
+interface CameraProtectionLoader {
+ fun loadCameraProtectionInfoList(): List<CameraProtectionInfo>
+}
- fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> {
+class CameraProtectionLoaderImpl @Inject constructor(private val context: Context) :
+ CameraProtectionLoader {
+
+ override fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> {
val list = mutableListOf<CameraProtectionInfo>()
val front =
loadCameraProtectionInfo(
R.string.config_protectedCameraId,
R.string.config_protectedPhysicalCameraId,
- R.string.config_frontBuiltInDisplayCutoutProtection
+ R.string.config_frontBuiltInDisplayCutoutProtection,
+ R.string.config_protectedScreenUniqueId,
)
if (front != null) {
list.add(front)
@@ -42,7 +48,8 @@
loadCameraProtectionInfo(
R.string.config_protectedInnerCameraId,
R.string.config_protectedInnerPhysicalCameraId,
- R.string.config_innerBuiltInDisplayCutoutProtection
+ R.string.config_innerBuiltInDisplayCutoutProtection,
+ R.string.config_protectedInnerScreenUniqueId,
)
if (inner != null) {
list.add(inner)
@@ -53,7 +60,8 @@
private fun loadCameraProtectionInfo(
cameraIdRes: Int,
physicalCameraIdRes: Int,
- pathRes: Int
+ pathRes: Int,
+ displayUniqueIdRes: Int,
): CameraProtectionInfo? {
val logicalCameraId = context.getString(cameraIdRes)
if (logicalCameraId.isNullOrEmpty()) {
@@ -70,11 +78,13 @@
computed.right.roundToInt(),
computed.bottom.roundToInt()
)
+ val displayUniqueId = context.getString(displayUniqueIdRes)
return CameraProtectionInfo(
logicalCameraId,
physicalCameraId,
protectionPath,
- protectionBounds
+ protectionBounds,
+ displayUniqueId
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt
new file mode 100644
index 0000000..58680a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt
@@ -0,0 +1,26 @@
+/*
+ * 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
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface CameraProtectionModule {
+
+ @Binds fun cameraProtectionLoaderImpl(impl: CameraProtectionLoaderImpl): CameraProtectionLoader
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt b/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt
new file mode 100644
index 0000000..fc0b97e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.view.DisplayCutout
+
+data class SysUICutoutInformation(
+ val cutout: DisplayCutout,
+ val cameraProtection: CameraProtectionInfo?
+)
diff --git a/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
new file mode 100644
index 0000000..aad9341
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
@@ -0,0 +1,47 @@
+/*
+ * 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
+
+import android.content.Context
+import android.view.DisplayCutout
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class SysUICutoutProvider
+@Inject
+constructor(
+ private val context: Context,
+ private val cameraProtectionLoader: CameraProtectionLoader,
+) {
+
+ private val cameraProtectionList by lazy {
+ cameraProtectionLoader.loadCameraProtectionInfoList()
+ }
+
+ fun cutoutInfoForCurrentDisplay(): SysUICutoutInformation? {
+ val display = context.display
+ val displayCutout: DisplayCutout = display.cutout ?: return null
+ val displayUniqueId: String? = display.uniqueId
+ if (displayUniqueId.isNullOrEmpty()) {
+ return SysUICutoutInformation(displayCutout, cameraProtection = null)
+ }
+ val cameraProtection: CameraProtectionInfo? =
+ cameraProtectionList.firstOrNull { it.displayUniqueId == displayUniqueId }
+ return SysUICutoutInformation(displayCutout, cameraProtection)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index efcbd47..28fd9a9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -27,6 +27,7 @@
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.BootCompleteCache;
import com.android.systemui.BootCompleteCacheImpl;
+import com.android.systemui.CameraProtectionModule;
import com.android.systemui.accessibility.AccessibilityModule;
import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
import com.android.systemui.appops.dagger.AppOpsModule;
@@ -177,6 +178,7 @@
BouncerInteractorModule.class,
BouncerRepositoryModule.class,
BouncerViewModule.class,
+ CameraProtectionModule.class,
ClipboardOverlayModule.class,
ClockRegistryModule.class,
CommunalModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 5e0110b..0a11eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1445,12 +1445,12 @@
if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
}
- final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
// TODO(b/322228881): Clean up scene container vs legacy behavior in NSSL
if (SceneContainerFlag.isEnabled()) {
// stackY should be driven by scene container, not NSSL
mAmbientState.setStackY(mTopPadding);
} else {
+ final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
mAmbientState.setStackY(stackY);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index a3e0941..b38d619 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -59,9 +59,6 @@
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
public static final int ANIMATION_DURATION_FOLD_TO_AOD =
AnimatableClockView.ANIMATION_DURATION_FOLD_TO_AOD;
- public static final int ANIMATION_DURATION_PULSE_APPEAR =
- KeyguardSliceView.DEFAULT_ANIM_DURATION;
- public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240;
public static final int ANIMATION_DURATION_PRIORITY_CHANGE = 500;
public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
@@ -70,7 +67,6 @@
private static final int MAX_STAGGER_COUNT = 5;
private final int mGoToFullShadeAppearingTranslation;
- private final int mPulsingAppearingTranslation;
@VisibleForTesting
float mHeadsUpAppearStartAboveScreen;
private final ExpandableViewState mTmpState = new ExpandableViewState();
@@ -102,9 +98,6 @@
mGoToFullShadeAppearingTranslation =
hostLayout.getContext().getResources().getDimensionPixelSize(
R.dimen.go_to_full_shade_appearing_translation);
- mPulsingAppearingTranslation =
- hostLayout.getContext().getResources().getDimensionPixelSize(
- R.dimen.pulsing_notification_appear_translation);
mHeadsUpAppearStartAboveScreen = hostLayout.getContext().getResources()
.getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
mAnimationProperties = new AnimationProperties() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index 0197264..311ba83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -28,9 +28,6 @@
/** The bounds of the notification stack in the current scene. */
val stackBounds = MutableStateFlow(NotificationContainerBounds())
- /** The corner radius of the notification stack, in dp. */
- val cornerRadiusDp = MutableStateFlow(32f)
-
/**
* The height in px of the contents of notification stack. Depending on the number of
* notifications, this can exceed the space available on screen to show notifications, at which
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 8307397..9984ba9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -34,9 +34,6 @@
/** The bounds of the notification stack in the current scene. */
val stackBounds: StateFlow<NotificationContainerBounds> = repository.stackBounds.asStateFlow()
- /** The corner radius of the notification stack, in dp. */
- val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow()
-
/**
* The height in px of the contents of notification stack. Depending on the number of
* notifications, this can exceed the space available on screen to show notifications, at which
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index 50b08b8..814146c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -31,6 +31,7 @@
/** Binds the shared notification container to its view-model. */
object NotificationStackAppearanceViewBinder {
+ const val SCRIM_CORNER_RADIUS = 32f
@JvmStatic
fun bind(
@@ -49,8 +50,8 @@
bounds.top.roundToInt(),
bounds.right.roundToInt(),
bounds.bottom.roundToInt(),
- viewModel.cornerRadiusDp.value.dpToPx(context),
- viewModel.cornerRadiusDp.value.dpToPx(context),
+ SCRIM_CORNER_RADIUS.dpToPx(context),
+ 0,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index fe5bdd4..f3d0d2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -136,8 +136,12 @@
.collect { y -> controller.setTranslationY(y) }
}
- launch {
- viewModel.expansionAlpha.collect { controller.setMaxAlphaForExpansion(it) }
+ if (!sceneContainerFlags.isEnabled()) {
+ launch {
+ viewModel.expansionAlpha.collect {
+ controller.setMaxAlphaForExpansion(it)
+ }
+ }
}
launch {
viewModel.glanceableHubAlpha.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index 56ff7f9..bdf1a64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -74,9 +74,6 @@
/** The bounds of the notification stack in the current scene. */
val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds
- /** The corner radius of the notification stack, in dp. */
- val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp
-
/** The y-coordinate in px of top of the contents of the notification stack. */
val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 8b723da..65d9c9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -28,7 +28,6 @@
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
/**
* ViewModel used by the Notification placeholders inside the scene container to update the
@@ -74,9 +73,6 @@
interactor.setStackBounds(notificationContainerBounds)
}
- /** The corner radius of the placeholder, in dp. */
- val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp
-
/**
* The height in px of the contents of notification stack. Depending on the number of
* notifications, this can exceed the space available on screen to show notifications, at which
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 877bd7c..e84b7a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -44,6 +44,8 @@
import com.android.app.tracing.traceSection
import com.android.systemui.BottomMarginCommand
import com.android.systemui.StatusBarInsetsCommand
+import com.android.systemui.SysUICutoutInformation
+import com.android.systemui.SysUICutoutProvider
import com.android.systemui.statusbar.commandline.CommandRegistry
import java.io.PrintWriter
import java.lang.Math.max
@@ -69,6 +71,7 @@
val configurationController: ConfigurationController,
val dumpManager: DumpManager,
val commandRegistry: CommandRegistry,
+ val sysUICutoutProvider: SysUICutoutProvider,
) : CallbackController<StatusBarContentInsetsChangedListener>,
ConfigurationController.ConfigurationListener,
Dumpable {
@@ -176,7 +179,8 @@
*/
fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets =
traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
- val displayCutout = checkNotNull(context.display).cutout
+ val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+ val displayCutout = sysUICutout?.cutout
val key = getCacheKey(rotation, displayCutout)
val screenBounds = context.resources.configuration.windowConfiguration.maxBounds
@@ -187,7 +191,7 @@
val width = point.logicalWidth(rotation)
val area = insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
- rotation, displayCutout, getResourcesForRotation(rotation, context), key)
+ rotation, sysUICutout, getResourcesForRotation(rotation, context), key)
Insets.of(area.left, area.top, /* right= */ width - area.right, /* bottom= */ 0)
}
@@ -212,10 +216,11 @@
fun getStatusBarContentAreaForRotation(
@Rotation rotation: Int
): Rect {
- val displayCutout = checkNotNull(context.display).cutout
+ val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+ val displayCutout = sysUICutout?.cutout
val key = getCacheKey(rotation, displayCutout)
return insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
- rotation, displayCutout, getResourcesForRotation(rotation, context), key)
+ rotation, sysUICutout, getResourcesForRotation(rotation, context), key)
}
/**
@@ -228,18 +233,18 @@
private fun getAndSetCalculatedAreaForRotation(
@Rotation targetRotation: Int,
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
rotatedResources: Resources,
key: CacheKey
): Rect {
- return getCalculatedAreaForRotation(displayCutout, targetRotation, rotatedResources)
+ return getCalculatedAreaForRotation(sysUICutout, targetRotation, rotatedResources)
.also {
insetsCache.put(key, it)
}
}
private fun getCalculatedAreaForRotation(
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
@Rotation targetRotation: Int,
rotatedResources: Resources
): Rect {
@@ -271,7 +276,7 @@
return calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- displayCutout,
+ sysUICutout,
context.resources.configuration.windowConfiguration.maxBounds,
SystemBarUtils.getStatusBarHeightForRotation(context, targetRotation),
minLeft,
@@ -415,7 +420,7 @@
fun calculateInsetsForRotationWithRotatedResources(
@Rotation currentRotation: Int,
@Rotation targetRotation: Int,
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
maxBounds: Rect,
statusBarHeight: Int,
minLeft: Int,
@@ -434,7 +439,7 @@
val rotZeroBounds = getRotationZeroDisplayBounds(maxBounds, currentRotation)
return getStatusBarContentBounds(
- displayCutout,
+ sysUICutout,
statusBarHeight,
rotZeroBounds.right,
rotZeroBounds.bottom,
@@ -470,7 +475,7 @@
* rotation
*/
private fun getStatusBarContentBounds(
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
sbHeight: Int,
width: Int,
height: Int,
@@ -489,19 +494,17 @@
val logicalDisplayWidth = if (targetRotation.isHorizontal()) height else width
- val cutoutRects = displayCutout?.boundingRects
- if (cutoutRects == null || cutoutRects.isEmpty()) {
- return Rect(minLeft,
- insetTop,
- logicalDisplayWidth - minRight,
- sbHeight)
+ val cutoutRects = sysUICutout?.cutout?.boundingRects
+ if (cutoutRects.isNullOrEmpty()) {
+ return Rect(minLeft, insetTop, logicalDisplayWidth - minRight, sbHeight)
}
- val relativeRotation = if (currentRotation - targetRotation < 0) {
- currentRotation - targetRotation + 4
- } else {
- currentRotation - targetRotation
- }
+ val relativeRotation =
+ if (currentRotation - targetRotation < 0) {
+ currentRotation - targetRotation + 4
+ } else {
+ currentRotation - targetRotation
+ }
// Size of the status bar window for the given rotation relative to our exact rotation
val sbRect = sbRect(relativeRotation, sbHeight, Pair(cWidth, cHeight))
@@ -509,19 +512,26 @@
var leftMargin = minLeft
var rightMargin = minRight
for (cutoutRect in cutoutRects) {
+ val protectionRect = sysUICutout.cameraProtection?.cutoutBounds
+ val actualCutoutRect =
+ if (protectionRect?.intersects(cutoutRect) == true) {
+ rectUnion(cutoutRect, protectionRect)
+ } else {
+ cutoutRect
+ }
// There is at most one non-functional area per short edge of the device. So if the status
// bar doesn't share a short edge with the cutout, we can ignore its insets because there
// will be no letter-boxing to worry about
- if (!shareShortEdge(sbRect, cutoutRect, cWidth, cHeight)) {
+ if (!shareShortEdge(sbRect, actualCutoutRect, cWidth, cHeight)) {
continue
}
- if (cutoutRect.touchesLeftEdge(relativeRotation, cWidth, cHeight)) {
- var logicalWidth = cutoutRect.logicalWidth(relativeRotation)
+ if (actualCutoutRect.touchesLeftEdge(relativeRotation, cWidth, cHeight)) {
+ var logicalWidth = actualCutoutRect.logicalWidth(relativeRotation)
if (isRtl) logicalWidth += dotWidth
leftMargin = max(logicalWidth, leftMargin)
- } else if (cutoutRect.touchesRightEdge(relativeRotation, cWidth, cHeight)) {
- var logicalWidth = cutoutRect.logicalWidth(relativeRotation)
+ } else if (actualCutoutRect.touchesRightEdge(relativeRotation, cWidth, cHeight)) {
+ var logicalWidth = actualCutoutRect.logicalWidth(relativeRotation)
if (!isRtl) logicalWidth += dotWidth
rightMargin = max(rightMargin, logicalWidth)
}
@@ -532,6 +542,11 @@
return Rect(leftMargin, insetTop, logicalDisplayWidth - rightMargin, sbHeight)
}
+private fun rectUnion(first: Rect, second: Rect) = Rect(first).apply { union(second) }
+
+private fun Rect.intersects(other: Rect): Boolean =
+ intersects(other.left, other.top, other.right, other.bottom)
+
/*
* Returns the inset top of the status bar.
*
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
index ceebcb7..e5179dd 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
@@ -37,19 +37,13 @@
*/
fun registerTask(name: String): Runnable {
pendingTasksCount.incrementAndGet()
-
- if (ENABLE_TRACE) {
- Trace.beginAsyncSection("PendingTasksContainer#$name", 0)
- }
+ Trace.beginAsyncSection("PendingTasksContainer#$name", 0)
return Runnable {
+ Trace.endAsyncSection("PendingTasksContainer#$name", 0)
if (pendingTasksCount.decrementAndGet() == 0) {
val onComplete = completionCallback.getAndSet(null)
onComplete?.run()
-
- if (ENABLE_TRACE) {
- Trace.endAsyncSection("PendingTasksContainer#$name", 0)
- }
}
}
}
@@ -82,4 +76,3 @@
fun getPendingCount(): Int = pendingTasksCount.get()
}
-private const val ENABLE_TRACE = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
index 64cd526..f776a63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
@@ -347,8 +347,8 @@
return CameraAvailabilityListener.build(
context,
context.mainExecutor,
- CameraProtectionLoader((context))
- )
+ CameraProtectionLoaderImpl((context))
+ )
.also {
it.addTransitionCallback(cameraTransitionCallback)
it.startListening()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
similarity index 75%
rename from packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
index 238e5e9..a19a0c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
@@ -27,9 +27,9 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-class CameraProtectionLoaderTest : SysuiTestCase() {
+class CameraProtectionLoaderImplTest : SysuiTestCase() {
- private val loader = CameraProtectionLoader(context)
+ private val loader = CameraProtectionLoaderImpl(context)
@Before
fun setUp() {
@@ -39,19 +39,21 @@
R.string.config_frontBuiltInDisplayCutoutProtection,
OUTER_CAMERA_PROTECTION_PATH
)
+ overrideResource(R.string.config_protectedScreenUniqueId, OUTER_SCREEN_UNIQUE_ID)
overrideResource(R.string.config_protectedInnerCameraId, INNER_CAMERA_LOGICAL_ID)
overrideResource(R.string.config_protectedInnerPhysicalCameraId, INNER_CAMERA_PHYSICAL_ID)
overrideResource(
R.string.config_innerBuiltInDisplayCutoutProtection,
INNER_CAMERA_PROTECTION_PATH
)
+ overrideResource(R.string.config_protectedInnerScreenUniqueId, INNER_SCREEN_UNIQUE_ID)
}
@Test
fun loadCameraProtectionInfoList() {
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos)
+ assertThat(protectionList)
.containsExactly(OUTER_CAMERA_PROTECTION_INFO, INNER_CAMERA_PROTECTION_INFO)
}
@@ -59,18 +61,18 @@
fun loadCameraProtectionInfoList_outerCameraIdEmpty_onlyReturnsInnerInfo() {
overrideResource(R.string.config_protectedCameraId, "")
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos).containsExactly(INNER_CAMERA_PROTECTION_INFO)
+ assertThat(protectionList).containsExactly(INNER_CAMERA_PROTECTION_INFO)
}
@Test
fun loadCameraProtectionInfoList_innerCameraIdEmpty_onlyReturnsOuterInfo() {
overrideResource(R.string.config_protectedInnerCameraId, "")
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos).containsExactly(OUTER_CAMERA_PROTECTION_INFO)
+ assertThat(protectionList).containsExactly(OUTER_CAMERA_PROTECTION_INFO)
}
@Test
@@ -78,13 +80,16 @@
overrideResource(R.string.config_protectedCameraId, "")
overrideResource(R.string.config_protectedInnerCameraId, "")
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos).isEmpty()
+ assertThat(protectionList).isEmpty()
}
+ private fun loadProtectionList() =
+ loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+
private fun CameraProtectionInfo.toTestableVersion() =
- TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds)
+ TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds, displayUniqueId)
/**
* "Testable" version, because the original version contains a Path property, which doesn't
@@ -94,6 +99,7 @@
val logicalCameraId: String,
val physicalCameraId: String?,
val cutoutBounds: Rect,
+ val displayUniqueId: String?,
)
companion object {
@@ -102,11 +108,13 @@
private const val OUTER_CAMERA_PROTECTION_PATH = "M 0,0 H 10,10 V 10,10 H 0,10 Z"
private val OUTER_CAMERA_PROTECTION_BOUNDS =
Rect(/* left = */ 0, /* top = */ 0, /* right = */ 10, /* bottom = */ 10)
+ private const val OUTER_SCREEN_UNIQUE_ID = "111"
private val OUTER_CAMERA_PROTECTION_INFO =
TestableProtectionInfo(
OUTER_CAMERA_LOGICAL_ID,
OUTER_CAMERA_PHYSICAL_ID,
- OUTER_CAMERA_PROTECTION_BOUNDS
+ OUTER_CAMERA_PROTECTION_BOUNDS,
+ OUTER_SCREEN_UNIQUE_ID,
)
private const val INNER_CAMERA_LOGICAL_ID = "2"
@@ -114,11 +122,13 @@
private const val INNER_CAMERA_PROTECTION_PATH = "M 0,0 H 20,20 V 20,20 H 0,20 Z"
private val INNER_CAMERA_PROTECTION_BOUNDS =
Rect(/* left = */ 0, /* top = */ 0, /* right = */ 20, /* bottom = */ 20)
+ private const val INNER_SCREEN_UNIQUE_ID = "222"
private val INNER_CAMERA_PROTECTION_INFO =
TestableProtectionInfo(
INNER_CAMERA_LOGICAL_ID,
INNER_CAMERA_PHYSICAL_ID,
- INNER_CAMERA_PROTECTION_BOUNDS
+ INNER_CAMERA_PROTECTION_BOUNDS,
+ INNER_SCREEN_UNIQUE_ID,
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
new file mode 100644
index 0000000..f769b4e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
@@ -0,0 +1,70 @@
+/*
+ * 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
+
+import com.android.systemui.res.R
+
+class FakeCameraProtectionLoader(private val context: SysuiTestableContext) :
+ CameraProtectionLoader {
+
+ private val realLoader = CameraProtectionLoaderImpl(context)
+
+ override fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> =
+ realLoader.loadCameraProtectionInfoList()
+
+ fun clearProtectionInfoList() {
+ context.orCreateTestableResources.addOverride(R.string.config_protectedCameraId, "")
+ context.orCreateTestableResources.addOverride(R.string.config_protectedInnerCameraId, "")
+ }
+
+ fun addAllProtections() {
+ addOuterCameraProtection()
+ addInnerCameraProtection()
+ }
+
+ fun addOuterCameraProtection(displayUniqueId: String = "111") {
+ context.orCreateTestableResources.addOverride(R.string.config_protectedCameraId, "1")
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedPhysicalCameraId,
+ "11"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_frontBuiltInDisplayCutoutProtection,
+ "M 0,0 H 10,10 V 10,10 H 0,10 Z"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedScreenUniqueId,
+ displayUniqueId
+ )
+ }
+
+ fun addInnerCameraProtection(displayUniqueId: String = "222") {
+ context.orCreateTestableResources.addOverride(R.string.config_protectedInnerCameraId, "2")
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedInnerPhysicalCameraId,
+ "22"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_innerBuiltInDisplayCutoutProtection,
+ "M 0,0 H 20,20 V 20,20 H 0,20 Z"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedInnerScreenUniqueId,
+ displayUniqueId
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 1f1fa72..c20367e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -177,7 +177,7 @@
new FakeFacePropertyRepository();
private List<DecorProvider> mMockCutoutList;
private final CameraProtectionLoader mCameraProtectionLoader =
- new CameraProtectionLoader(mContext);
+ new CameraProtectionLoaderImpl(mContext);
@Before
public void setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
new file mode 100644
index 0000000..f37c4ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
@@ -0,0 +1,127 @@
+/*
+ * 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
+
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayCutout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysUICutoutProviderTest : SysuiTestCase() {
+
+ private val fakeProtectionLoader = FakeCameraProtectionLoader(context)
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_noCutout_returnsNull() {
+ val noCutoutDisplay = createDisplay(cutout = null)
+ val noCutoutDisplayContext = context.createDisplayContext(noCutoutDisplay)
+ val provider = SysUICutoutProvider(noCutoutDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()
+
+ assertThat(sysUICutout).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_returnsCutout() {
+ val cutoutDisplay = createDisplay()
+ val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
+ val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cutout).isEqualTo(cutoutDisplay.cutout)
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_noAssociatedProtection_returnsNoProtection() {
+ val cutoutDisplay = createDisplay()
+ val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
+ val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_outerDisplay_protectionAssociated_returnsProtection() {
+ fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = OUTER_DISPLAY_UNIQUE_ID)
+ val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
+ val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNotNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_outerDisplay_protectionNotAvailable_returnsNullProtection() {
+ fakeProtectionLoader.clearProtectionInfoList()
+ val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
+ val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_displayWithNullId_protectionsWithNoId_returnsNullProtection() {
+ fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = "")
+ val displayContext = context.createDisplayContext(createDisplay(uniqueId = null))
+ val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_displayWithEmptyId_protectionsWithNoId_returnsNullProtection() {
+ fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = "")
+ val displayContext = context.createDisplayContext(createDisplay(uniqueId = ""))
+ val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ companion object {
+ private const val OUTER_DISPLAY_UNIQUE_ID = "outer"
+ private val OUTER_DISPLAY = createDisplay(uniqueId = OUTER_DISPLAY_UNIQUE_ID)
+
+ private fun createDisplay(
+ uniqueId: String? = "uniqueId",
+ cutout: DisplayCutout? = mock<DisplayCutout>()
+ ) =
+ mock<Display> {
+ whenever(this.displayAdjustments).thenReturn(DisplayAdjustments())
+ whenever(this.cutout).thenReturn(cutout)
+ whenever(this.uniqueId).thenReturn(uniqueId)
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 10cd6e5..d110349 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1950,7 +1950,8 @@
mService.mNativeDebuggingApp = null;
}
- if (app.info.isEmbeddedDexUsed()) {
+ if (app.info.isEmbeddedDexUsed()
+ || (app.processInfo != null && app.processInfo.useEmbeddedDex)) {
runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e930627..7ebc311 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -695,14 +695,14 @@
logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(),
userSerial);
dpc.setBrightnessConfiguration(config, /* shouldResetShortTermModel= */ true);
- // change the brightness value according to the selected user.
- final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked();
- if (device != null) {
- dpc.setBrightness(
- mPersistentDataStore.getBrightness(device, userSerial), userSerial);
- }
}
- dpc.onSwitchUser(newUserId);
+ final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked();
+ float newBrightness = device == null ? PowerManager.BRIGHTNESS_INVALID_FLOAT
+ : mPersistentDataStore.getBrightness(device, userSerial);
+ if (Float.isNaN(newBrightness)) {
+ newBrightness = logicalDisplay.getDisplayInfoLocked().brightnessDefault;
+ }
+ dpc.onSwitchUser(newUserId, userSerial, newBrightness);
});
handleSettingsChange();
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 087cacf..1ca3923 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -685,17 +685,27 @@
}
@Override
- public void onSwitchUser(@UserIdInt int newUserId) {
- Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, newUserId);
- mHandler.sendMessage(msg);
+ public void onSwitchUser(@UserIdInt int newUserId, int userSerial, float newBrightness) {
+ Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, newUserId, userSerial, newBrightness);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
- private void handleOnSwitchUser(@UserIdInt int newUserId) {
- handleSettingsChange(true /* userSwitch */);
+ private void handleOnSwitchUser(@UserIdInt int newUserId, int userSerial, float newBrightness) {
+ Slog.i(mTag, "Switching user newUserId=" + newUserId + " userSerial=" + userSerial
+ + " newBrightness=" + newBrightness);
handleBrightnessModeChange();
if (mBrightnessTracker != null) {
mBrightnessTracker.onSwitchUser(newUserId);
}
+ setBrightness(newBrightness, userSerial);
+
+ // Don't treat user switches as user initiated change.
+ mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(newBrightness);
+
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.resetShortTermModel();
+ }
+ sendUpdatePowerState();
}
@Nullable
@@ -2394,20 +2404,11 @@
MetricsLogger.action(log);
}
- private void handleSettingsChange(boolean userSwitch) {
+ private void handleSettingsChange() {
mDisplayBrightnessController
.setPendingScreenBrightness(mDisplayBrightnessController
.getScreenBrightnessSetting());
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(userSwitch);
- if (userSwitch) {
- // Don't treat user switches as user initiated change.
- mDisplayBrightnessController
- .setAndNotifyCurrentScreenBrightness(mDisplayBrightnessController
- .getPendingScreenBrightness());
- if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.resetShortTermModel();
- }
- }
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
sendUpdatePowerState();
}
@@ -2416,11 +2417,8 @@
mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
- mHandler.postAtTime(() -> {
- mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
- == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- updatePowerState();
- }, mClock.uptimeMillis());
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
+ == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
}
@@ -2430,9 +2428,13 @@
}
@Override
- public void setBrightness(float brightnessValue, int userSerial) {
- mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightnessValue),
- userSerial);
+ public void setBrightness(float brightness) {
+ mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness));
+ }
+
+ @Override
+ public void setBrightness(float brightness, int userSerial) {
+ mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness), userSerial);
}
@Override
@@ -2966,7 +2968,7 @@
if (mStopped) {
return;
}
- handleSettingsChange(false /*userSwitch*/);
+ handleSettingsChange();
break;
case MSG_UPDATE_RBC:
@@ -2985,7 +2987,9 @@
break;
case MSG_SWITCH_USER:
- handleOnSwitchUser(msg.arg1);
+ float newBrightness = msg.obj instanceof Float ? (float) msg.obj
+ : PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ handleOnSwitchUser(msg.arg1, msg.arg2, newBrightness);
break;
case MSG_BOOT_COMPLETED:
@@ -3023,7 +3027,10 @@
@Override
public void onChange(boolean selfChange, Uri uri) {
if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
- handleBrightnessModeChange();
+ mHandler.postAtTime(() -> {
+ handleBrightnessModeChange();
+ updatePowerState();
+ }, mClock.uptimeMillis());
} else if (uri.equals(Settings.System.getUriFor(
Settings.System.SCREEN_BRIGHTNESS_FOR_ALS))) {
int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
@@ -3035,7 +3042,7 @@
setUpAutoBrightness(mContext, mHandler);
sendUpdatePowerState();
} else {
- handleSettingsChange(false /* userSwitch */);
+ handleSettingsChange();
}
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 13acb3f..ecf1635 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -30,7 +30,6 @@
* An interface to manage the display's power state and brightness
*/
public interface DisplayPowerControllerInterface {
- int DEFAULT_USER_SERIAL = -1;
/**
* Notified when the display is changed.
*
@@ -100,15 +99,12 @@
* Set the screen brightness of the associated display
* @param brightness The value to which the brightness is to be set
*/
- default void setBrightness(float brightness) {
- setBrightness(brightness, DEFAULT_USER_SERIAL);
- }
+ void setBrightness(float brightness);
/**
* Set the screen brightness of the associated display
* @param brightness The value to which the brightness is to be set
- * @param userSerial The user for which the brightness value is to be set. Use userSerial = -1,
- * if brightness needs to be updated for the current user.
+ * @param userSerial The user for which the brightness value is to be set.
*/
void setBrightness(float brightness, int userSerial);
@@ -188,8 +184,10 @@
/**
* Handles the changes to be done to update the brightness when the user is changed
* @param newUserId The new userId
+ * @param userSerial The serial number of the new user
+ * @param newBrightness The brightness for the new user
*/
- void onSwitchUser(int newUserId);
+ void onSwitchUser(int newUserId, int userSerial, float newBrightness);
/**
* Get the ID of the display associated with this DPC.
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 3bb7986..f6d02db 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -41,7 +41,6 @@
* display. Applies the chosen brightness.
*/
public final class DisplayBrightnessController {
- private static final int DEFAULT_USER_SERIAL = -1;
// The ID of the display tied to this DisplayBrightnessController
private final int mDisplayId;
@@ -302,16 +301,8 @@
* Notifies the brightnessSetting to persist the supplied brightness value.
*/
public void setBrightness(float brightnessValue) {
- setBrightness(brightnessValue, DEFAULT_USER_SERIAL);
- }
-
- /**
- * Notifies the brightnessSetting to persist the supplied brightness value for a user.
- */
- public void setBrightness(float brightnessValue, int userSerial) {
// Update the setting, which will eventually call back into DPC to have us actually
// update the display with the new value.
- mBrightnessSetting.setUserSerial(userSerial);
mBrightnessSetting.setBrightness(brightnessValue);
if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
float nits = convertToNits(brightnessValue);
@@ -322,6 +313,14 @@
}
/**
+ * Notifies the brightnessSetting to persist the supplied brightness value for a user.
+ */
+ public void setBrightness(float brightnessValue, int userSerial) {
+ mBrightnessSetting.setUserSerial(userSerial);
+ setBrightness(brightnessValue);
+ }
+
+ /**
* Sets the current screen brightness, and notifies the BrightnessSetting about the change.
*/
public void updateScreenBrightnessSetting(float brightnessValue) {
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 3c23b5c..3e6e09d 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -203,14 +203,11 @@
* Sets the pending auto-brightness adjustments in the system settings. Executed
* when there is a change in the brightness system setting, or when there is a user switch.
*/
- public void updatePendingAutoBrightnessAdjustments(boolean userSwitch) {
+ public void updatePendingAutoBrightnessAdjustments() {
final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
mPendingAutoBrightnessAdjustment = Float.isNaN(adj) ? Float.NaN
: BrightnessUtils.clampBrightnessAdjustment(adj);
- if (userSwitch) {
- processPendingAutoBrightnessAdjustments();
- }
}
/**
diff --git a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
index 8278600..202c894 100644
--- a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
@@ -98,18 +98,21 @@
localDevice().getService().enableAbsoluteVolumeBehavior(audioStatus);
mState = STATE_MONITOR_AUDIO_STATUS;
} else if (mState == STATE_MONITOR_AUDIO_STATUS) {
- // On TV panels, we notify AudioService even if neither volume nor mute state changed.
- // This ensures that the user sees volume UI if they tried to adjust the AVR's volume,
- // even if the new volume level is the same as the previous one.
- boolean notifyAvbVolumeToShowUi = localDevice().getService().isTvDevice()
- && audioStatus.equals(mLastAudioStatus);
-
- if (audioStatus.getVolume() != mLastAudioStatus.getVolume()
- || notifyAvbVolumeToShowUi) {
+ // Update volume in AudioService if it has changed since the last <Report Audio Status>
+ boolean updateVolume = audioStatus.getVolume() != mLastAudioStatus.getVolume();
+ if (updateVolume) {
localDevice().getService().notifyAvbVolumeChange(audioStatus.getVolume());
}
- if (audioStatus.getMute() != mLastAudioStatus.getMute()) {
+ // Update mute in AudioService if any of the following conditions are met:
+ // - The mute status changed
+ // - The volume changed - we need to make sure mute is set correctly afterwards, since
+ // setting volume can affect mute status as well as a side effect.
+ // - We're a TV panel - we want to trigger volume UI on TV panels, so that the user
+ // always gets visual feedback when they attempt to adjust the AVR's volume/mute.
+ if ((audioStatus.getMute() != mLastAudioStatus.getMute())
+ || updateVolume
+ || localDevice().getService().isTvDevice()) {
localDevice().getService().notifyAvbMuteChange(audioStatus.getMute());
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index f852b81..097daf2 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -97,7 +97,7 @@
private static final float DEFAULT_VOLUME = 1.0f;
// TODO (b/291899544): remove for release
private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED = 1;
- private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 0;
+ private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 1;
private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1;
private static final int DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = 0;
@@ -1405,6 +1405,7 @@
long timestampMillis) {
super.setLastNotificationUpdateTimeMs(record, timestampMillis);
mLastNotificationTimestamp = timestampMillis;
+ mAppStrategy.setLastNotificationUpdateTimeMs(record, timestampMillis);
}
long getLastNotificationUpdateTimeMs(final NotificationRecord record) {
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index f1dc284..b9aa28e 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -49,6 +49,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
+import android.content.pm.UserProperties;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -268,7 +269,8 @@
private boolean canRequestInteractAcrossProfilesUnchecked(String packageName) {
final int callingUserId = mInjector.getCallingUserId();
final int[] enabledProfileIds =
- mInjector.getUserManager().getEnabledProfileIds(callingUserId);
+ mInjector.getUserManager().getProfileIdsExcludingHidden(
+ callingUserId, /* enabled= */ true);
if (enabledProfileIds.length < 2) {
return false;
}
@@ -350,7 +352,8 @@
String packageName, @UserIdInt int userId) {
return mInjector.withCleanCallingIdentity(() -> {
final int[] enabledProfileIds =
- mInjector.getUserManager().getEnabledProfileIds(userId);
+ mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+ true);
List<UserHandle> targetProfiles = new ArrayList<>();
for (final int profileId : enabledProfileIds) {
@@ -466,7 +469,8 @@
return;
}
final int[] profileIds =
- mInjector.getUserManager().getProfileIds(userId, /* enabledOnly= */ false);
+ mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+ false);
for (int profileId : profileIds) {
if (!isPackageInstalled(packageName, profileId)) {
continue;
@@ -632,7 +636,8 @@
private boolean canUserAttemptToConfigureInteractAcrossProfiles(
String packageName, @UserIdInt int userId) {
final int[] profileIds =
- mInjector.getUserManager().getProfileIds(userId, /* enabledOnly= */ false);
+ mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+ false);
if (profileIds.length < 2) {
return false;
}
@@ -676,7 +681,8 @@
private boolean hasOtherProfileWithPackageInstalled(String packageName, @UserIdInt int userId) {
return mInjector.withCleanCallingIdentity(() -> {
final int[] profileIds =
- mInjector.getUserManager().getProfileIds(userId, /* enabledOnly= */ false);
+ mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+ false);
for (int profileId : profileIds) {
if (profileId != userId && isPackageInstalled(packageName, profileId)) {
return true;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c0596bb..796edde 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1385,7 +1385,7 @@
@Override
public int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) {
- return getProfileIds(userId, null, enabledOnly);
+ return getProfileIds(userId, null, enabledOnly, /* excludeHidden */ false);
}
// TODO(b/142482943): Probably @Override and make this accessible in UserManager.
@@ -1397,14 +1397,14 @@
* If enabledOnly, only returns users that are not {@link UserInfo#FLAG_DISABLED}.
*/
public int[] getProfileIds(@UserIdInt int userId, @Nullable String userType,
- boolean enabledOnly) {
+ boolean enabledOnly, boolean excludeHidden) {
if (userId != UserHandle.getCallingUserId()) {
checkQueryOrCreateUsersPermission("getting profiles related to user " + userId);
}
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mUsersLock) {
- return getProfileIdsLU(userId, userType, enabledOnly).toArray();
+ return getProfileIdsLU(userId, userType, enabledOnly, excludeHidden).toArray();
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -1415,7 +1415,8 @@
@GuardedBy("mUsersLock")
private List<UserInfo> getProfilesLU(@UserIdInt int userId, @Nullable String userType,
boolean enabledOnly, boolean fullInfo) {
- IntArray profileIds = getProfileIdsLU(userId, userType, enabledOnly);
+ IntArray profileIds = getProfileIdsLU(userId, userType, enabledOnly, /* excludeHidden */
+ false);
ArrayList<UserInfo> users = new ArrayList<>(profileIds.size());
for (int i = 0; i < profileIds.size(); i++) {
int profileId = profileIds.get(i);
@@ -1440,7 +1441,7 @@
*/
@GuardedBy("mUsersLock")
private IntArray getProfileIdsLU(@UserIdInt int userId, @Nullable String userType,
- boolean enabledOnly) {
+ boolean enabledOnly, boolean excludeHidden) {
UserInfo user = getUserInfoLU(userId);
IntArray result = new IntArray(mUsers.size());
if (user == null) {
@@ -1465,11 +1466,36 @@
if (userType != null && !userType.equals(profile.userType)) {
continue;
}
+ if (excludeHidden && isProfileHidden(userId)) {
+ continue;
+ }
result.add(profile.id);
}
return result;
}
+ /*
+ * Returns all the users that are in the same profile group as userId excluding those with
+ * {@link UserProperties#getProfileApiVisibility()} set to hidden. The returned list includes
+ * the user itself.
+ */
+ // TODO (b/323011770): Add a permission check to make an exception for App stores if we end
+ // up supporting Private Space on COPE devices
+ @Override
+ public int[] getProfileIdsExcludingHidden(@UserIdInt int userId, boolean enabledOnly) {
+ return getProfileIds(userId, null, enabledOnly, /* excludeHidden */ true);
+ }
+
+ private boolean isProfileHidden(int userId) {
+ UserProperties userProperties = getUserPropertiesCopy(userId);
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enableHidingProfiles()) {
+ return userProperties.getProfileApiVisibility()
+ == UserProperties.PROFILE_API_VISIBILITY_HIDDEN;
+ }
+ return false;
+ }
+
@Override
public int getCredentialOwnerProfile(@UserIdInt int userId) {
checkManageUsersPermission("get the credential owner");
@@ -3630,7 +3656,8 @@
return 0;
}
- final int userTypeCount = getProfileIds(userId, userType, false).length;
+ final int userTypeCount = getProfileIds(userId, userType, false, /* excludeHidden */
+ false).length;
final int profilesRemovedCount = userTypeCount > 0 && allowedToRemoveOne ? 1 : 0;
final int usersCountAfterRemoving = getAliveUsersExcludingGuestsCountLU()
- profilesRemovedCount;
@@ -5931,7 +5958,8 @@
}
userData = mUsers.get(userId);
isProfile = userData.info.isProfile();
- profileIds = isProfile ? null : getProfileIdsLU(userId, null, false);
+ profileIds = isProfile ? null : getProfileIdsLU(userId, null, false, /* excludeHidden */
+ false);
}
if (!isProfile) {
@@ -7458,7 +7486,8 @@
@Override
public @NonNull int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) {
synchronized (mUsersLock) {
- return getProfileIdsLU(userId, null /* userType */, enabledOnly).toArray();
+ return getProfileIdsLU(userId, null /* userType */, enabledOnly, /* excludeHidden */
+ false).toArray();
}
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 6ed2d31..a9e5a54 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -831,7 +831,7 @@
retProcs.put(proc.getName(),
new ProcessInfo(proc.getName(), new ArraySet<>(proc.getDeniedPermissions()),
proc.getGwpAsanMode(), proc.getMemtagMode(),
- proc.getNativeHeapZeroInitialized()));
+ proc.getNativeHeapZeroInitialized(), proc.isUseEmbeddedDex()));
}
return retProcs;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b1d04c9..2c2ebe0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -122,6 +122,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED;
+import static android.view.WindowManager.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
@@ -386,6 +387,7 @@
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.WindowManagerService.H;
import com.android.server.wm.utils.InsetUtils;
+import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
@@ -986,6 +988,9 @@
// Whether the ActivityEmbedding is enabled on the app.
private final boolean mAppActivityEmbeddingSplitsEnabled;
+ // Whether the Activity allows state sharing in untrusted embedding
+ private final boolean mAllowUntrustedEmbeddingStateSharing;
+
// Records whether client has overridden the WindowAnimation_(Open/Close)(Enter/Exit)Animation.
private CustomAppTransition mCustomOpenTransition;
private CustomAppTransition mCustomCloseTransition;
@@ -2223,6 +2228,7 @@
// No such property name.
}
mAppActivityEmbeddingSplitsEnabled = appActivityEmbeddingEnabled;
+ mAllowUntrustedEmbeddingStateSharing = getAllowUntrustedEmbeddingStateSharingProperty();
mOptInOnBackInvoked = WindowOnBackInvokedDispatcher
.isOnBackInvokedCallbackEnabled(info, info.applicationInfo,
@@ -3078,6 +3084,32 @@
return parent != null && parent.isEmbedded();
}
+ /**
+ * Returns {@code true} if the system is allowed to share this activity's state with the host
+ * app when this activity is embedded in untrusted mode.
+ */
+ boolean isUntrustedEmbeddingStateSharingAllowed() {
+ if (!Flags.untrustedEmbeddingStateSharing()) {
+ return false;
+ }
+ return mAllowUntrustedEmbeddingStateSharing;
+ }
+
+ private boolean getAllowUntrustedEmbeddingStateSharingProperty() {
+ if (!Flags.untrustedEmbeddingStateSharing()) {
+ return false;
+ }
+ try {
+ return mAtmService.mContext.getPackageManager()
+ .getProperty(PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING,
+ mActivityComponent)
+ .getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ // No such property name.
+ return false;
+ }
+ }
+
@Override
@Nullable
TaskDisplayArea getDisplayArea() {
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index c7df83a..e84026c 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -64,6 +64,10 @@
final IApplicationThread client = transaction.getClient();
try {
transaction.schedule();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to deliver transaction for " + client
+ + "\ntransaction=" + transaction);
+ throw e;
} finally {
if (!(client instanceof Binder)) {
// If client is not an instance of Binder - it's a remote call and at this point it
@@ -157,7 +161,7 @@
try {
scheduleTransaction(transaction);
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to deliver transaction for " + transaction.getClient());
+ Slog.e(TAG, "Failed to deliver pending transaction", e);
}
}
mPendingTransactions.clear();
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index d9dda4a..cd96806 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -178,6 +178,7 @@
mWindowContainer.cancelAnimation();
mWindowContainer.getInsetsSourceProviders().remove(mSource.getId());
mSeamlessRotating = false;
+ mHasPendingPosition = false;
}
ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s",
windowContainer, WindowInsets.Type.toString(mSource.getType()));
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 81fe453..8d054db 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -376,10 +376,15 @@
+ " is not in a task belong to the organizer app.");
return null;
}
- if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED
- || !task.isAllowedToEmbedActivityInTrustedMode(activity, mOrganizerUid)) {
+ if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) {
Slog.d(TAG, "Reparent activity=" + activity.token
- + " is not allowed to be embedded in trusted mode.");
+ + " is not allowed to be embedded.");
+ return null;
+ }
+ if (!task.isAllowedToEmbedActivityInTrustedMode(activity, mOrganizerUid)
+ && !activity.isUntrustedEmbeddingStateSharingAllowed()) {
+ Slog.d(TAG, "Reparent activity=" + activity.token
+ + " is not allowed to be shared with untrusted host.");
return null;
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 44cd23d..09c4f7c 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -499,6 +499,10 @@
}
void applyEnterAnimationLocked() {
+ if (mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow()) {
+ // It's unnecessary to play enter animation below starting window.
+ return;
+ }
final int transit;
if (mEnterAnimationPending) {
mEnterAnimationPending = false;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index 129efc6..005cad1 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -46,6 +46,7 @@
import android.permission.PermissionCheckerManager;
import android.permission.PermissionManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.SparseArray;
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
@@ -55,6 +56,7 @@
import com.android.server.wm.ActivityTaskManagerInternal;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -109,6 +111,8 @@
private IApplicationThread mIApplicationThread;
private SparseArray<Boolean> mUserEnabled = new SparseArray<>();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void initCrossProfileAppsServiceImpl() {
@@ -123,8 +127,9 @@
mUserEnabled.put(PRIMARY_USER, true);
mUserEnabled.put(PROFILE_OF_PRIMARY_USER, true);
mUserEnabled.put(SECONDARY_USER, true);
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_ENABLE_HIDING_PROFILES);
- when(mUserManager.getEnabledProfileIds(anyInt())).thenAnswer(
+ when(mUserManager.getProfileIdsExcludingHidden(anyInt(), eq(true))).thenAnswer(
invocation -> {
List<Integer> users = new ArrayList<>();
final int targetUser = invocation.getArgument(0);
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
index 93bdeae..d538f25 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
@@ -40,6 +40,7 @@
ParsedProcess::getGwpAsanMode,
ParsedProcess::getMemtagMode,
ParsedProcess::getNativeHeapZeroInitialized,
+ ParsedProcess::isUseEmbeddedDex,
)
override fun extraParams() = listOf(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 42bcb33..b142334 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -72,6 +72,7 @@
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -102,7 +103,9 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.os.UserManager;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
import android.util.SparseArray;
import android.view.ContentRecordingSession;
import android.view.Display;
@@ -210,6 +213,10 @@
private int mPreferredHdrOutputType;
+ private Handler mPowerHandler;
+
+ private UserManager mUserManager;
+
private final DisplayManagerService.Injector mShortMockedInjector =
new DisplayManagerService.Injector() {
@Override
@@ -370,11 +377,14 @@
mContext = spy(new ContextWrapper(
ApplicationProvider.getApplicationContext().createDisplayContext(display)));
mResources = Mockito.spy(mContext.getResources());
+ mPowerHandler = new Handler(Looper.getMainLooper());
manageDisplaysPermission(/* granted= */ false);
when(mContext.getResources()).thenReturn(mResources);
+ mUserManager = Mockito.spy(mContext.getSystemService(UserManager.class));
VirtualDeviceManager vdm = new VirtualDeviceManager(mIVirtualDeviceManager, mContext);
when(mContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
+ when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
// Disable binder caches in this process.
PropertyInvalidatedCache.disableForTestMode();
setUpDisplay();
@@ -2789,6 +2799,85 @@
assertThat(display.getDisplayOffloadSessionLocked()).isNull();
}
+ @Test
+ public void testOnUserSwitching_UpdatesBrightness() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ float brightness1 = 0.3f;
+ float brightness2 = 0.45f;
+
+ int userId1 = 123;
+ int userId2 = 456;
+ UserInfo userInfo1 = new UserInfo();
+ userInfo1.id = userId1;
+ UserInfo userInfo2 = new UserInfo();
+ userInfo2.id = userId2;
+ when(mUserManager.getUserSerialNumber(userId1)).thenReturn(12345);
+ when(mUserManager.getUserSerialNumber(userId2)).thenReturn(45678);
+ final SystemService.TargetUser from = new SystemService.TargetUser(userInfo1);
+ final SystemService.TargetUser to = new SystemService.TargetUser(userInfo2);
+
+ // The same brightness will be restored for a user only if auto-brightness is off,
+ // otherwise the current lux will be used to determine the brightness.
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+ displayManager.onUserSwitching(to, from);
+ waitForIdleHandler(mPowerHandler);
+ displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, brightness1);
+ displayManager.onUserSwitching(from, to);
+ waitForIdleHandler(mPowerHandler);
+ displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, brightness2);
+
+ displayManager.onUserSwitching(to, from);
+ waitForIdleHandler(mPowerHandler);
+ assertEquals(brightness1,
+ displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+ FLOAT_TOLERANCE);
+
+ displayManager.onUserSwitching(from, to);
+ waitForIdleHandler(mPowerHandler);
+ assertEquals(brightness2,
+ displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+ FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testOnUserSwitching_brightnessForNewUserIsDefault() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ int userId1 = 123;
+ int userId2 = 456;
+ UserInfo userInfo1 = new UserInfo();
+ userInfo1.id = userId1;
+ UserInfo userInfo2 = new UserInfo();
+ userInfo2.id = userId2;
+ when(mUserManager.getUserSerialNumber(userId1)).thenReturn(12345);
+ when(mUserManager.getUserSerialNumber(userId2)).thenReturn(45678);
+ final SystemService.TargetUser from = new SystemService.TargetUser(userInfo1);
+ final SystemService.TargetUser to = new SystemService.TargetUser(userInfo2);
+
+ displayManager.onUserSwitching(from, to);
+ waitForIdleHandler(mPowerHandler);
+ assertEquals(displayManagerBinderService.getDisplayInfo(Display.DEFAULT_DISPLAY)
+ .brightnessDefault,
+ displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+ FLOAT_TOLERANCE);
+ }
+
private void initDisplayPowerController(DisplayManagerInternal localService) {
localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
@Override
@@ -2820,7 +2909,7 @@
public void releaseSuspendBlocker(String id) {
}
- }, new Handler(Looper.getMainLooper()), mSensorManager);
+ }, mPowerHandler, mSensorManager);
}
private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 88a9758..57b8632 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -227,18 +227,14 @@
advanceTime(1);
// two times, one for unfinished business and one for proximity
- verify(mHolder.wakelockController, times(2)).acquireWakelock(
+ verify(mHolder.wakelockController).acquireWakelock(
WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
verify(mHolder.wakelockController).acquireWakelock(
WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
mHolder.dpc.stop();
advanceTime(1);
- // two times, one for unfinished business and one for proximity
- verify(mHolder.wakelockController, times(2)).acquireWakelock(
- WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
- verify(mHolder.wakelockController).acquireWakelock(
- WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+ verify(mHolder.wakelockController).releaseAll();
}
@Test
@@ -515,9 +511,8 @@
verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- // One triggered by handleBrightnessModeChange, another triggered by setBrightnessToFollow
- verify(followerDpc.hbmController, times(2)).onAmbientLuxChange(ambientLux);
- verify(followerDpc.animator, times(2)).animateTo(eq(followerBrightness), anyFloat(),
+ verify(followerDpc.hbmController).onAmbientLuxChange(ambientLux);
+ verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
@@ -811,7 +806,7 @@
DisplayPowerRequest dpr = new DisplayPowerRequest();
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
+ verify(mHolder.displayPowerState).setScreenState(anyInt());
mHolder = createDisplayPowerController(42, UNIQUE_ID);
@@ -1024,15 +1019,14 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- // One triggered by the test, the other by handleBrightnessModeChange
- verify(mHolder.automaticBrightnessController, times(2)).configure(
+ verify(mHolder.automaticBrightnessController).configure(
AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
/* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
/* userChangedBrightness= */ false, /* adjustment= */ 0,
/* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
/* shouldResetShortTermModel= */ false
);
- verify(mHolder.hbmController, times(2))
+ verify(mHolder.hbmController)
.setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
}
@@ -1098,8 +1092,7 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- // One triggered by the test, the other by handleBrightnessModeChange
- verify(mHolder.automaticBrightnessController, times(2)).configure(
+ verify(mHolder.automaticBrightnessController).configure(
AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
/* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
/* userChangedBrightness= */ false, /* adjustment= */ 0,
@@ -1136,8 +1129,7 @@
DisplayPowerRequest dpr = new DisplayPowerRequest();
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
- verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat(),
+ verify(mHolder.animator).animateTo(eq(newBrightness), anyFloat(), anyFloat(),
eq(false));
}
@@ -1210,8 +1202,9 @@
when(mHolder.hbmController.getCurrentBrightnessMax()).thenReturn(clampedBrightness);
mHolder.dpc.setBrightness(PowerManager.BRIGHTNESS_MAX);
+ mHolder.dpc.setBrightness(0.8f, /* userSerial= */ 123);
- verify(mHolder.brightnessSetting).setBrightness(clampedBrightness);
+ verify(mHolder.brightnessSetting, times(2)).setBrightness(clampedBrightness);
}
@Test
@@ -1564,9 +1557,7 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- // One triggered by handleBrightnessModeChange, another triggered by
- // setBrightnessFromOffload
- verify(mHolder.animator, times(2)).animateTo(eq(brightness), anyFloat(),
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
}
@@ -1580,9 +1571,7 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- // One triggered by handleBrightnessModeChange, another triggered by requestPowerState
- verify(mHolder.automaticBrightnessController, times(2))
- .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+ verify(mHolder.automaticBrightnessController).switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
// Back to default mode
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
@@ -1620,6 +1609,62 @@
.switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
}
+ @Test
+ public void testOnSwitchUserUpdatesBrightness() {
+ int userSerial = 12345;
+ float brightness = 0.65f;
+
+ mHolder.dpc.onSwitchUser(/* newUserId= */ 15, userSerial, brightness);
+ advanceTime(1);
+
+ verify(mHolder.brightnessSetting).setUserSerial(userSerial);
+ verify(mHolder.brightnessSetting).setBrightness(brightness);
+ }
+
+ @Test
+ public void testOnSwitchUserDoesNotAddUserDataPoint() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ int userSerial = 12345;
+ float brightness = 0.65f;
+ when(mHolder.automaticBrightnessController.hasValidAmbientLux()).thenReturn(true);
+ when(mHolder.automaticBrightnessController.convertToAdjustedNits(brightness))
+ .thenReturn(500f);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.onSwitchUser(/* newUserId= */ 15, userSerial, brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+
+ verify(mHolder.automaticBrightnessController, never()).configure(
+ /* state= */ anyInt(),
+ /* configuration= */ any(),
+ eq(brightness),
+ /* userChangedBrightness= */ eq(true),
+ /* adjustment= */ anyFloat(),
+ /* userChangedAutoBrightnessAdjustment= */ anyBoolean(),
+ /* displayPolicy= */ anyInt(),
+ /* shouldResetShortTermModel= */ anyBoolean());
+ verify(mBrightnessTrackerMock, never()).notifyBrightnessChanged(
+ /* brightness= */ anyFloat(),
+ /* userInitiated= */ eq(true),
+ /* powerBrightnessFactor= */ anyFloat(),
+ /* wasShortTermModelActive= */ anyBoolean(),
+ /* isDefaultBrightnessConfig= */ anyBoolean(),
+ /* uniqueDisplayId= */ any(),
+ /* luxValues */ any(),
+ /* luxTimestamps= */ any());
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 2d0c3fd..289d54b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -19,7 +19,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -252,7 +251,6 @@
0.0f);
verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
verify(mBrightnessSetting).setBrightness(brightnessValue);
- verify(mBrightnessSetting).setUserSerial(anyInt());
// Does nothing if the value is invalid
mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index 78ec2ff3..5408e11 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -104,7 +104,7 @@
int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
float lastUserSetBrightness = 0.2f;
boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -127,7 +127,7 @@
int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
float lastUserSetBrightness = 0.2f;
boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -150,7 +150,7 @@
int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
float lastUserSetBrightness = 0.2f;
boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -173,7 +173,7 @@
int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
float lastUserSetBrightness = 0.2f;
boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -196,7 +196,7 @@
int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
float lastUserSetBrightness = 0.2f;
boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -221,7 +221,7 @@
boolean userSetBrightnessChanged = true;
Settings.System.putFloat(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -247,7 +247,7 @@
float pendingBrightnessAdjustment = 0.1f;
Settings.System.putFloat(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -411,7 +411,7 @@
private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
Settings.System.putFloat(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
}
private void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
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 eaf0ffd..3355a6c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -29,6 +29,8 @@
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
+import static com.android.server.job.Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS;
+import static com.android.server.job.Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -60,12 +62,15 @@
import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.NetworkPolicyManager;
import android.os.BatteryManagerInternal;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.platform.test.flag.junit.SetFlagsRule;
import com.android.server.AppStateTracker;
import com.android.server.AppStateTrackerImpl;
@@ -82,6 +87,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoSession;
@@ -105,6 +111,9 @@
@Mock
private PackageManagerInternal mPackageManagerInternal;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private class TestJobSchedulerService extends JobSchedulerService {
TestJobSchedulerService(Context context) {
super(context);
@@ -1711,6 +1720,262 @@
/** Tests that rare job batching works as expected. */
@Test
+ public void testConnectivityJobBatching() {
+ mSetFlagsRule.enableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
+ spyOn(mService);
+ doReturn(false).when(mService).evaluateControllerStatesLocked(any());
+ doNothing().when(mService).noteJobsPending(any());
+ doReturn(true).when(mService).isReadyToBeExecutedLocked(any(), anyBoolean());
+ ConnectivityController connectivityController = mService.getConnectivityController();
+ spyOn(connectivityController);
+ advanceElapsedClock(24 * HOUR_IN_MILLIS);
+
+ JobSchedulerService.MaybeReadyJobQueueFunctor maybeQueueFunctor =
+ mService.new MaybeReadyJobQueueFunctor();
+ mService.mConstants.CONN_TRANSPORT_BATCH_THRESHOLD.clear();
+ mService.mConstants.CONN_TRANSPORT_BATCH_THRESHOLD
+ .put(NetworkCapabilities.TRANSPORT_CELLULAR, 5);
+ mService.mConstants.CONN_TRANSPORT_BATCH_THRESHOLD
+ .put(NetworkCapabilities.TRANSPORT_WIFI, 2);
+ mService.mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
+
+ final Network network = mock(Network.class);
+
+ // Not enough connectivity jobs to run.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ NetworkCapabilities capabilities = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
+ doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+ doReturn(false).when(connectivityController).isNetworkInStateForJobRunLocked(any());
+ for (int i = 0; i < 4; ++i) {
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+
+ maybeQueueFunctor.accept(job);
+ assertNull(maybeQueueFunctor.mBatches.get(null));
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(0, mService.getPendingJobQueue().size());
+
+ // Not enough connectivity jobs to run, but the network is already active
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+ doReturn(true).when(connectivityController).isNetworkInStateForJobRunLocked(any());
+ for (int i = 0; i < 4; ++i) {
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+
+ maybeQueueFunctor.accept(job);
+ assertNull(maybeQueueFunctor.mBatches.get(null));
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(0, job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(4, mService.getPendingJobQueue().size());
+
+ // Enough connectivity jobs to run.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ capabilities = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+ doReturn(false).when(connectivityController).isNetworkInStateForJobRunLocked(any());
+ for (int i = 0; i < 3; ++i) {
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(3, mService.getPendingJobQueue().size());
+
+ // Not enough connectivity jobs to run, but a non-batched job saves the day.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ JobStatus runningJob = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ runningJob.network = network;
+ doReturn(true).when(mService).isCurrentlyRunningLocked(runningJob);
+ capabilities = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
+ doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+ for (int i = 0; i < 3; ++i) {
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.accept(runningJob);
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(3, mService.getPendingJobQueue().size());
+
+ // Not enough connectivity jobs to run, but an old connectivity job saves the day.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ JobStatus oldConnJob = createJobStatus("testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ oldConnJob.network = network;
+ final long oldBatchTime = sElapsedRealtimeClock.millis()
+ - 2 * mService.mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS;
+ oldConnJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
+ for (int i = 0; i < 2; ++i) {
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.accept(oldConnJob);
+ assertEquals(oldBatchTime, oldConnJob.getFirstForceBatchedTimeElapsed());
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(3, mService.getPendingJobQueue().size());
+
+ // Transport type doesn't have a set threshold. One job should be the default threshold.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ capabilities = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+ .build();
+ doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+ maybeQueueFunctor.accept(job);
+ assertEquals(1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(1, mService.getPendingJobQueue().size());
+ }
+
+ /** Tests that active job batching works as expected. */
+ @Test
+ public void testActiveJobBatching_activeBatchingEnabled() {
+ mSetFlagsRule.enableFlags(FLAG_BATCH_ACTIVE_BUCKET_JOBS);
+
+ spyOn(mService);
+ doReturn(false).when(mService).evaluateControllerStatesLocked(any());
+ doNothing().when(mService).noteJobsPending(any());
+ doReturn(true).when(mService).isReadyToBeExecutedLocked(any(), anyBoolean());
+ advanceElapsedClock(24 * HOUR_IN_MILLIS);
+
+ JobSchedulerService.MaybeReadyJobQueueFunctor maybeQueueFunctor =
+ mService.new MaybeReadyJobQueueFunctor();
+ mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT = 5;
+ mService.mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
+
+ // Not enough ACTIVE jobs to run.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+ job.setStandbyBucket(ACTIVE_INDEX);
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(0, mService.getPendingJobQueue().size());
+
+ // Enough ACTIVE jobs to run.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT; ++i) {
+ JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+ job.setStandbyBucket(ACTIVE_INDEX);
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(5, mService.getPendingJobQueue().size());
+
+ // Not enough ACTIVE jobs to run, but a non-batched job saves the day.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ JobStatus expeditedJob = createJobStatus("testActiveJobBatching",
+ createJobInfo().setExpedited(true));
+ spyOn(expeditedJob);
+ when(expeditedJob.shouldTreatAsExpeditedJob()).thenReturn(true);
+ expeditedJob.setStandbyBucket(RARE_INDEX);
+ for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+ job.setStandbyBucket(ACTIVE_INDEX);
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.accept(expeditedJob);
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(3, mService.getPendingJobQueue().size());
+
+ // Not enough ACTIVE jobs to run, but an old ACTIVE job saves the day.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ JobStatus oldActiveJob = createJobStatus("testActiveJobBatching", createJobInfo());
+ oldActiveJob.setStandbyBucket(ACTIVE_INDEX);
+ final long oldBatchTime = sElapsedRealtimeClock.millis()
+ - 2 * mService.mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS;
+ oldActiveJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
+ for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+ job.setStandbyBucket(ACTIVE_INDEX);
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.accept(oldActiveJob);
+ assertEquals(oldBatchTime, oldActiveJob.getFirstForceBatchedTimeElapsed());
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(3, mService.getPendingJobQueue().size());
+ }
+
+ /** Tests that rare job batching works as expected. */
+ @Test
public void testRareJobBatching() {
spyOn(mService);
doReturn(false).when(mService).evaluateControllerStatesLocked(any());
@@ -1723,17 +1988,15 @@
mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
- JobStatus job = createJobStatus(
- "testRareJobBatching",
- createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
- job.setStandbyBucket(RARE_INDEX);
-
// Not enough RARE jobs to run.
mService.getPendingJobQueue().clear();
maybeQueueFunctor.reset();
for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+ job.setStandbyBucket(RARE_INDEX);
+
maybeQueueFunctor.accept(job);
- assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
}
@@ -1744,8 +2007,11 @@
mService.getPendingJobQueue().clear();
maybeQueueFunctor.reset();
for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT; ++i) {
+ JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+ job.setStandbyBucket(RARE_INDEX);
+
maybeQueueFunctor.accept(job);
- assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
}
@@ -1753,15 +2019,17 @@
assertEquals(5, mService.getPendingJobQueue().size());
// Not enough RARE jobs to run, but a non-batched job saves the day.
+ mSetFlagsRule.disableFlags(FLAG_BATCH_ACTIVE_BUCKET_JOBS);
mService.getPendingJobQueue().clear();
maybeQueueFunctor.reset();
- JobStatus activeJob = createJobStatus(
- "testRareJobBatching",
- createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ JobStatus activeJob = createJobStatus("testRareJobBatching", createJobInfo());
activeJob.setStandbyBucket(ACTIVE_INDEX);
for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+ job.setStandbyBucket(RARE_INDEX);
+
maybeQueueFunctor.accept(job);
- assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
}
@@ -1778,8 +2046,11 @@
- 2 * mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
oldRareJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+ job.setStandbyBucket(RARE_INDEX);
+
maybeQueueFunctor.accept(job);
- assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 4958f1c..f27d0c2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -36,6 +36,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK;
import static com.android.server.job.Flags.FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
@@ -69,6 +70,7 @@
import android.content.pm.PackageManagerInternal;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.ConnectivityManager.OnNetworkActiveListener;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkPolicyManager;
@@ -102,6 +104,7 @@
import org.mockito.stubbing.Answer;
import java.time.Clock;
+import java.time.Duration;
import java.time.ZoneOffset;
import java.util.Set;
@@ -1650,6 +1653,141 @@
assertEquals((81920 + 4096) * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
}
+ @Test
+ public void testIsNetworkInStateForJobRunLocked_JobStatus() {
+ mSetFlagsRule.enableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
+
+ // Null network
+ final JobStatus expeditedJob =
+ spy(createJobStatus(createJob().setExpedited(true), UID_RED));
+ doReturn(true).when(expeditedJob).shouldTreatAsExpeditedJob();
+ final JobStatus highProcJob = spy(createJobStatus(createJob(), UID_BLUE));
+ doReturn(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE)
+ .when(mService).getUidProcState(eq(UID_BLUE));
+ doReturn(ActivityManager.PROCESS_STATE_CACHED_EMPTY)
+ .when(mService).getUidProcState(eq(UID_RED));
+ final JobStatus regJob = createJobStatus(createJob(), UID_RED);
+ final JobStatus uiJob = spy(createJobStatus(
+ createJob().setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY),
+ UID_RED));
+ doReturn(true).when(uiJob).shouldTreatAsUserInitiatedJob();
+ assertFalse(controller.isNetworkInStateForJobRunLocked(expeditedJob));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(highProcJob));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(regJob));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(uiJob));
+
+ // Privileged jobs are exempted
+ expeditedJob.network = mock(Network.class);
+ highProcJob.network = mock(Network.class);
+ regJob.network = mock(Network.class);
+ uiJob.network = mock(Network.class);
+ assertTrue(controller.isNetworkInStateForJobRunLocked(expeditedJob));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(highProcJob));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(regJob));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(uiJob));
+
+ mSetFlagsRule.disableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+ assertTrue(controller.isNetworkInStateForJobRunLocked(expeditedJob));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(highProcJob));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(regJob));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(uiJob));
+ }
+
+ @Test
+ public void testIsNetworkInStateForJobRunLocked_Network() {
+ mSetFlagsRule.disableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
+ final ArgumentCaptor<NetworkCallback> allNetworkCallbackCaptor =
+ ArgumentCaptor.forClass(NetworkCallback.class);
+ doNothing().when(mConnManager)
+ .registerNetworkCallback(any(), allNetworkCallbackCaptor.capture());
+ final ArgumentCaptor<OnNetworkActiveListener> onNetworkActiveListenerCaptor =
+ ArgumentCaptor.forClass(OnNetworkActiveListener.class);
+ doNothing().when(mConnManager).addDefaultNetworkActiveListener(
+ onNetworkActiveListenerCaptor.capture());
+ final ArgumentCaptor<NetworkCallback> systemDefaultNetworkCallbackCaptor =
+ ArgumentCaptor.forClass(NetworkCallback.class);
+ doNothing().when(mConnManager).registerSystemDefaultNetworkCallback(
+ systemDefaultNetworkCallbackCaptor.capture(), any());
+
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
+
+ assertTrue(controller.isNetworkInStateForJobRunLocked(mock(Network.class)));
+
+ mSetFlagsRule.enableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
+ // Unknown network
+ assertFalse(controller.isNetworkInStateForJobRunLocked(mock(Network.class)));
+
+ final Network systemDefaultNetwork = mock(Network.class);
+ final Network otherNetwork = mock(Network.class);
+
+ controller.startTrackingLocked();
+
+ final NetworkCallback allNetworkCallback = allNetworkCallbackCaptor.getValue();
+ final OnNetworkActiveListener onNetworkActiveListener =
+ onNetworkActiveListenerCaptor.getValue();
+ final NetworkCallback systemDefaultNetworkCallback =
+ systemDefaultNetworkCallbackCaptor.getValue();
+
+ // No capabilities set
+ allNetworkCallback.onAvailable(systemDefaultNetwork);
+ allNetworkCallback.onAvailable(otherNetwork);
+ assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Capabilities set, but never active.
+ allNetworkCallback.onCapabilitiesChanged(
+ systemDefaultNetwork, mock(NetworkCapabilities.class));
+ allNetworkCallback.onCapabilitiesChanged(otherNetwork, mock(NetworkCapabilities.class));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Mark system default network as active before identifying system default network.
+ onNetworkActiveListener.onNetworkActive();
+ assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Identify system default network and mark as active.
+ systemDefaultNetworkCallback.onAvailable(systemDefaultNetwork);
+ onNetworkActiveListener.onNetworkActive();
+ assertTrue(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ advanceElapsedClock(controller.getCcConfig().NETWORK_ACTIVATION_EXPIRATION_MS - 1);
+ assertTrue(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Network stays active beyond expected timeout.
+ advanceElapsedClock(2);
+ doReturn(true).when(mConnManager).isDefaultNetworkActive();
+ assertTrue(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Network becomes inactive after expected timeout.
+ advanceElapsedClock(controller.getCcConfig().NETWORK_ACTIVATION_EXPIRATION_MS);
+ doReturn(false).when(mConnManager).isDefaultNetworkActive();
+ assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Other network hasn't received a signal for a long time. System default network has
+ // been active within the max wait time.
+ advanceElapsedClock(controller.getCcConfig().NETWORK_ACTIVATION_MAX_WAIT_TIME_MS
+ - controller.getCcConfig().NETWORK_ACTIVATION_EXPIRATION_MS);
+ doReturn(false).when(mConnManager).isDefaultNetworkActive();
+ assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+ }
+
+ private void advanceElapsedClock(long incrementMs) {
+ JobSchedulerService.sElapsedRealtimeClock = Clock.offset(
+ JobSchedulerService.sElapsedRealtimeClock, Duration.ofMillis(incrementMs));
+ }
+
private void answerNetwork(@NonNull NetworkCallback generalCallback,
@Nullable NetworkCallback uidCallback, @Nullable Network lastNetwork,
@Nullable Network net, @Nullable NetworkCapabilities caps) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
index e7da26e..98789ac 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -519,11 +519,12 @@
eq(AudioManager.ADJUST_MUTE), anyInt());
clearInvocations(mAudioManager);
- // New volume only: sets volume only
+ // New volume only: sets both volume and mute.
+ // Volume changes can affect mute status; we need to set mute afterwards to undo this.
receiveReportAudioStatus(32, true);
verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
anyInt());
- verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
eq(AudioManager.ADJUST_MUTE), anyInt());
clearInvocations(mAudioManager);
@@ -536,17 +537,17 @@
clearInvocations(mAudioManager);
// Repeat of earlier message: sets neither volume nor mute
- // Exception: On TV, volume is set to ensure that UI is shown
+ // Exception: On TV, mute is set to ensure that UI is shown
receiveReportAudioStatus(32, false);
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(32), anyInt());
if (getDeviceType() == HdmiDeviceInfo.DEVICE_TV) {
- verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
- anyInt());
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE), anyInt());
} else {
- verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
- anyInt());
+ verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE), anyInt());
}
- verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
- eq(AudioManager.ADJUST_UNMUTE), anyInt());
clearInvocations(mAudioManager);
// Volume not within range [0, 100]: sets neither volume nor mute
@@ -570,7 +571,8 @@
receiveReportAudioStatus(32, false);
verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
anyInt());
- verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ // Update mute status because we updated volume
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
eq(AudioManager.ADJUST_UNMUTE), anyInt());
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
index a410702..9ad2652 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
@@ -174,11 +174,12 @@
eq(AudioManager.ADJUST_MUTE), anyInt());
clearInvocations(mAudioManager);
- // New volume only: sets volume only
+ // New volume only: sets both volume and mute.
+ // Volume changes can affect mute status; we need to set mute afterwards to undo this.
receiveReportAudioStatus(32, true);
verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
anyInt());
- verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
eq(AudioManager.ADJUST_MUTE), anyInt());
clearInvocations(mAudioManager);
@@ -190,11 +191,11 @@
eq(AudioManager.ADJUST_UNMUTE), anyInt());
clearInvocations(mAudioManager);
- // Repeat of earlier message: sets volume only (to ensure volume UI is shown)
+ // Repeat of earlier message: sets mute only (to ensure volume UI is shown)
receiveReportAudioStatus(32, false);
- verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
anyInt());
- verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
eq(AudioManager.ADJUST_UNMUTE), anyInt());
clearInvocations(mAudioManager);
@@ -392,18 +393,18 @@
INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute()));
mTestLooper.dispatchAll();
- // HdmiControlService calls setStreamVolume to trigger volume UI
- verify(mAudioManager).setStreamVolume(
+ // HdmiControlService calls adjustStreamVolume to trigger volume UI
+ verify(mAudioManager).adjustStreamVolume(
+ eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE),
+ eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
+ // setStreamVolume is not called because volume didn't change,
+ // and adjustStreamVolume is sufficient to show volume UI
+ verify(mAudioManager, never()).setStreamVolume(
eq(AudioManager.STREAM_MUSIC),
// Volume level is rescaled to the max volume of STREAM_MUSIC
eq(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume()
* STREAM_MUSIC_MAX_VOLUME / AudioStatus.MAX_VOLUME),
eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
- // adjustStreamVolume is not called because mute status didn't change,
- // and setStreamVolume is sufficient to show volume UI
- verify(mAudioManager, never()).adjustStreamVolume(
- eq(AudioManager.STREAM_MUSIC),
- eq(AudioManager.ADJUST_UNMUTE),
- eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
index 213e05e..2b07d33 100644
--- a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
@@ -18,6 +18,7 @@
import static android.app.job.JobInfo.NETWORK_TYPE_ANY;
import static android.app.job.JobInfo.NETWORK_TYPE_NONE;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -39,8 +40,6 @@
import org.junit.Test;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Random;
public class PendingJobQueueTest {
@@ -68,7 +67,7 @@
@Test
public void testAdd() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
jobs.add(createJobStatus("testAdd", createJobInfo(1), 1));
jobs.add(createJobStatus("testAdd", createJobInfo(2), 2));
jobs.add(createJobStatus("testAdd", createJobInfo(3).setExpedited(true), 3));
@@ -77,7 +76,7 @@
PendingJobQueue jobQueue = new PendingJobQueue();
for (int i = 0; i < jobs.size(); ++i) {
- jobQueue.add(jobs.get(i));
+ jobQueue.add(jobs.valueAt(i));
assertEquals(i + 1, jobQueue.size());
}
@@ -90,7 +89,7 @@
@Test
public void testAddAll() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
jobs.add(createJobStatus("testAddAll", createJobInfo(1), 1));
jobs.add(createJobStatus("testAddAll", createJobInfo(2), 2));
jobs.add(createJobStatus("testAddAll", createJobInfo(3).setExpedited(true), 3));
@@ -110,7 +109,7 @@
@Test
public void testClear() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
jobs.add(createJobStatus("testClear", createJobInfo(1), 1));
jobs.add(createJobStatus("testClear", createJobInfo(2), 2));
jobs.add(createJobStatus("testClear", createJobInfo(3).setExpedited(true), 3));
@@ -179,7 +178,7 @@
@Test
public void testRemove() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
jobs.add(createJobStatus("testRemove", createJobInfo(1), 1));
jobs.add(createJobStatus("testRemove", createJobInfo(2), 2));
jobs.add(createJobStatus("testRemove", createJobInfo(3).setExpedited(true), 3));
@@ -192,8 +191,8 @@
ArraySet<JobStatus> removed = new ArraySet<>();
JobStatus job;
for (int i = 0; i < jobs.size(); ++i) {
- jobQueue.remove(jobs.get(i));
- removed.add(jobs.get(i));
+ jobQueue.remove(jobs.valueAt(i));
+ removed.add(jobs.valueAt(i));
assertEquals(jobs.size() - i - 1, jobQueue.size());
@@ -209,7 +208,7 @@
@Test
public void testRemove_duringIteration() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
jobs.add(createJobStatus("testRemove", createJobInfo(1), 1));
jobs.add(createJobStatus("testRemove", createJobInfo(2), 2));
jobs.add(createJobStatus("testRemove", createJobInfo(3).setExpedited(true), 3));
@@ -234,7 +233,7 @@
@Test
public void testRemove_outOfOrder() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
JobStatus job1 = createJobStatus("testRemove", createJobInfo(1), 1);
JobStatus job2 = createJobStatus("testRemove", createJobInfo(2), 1);
JobStatus job3 = createJobStatus("testRemove", createJobInfo(3).setExpedited(true), 1);
@@ -269,8 +268,8 @@
Log.d(TAG, testJobToString(job));
}
for (int i = 0; i < jobs.size(); ++i) {
- jobQueue.remove(jobs.get(i));
- removed.add(jobs.get(i));
+ jobQueue.remove(jobs.valueAt(i));
+ removed.add(jobs.valueAt(i));
assertEquals(jobs.size() - i - 1, jobQueue.size());
@@ -294,8 +293,8 @@
removed.clear();
for (int i = 0; i < jobs.size(); ++i) {
- jobQueue.remove(jobs.get(i));
- removed.add(jobs.get(i));
+ jobQueue.remove(jobs.valueAt(i));
+ removed.add(jobs.valueAt(i));
assertEquals(jobs.size() - i - 1, jobQueue.size());
@@ -319,8 +318,8 @@
removed.clear();
for (int i = 0; i < jobs.size(); ++i) {
- jobQueue.remove(jobs.get(i));
- removed.add(jobs.get(i));
+ jobQueue.remove(jobs.valueAt(i));
+ removed.add(jobs.valueAt(i));
assertEquals(jobs.size() - i - 1, jobQueue.size());
diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index c81fbb4..cee7387 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -46,6 +46,7 @@
import android.permission.PermissionCheckerManager;
import android.permission.PermissionManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.SparseArray;
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
@@ -55,6 +56,7 @@
import com.android.server.wm.ActivityTaskManagerInternal;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -109,6 +111,8 @@
private IApplicationThread mIApplicationThread;
private SparseArray<Boolean> mUserEnabled = new SparseArray<>();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void initCrossProfileAppsServiceImpl() {
@@ -123,8 +127,9 @@
mUserEnabled.put(PRIMARY_USER, true);
mUserEnabled.put(PROFILE_OF_PRIMARY_USER, true);
mUserEnabled.put(SECONDARY_USER, true);
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_ENABLE_HIDING_PROFILES);
- when(mUserManager.getEnabledProfileIds(anyInt())).thenAnswer(
+ when(mUserManager.getProfileIdsExcludingHidden(anyInt(), eq(true))).thenAnswer(
invocation -> {
List<Integer> users = new ArrayList<>();
final int targetUser = invocation.getArgument(0);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
index 39cc653..6decf43 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
@@ -114,16 +114,19 @@
final String userType1 = USER_TYPE_PROFILE_MANAGED;
// System user should still have no userType1 profile so getProfileIds should be empty.
- int[] users = mUserManagerService.getProfileIds(UserHandle.USER_SYSTEM, userType1, false);
+ int[] users = mUserManagerService.getProfileIds(UserHandle.USER_SYSTEM, userType1,
+ false, /* excludeHidden */ false);
assertEquals("System user should have no managed profiles", 0, users.length);
// Secondary user should have one userType1 profile, so return just that.
- users = mUserManagerService.getProfileIds(secondaryUser.id, userType1, false);
+ users = mUserManagerService.getProfileIds(secondaryUser.id, userType1,
+ false, /* excludeHidden */ false);
assertEquals("Wrong number of profiles", 1, users.length);
assertEquals("Wrong profile id", profile.id, users[0]);
// The profile itself is a userType1 profile, so it should return just itself.
- users = mUserManagerService.getProfileIds(profile.id, userType1, false);
+ users = mUserManagerService.getProfileIds(profile.id, userType1, false, /* excludeHidden */
+ false);
assertEquals("Wrong number of profiles", 1, users.length);
assertEquals("Wrong profile id", profile.id, users[0]);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index a88285a..897a3da 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -527,29 +527,11 @@
@Test
public void testOnActivityReparentedToTask_untrustedEmbed_notReported() {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
- DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
- mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
- final Task task = createTask(mDisplayContent);
- task.addChild(mTaskFragment, POSITION_TOP);
- final ActivityRecord activity = createActivityRecord(task);
- // Flush EVENT_APPEARED.
- mController.dispatchPendingEvents();
-
- // Make sure the activity is embedded in untrusted mode.
- activity.info.applicationInfo.uid = uid + 1;
- doReturn(pid + 1).when(activity).getPid();
- task.effectiveUid = uid;
- doReturn(EMBEDDING_ALLOWED).when(task).isAllowedToEmbedActivity(activity, uid);
- doReturn(false).when(task).isAllowedToEmbedActivityInTrustedMode(activity, uid);
- doReturn(true).when(task).isAllowedToEmbedActivityInUntrustedMode(activity);
+ final ActivityRecord activity = setupUntrustedEmbeddingPipReparent();
+ doReturn(false).when(activity).isUntrustedEmbeddingStateSharingAllowed();
// Notify organizer if it was embedded before entered Pip.
// Create a temporary token since the activity doesn't belong to the same process.
- clearInvocations(mOrganizer);
- activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
@@ -558,6 +540,30 @@
}
@Test
+ public void testOnActivityReparentedToTask_untrustedEmbed_reportedWhenAppOptIn() {
+ final ActivityRecord activity = setupUntrustedEmbeddingPipReparent();
+ doReturn(true).when(activity).isUntrustedEmbeddingStateSharingAllowed();
+
+ // Notify organizer if it was embedded before entered Pip.
+ // Create a temporary token since the activity doesn't belong to the same process.
+ mController.onActivityReparentedToTask(activity);
+ mController.dispatchPendingEvents();
+
+ // Allow organizer to reparent activity in other process using the temporary token.
+ verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
+ final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
+ final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
+ assertFalse(changes.isEmpty());
+ final TaskFragmentTransaction.Change change = changes.get(0);
+ assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType());
+ assertEquals(activity.getTask().mTaskId, change.getTaskId());
+ assertIntentsEqualForOrganizer(activity.intent, change.getActivityIntent());
+ assertNotEquals(activity.token, change.getActivityToken());
+ mTransaction.reparentActivityToTaskFragment(mFragmentToken, change.getActivityToken());
+ assertApplyTransactionAllowed(mTransaction);
+ }
+
+ @Test
public void testOnActivityReparentedToTask_trimReportedIntent() {
// Make sure the activity pid/uid is the same as the organizer caller.
final int pid = Binder.getCallingPid();
@@ -1868,6 +1874,34 @@
OP_TYPE_REORDER_TO_TOP_OF_TASK);
}
+ @NonNull
+ private ActivityRecord setupUntrustedEmbeddingPipReparent() {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
+ DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ final Task task = createTask(mDisplayContent);
+ task.addChild(mTaskFragment, POSITION_TOP);
+ final ActivityRecord activity = createActivityRecord(task);
+
+ // Flush EVENT_APPEARED.
+ mController.dispatchPendingEvents();
+
+ // Make sure the activity is embedded in untrusted mode.
+ activity.info.applicationInfo.uid = uid + 1;
+ doReturn(pid + 1).when(activity).getPid();
+ task.effectiveUid = uid;
+ doReturn(EMBEDDING_ALLOWED).when(task).isAllowedToEmbedActivity(activity, uid);
+ doReturn(false).when(task).isAllowedToEmbedActivityInTrustedMode(activity, uid);
+ doReturn(true).when(task).isAllowedToEmbedActivityInUntrustedMode(activity);
+
+ clearInvocations(mOrganizer);
+ activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
+
+ return activity;
+ }
+
private void testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
@TaskFragmentOperation.OperationType int opType) {
final Task task = createTask(mDisplayContent);