diff options
169 files changed, 4694 insertions, 1091 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 0fdd880a80f4..cd991c70d719 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -69,6 +69,7 @@ aconfig_srcjars = [ ":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 @@ java_aconfig_library { 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 5d65d9d0629f..8a5206fa3f09 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 @@ flag { 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 6550f26436d4..012ede274bc1 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 @@ class JobConcurrencyManager { 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 57467e3cc83d..cea16d6213af 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.ParceledListSlice; 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.text.format.DateUtils; 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService sc.onConstantsUpdatedLocked(); } } + + mHandler.sendEmptyMessage(MSG_CHECK_JOB); } @Override @@ -646,8 +654,12 @@ public class JobSchedulerService extends com.android.server.SystemService */ 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 @@ public class JobSchedulerService extends com.android.server.SystemService "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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 class JobSchedulerService extends com.android.server.SystemService */ 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 class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService .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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService } 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 @@ public class JobSchedulerService extends com.android.server.SystemService * 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService @GuardedBy("mLock") @VisibleForTesting void postProcessLocked() { - if (unbatchedCount > 0 - || forceBatchedCount >= mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT) { + 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; + } + + // 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 jobs."); + Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running " + + jobsToRun + " jobs."); } - noteJobsPending(runnableJobs); - mPendingJobQueue.addAll(runnableJobs); + 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 @@ public class JobSchedulerService extends com.android.server.SystemService @VisibleForTesting void reset() { - forceBatchedCount = 0; - unbatchedCount = 0; runnableJobs.clear(); + mBatches.clear(); + mUnbatchedJobs.clear(); + mUnbatchedJobCount.clear(); } } @@ -5468,8 +5753,14 @@ public class JobSchedulerService extends com.android.server.SystemService 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 0cf6a7a8a4f6..90b4630e9cff 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 4f4096f69ad5..813cf8710ab1 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 @@ package com.android.server.job; import android.annotation.NonNull; import android.annotation.Nullable; +import android.util.ArraySet; import android.util.Pools; import android.util.SparseArray; @@ -96,10 +97,10 @@ class PendingJobQueue { } } - 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 f40508302ee3..6ed42ec990ea 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.NET_CAPABILITY_TEMPORARILY_NOT_MET 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.GuardedBy; 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 @@ public final class ConnectivityController extends RestrictingController implemen @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 @@ public final class ConnectivityController extends RestrictingController implemen 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 @@ public final class ConnectivityController extends RestrictingController implemen } } + @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 @@ public final class ConnectivityController extends RestrictingController implemen 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 @@ public final class ConnectivityController extends RestrictingController implemen // 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 @@ public final class ConnectivityController extends RestrictingController implemen } @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 @@ public final class ConnectivityController extends RestrictingController implemen } /** + * 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 @@ public final class ConnectivityController extends RestrictingController implemen 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 @@ public final class ConnectivityController extends RestrictingController implemen } }; + /** 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 @@ public final class ConnectivityController extends RestrictingController implemen } } 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 @@ public final class ConnectivityController extends RestrictingController implemen @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 final class ConnectivityController extends RestrictingController implemen 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 @@ public final class ConnectivityController extends RestrictingController implemen 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 @@ public final class ConnectivityController extends RestrictingController implemen 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 @@ public final class ConnectivityController extends RestrictingController implemen 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 @@ public final class ConnectivityController extends RestrictingController implemen 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 787055c8cd89..25d3a341bf6e 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 @@ public class Device { 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 e2c2f274e3b2..283e429c13bf 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -506,6 +506,7 @@ package android { 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 @@ -4379,6 +4380,7 @@ package android.app { method public android.transition.TransitionManager getContentTransitionManager(); method @Nullable public android.view.View getCurrentFocus(); method @Deprecated public android.app.FragmentManager getFragmentManager(); + method @FlaggedApi("android.security.content_uri_permission_apis") @NonNull public android.app.ComponentCaller getInitialCaller(); method public android.content.Intent getIntent(); method @Nullable public Object getLastNonConfigurationInstance(); method @Nullable public String getLaunchedFromPackage(); @@ -5419,6 +5421,12 @@ package android.app { field public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1; // 0x1 } + @FlaggedApi("android.security.content_uri_permission_apis") public final class ComponentCaller { + ctor public ComponentCaller(@NonNull android.os.IBinder, @Nullable android.os.IBinder); + method @Nullable public String getPackage(); + method public int getUid(); + } + public class DatePickerDialog extends android.app.AlertDialog implements android.widget.DatePicker.OnDateChangedListener android.content.DialogInterface.OnClickListener { ctor public DatePickerDialog(@NonNull android.content.Context); ctor public DatePickerDialog(@NonNull android.content.Context, @StyleRes int); @@ -42370,6 +42378,8 @@ package android.telecom { method public void onConference(android.telecom.Connection, android.telecom.Connection); method public void onConnectionServiceFocusGained(); method public void onConnectionServiceFocusLost(); + method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public void onCreateConferenceComplete(@NonNull android.telecom.Conference); + method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public void onCreateConnectionComplete(@NonNull android.telecom.Connection); method @Nullable public android.telecom.Conference onCreateIncomingConference(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest); method public void onCreateIncomingConferenceFailed(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); @@ -53905,6 +53915,7 @@ package android.view { 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 42b2ef6d41d5..66e03db5923b 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10168,6 +10168,7 @@ package android.nfc.cardemulation { @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 @@ package android.nfc.cardemulation { 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/Activity.java b/core/java/android/app/Activity.java index 2103055afe50..ab9a4ecd8506 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -844,6 +844,7 @@ public class Activity extends ContextThemeWrapper private IBinder mToken; private IBinder mAssistToken; private IBinder mShareableActivityToken; + private ComponentCaller mInitialCaller; @UnsupportedAppUsage private int mIdent; @UnsupportedAppUsage @@ -7031,6 +7032,20 @@ public class Activity extends ContextThemeWrapper } /** + * Returns the ComponentCaller instance of the app that initially launched this activity. + * + * <p>Note that calls to {@link #onNewIntent} have no effect on the returned value of this + * method. + * + * @return {@link ComponentCaller} instance + * @see ComponentCaller + */ + @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS) + public @NonNull ComponentCaller getInitialCaller() { + return mInitialCaller; + } + + /** * Control whether this activity's main window is visible. This is intended * only for the special case of an activity that is not going to show a * UI itself, but can't just finish prior to onResume() because it needs @@ -8647,6 +8662,19 @@ public class Activity extends ContextThemeWrapper Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken, IBinder shareableActivityToken) { + attach(context, aThread, instr, token, ident, application, intent, info, title, parent, id, + lastNonConfigurationInstances, config, referrer, voiceInteractor, window, + activityConfigCallback, assistToken, shareableActivityToken, null); + } + + final void attach(Context context, ActivityThread aThread, + Instrumentation instr, IBinder token, int ident, + Application application, Intent intent, ActivityInfo info, + CharSequence title, Activity parent, String id, + NonConfigurationInstances lastNonConfigurationInstances, + Configuration config, String referrer, IVoiceInteractor voiceInteractor, + Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken, + IBinder shareableActivityToken, IBinder initialCallerInfoAccessToken) { if (sandboxActivitySdkBasedContext()) { // Sandbox activities extract a token from the intent's extra to identify the related // SDK as part of overriding attachBaseContext, then it wraps the passed context in an @@ -8711,6 +8739,10 @@ public class Activity extends ContextThemeWrapper getAutofillClientController().onActivityAttached(application); setContentCaptureOptions(application.getContentCaptureOptions()); + + if (android.security.Flags.contentUriPermissionApis()) { + mInitialCaller = new ComponentCaller(getActivityToken(), initialCallerInfoAccessToken); + } } /** @hide */ diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 4c54b03bd6d6..2c00c99cec91 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -580,6 +580,7 @@ public final class ActivityThread extends ClientTransactionHandler public IBinder shareableActivityToken; // The token of the TaskFragment that embedded this activity. @Nullable public IBinder mTaskFragmentToken; + public IBinder initialCallerInfoAccessToken; int ident; @UnsupportedAppUsage Intent intent; @@ -668,7 +669,7 @@ public final class ActivityThread extends ClientTransactionHandler List<ReferrerIntent> pendingNewIntents, SceneTransitionInfo sceneTransitionInfo, boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client, IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble, - IBinder taskFragmentToken) { + IBinder taskFragmentToken, IBinder initialCallerInfoAccessToken) { this.token = token; this.assistToken = assistToken; this.shareableActivityToken = shareableActivityToken; @@ -685,6 +686,7 @@ public final class ActivityThread extends ClientTransactionHandler this.profilerInfo = profilerInfo; this.overrideConfig = overrideConfig; this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo); + this.initialCallerInfoAccessToken = initialCallerInfoAccessToken; mSceneTransitionInfo = sceneTransitionInfo; mLaunchedFromBubble = launchedFromBubble; mTaskFragmentToken = taskFragmentToken; @@ -3914,7 +3916,7 @@ public final class ActivityThread extends ClientTransactionHandler r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.activityConfigCallback, - r.assistToken, r.shareableActivityToken); + r.assistToken, r.shareableActivityToken, r.initialCallerInfoAccessToken); if (customIntent != null) { activity.mIntent = customIntent; diff --git a/core/java/android/app/ComponentCaller.java b/core/java/android/app/ComponentCaller.java new file mode 100644 index 000000000000..583408ed8db9 --- /dev/null +++ b/core/java/android/app/ComponentCaller.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.annotation.FlaggedApi; +import android.annotation.Nullable; +import android.os.IBinder; +import android.os.Process; + +import androidx.annotation.NonNull; + +import java.util.Objects; + +/** + * Represents the app that launched the component. See below for the APIs available on the component + * caller. + * + * <p><b>Note</b>, that in {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} only + * {@link Activity} has access to {@link ComponentCaller} instances. + * + * @see Activity#getInitialCaller() + */ +@FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS) +public final class ComponentCaller { + private final IBinder mActivityToken; + private final IBinder mCallerToken; + + public ComponentCaller(@NonNull IBinder activityToken, @Nullable IBinder callerToken) { + mActivityToken = activityToken; + mCallerToken = callerToken; + } + + /** + * Returns the uid of this component caller. + * + * <p><b>Note</b>, in {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} only + * {@link Activity} has access to {@link ComponentCaller} instances. + * <p> + * <h3>Requirements for {@link Activity} callers</h3> + * + * <p>In order to receive the calling app's uid, at least one of the following has to be met: + * <ul> + * <li>The calling app must call {@link ActivityOptions#setShareIdentityEnabled(boolean)} + * with a value of {@code true} and launch this activity with the resulting + * {@code ActivityOptions}. + * <li>The launched activity has the same uid as the calling app. + * <li>The launched activity is running in a package that is signed with the same key used + * to sign the platform (typically only system packages such as Settings will meet this + * requirement). + * </ul> + * These are the same requirements for {@link #getPackage()}; if any of these are met, then + * these methods can be used to obtain the uid and package name of the calling app. If none are + * met, then {@link Process#INVALID_UID} is returned. + * + * <p>Note, even if the above conditions are not met, the calling app's identity may still be + * available from {@link Activity#getCallingPackage()} if this activity was started with + * {@code Activity#startActivityForResult} to allow validation of the result's recipient. + * + * @return the uid of the calling app or {@link Process#INVALID_UID} if the current component + * cannot access the identity of the calling app or the caller is invalid + * + * @see ActivityOptions#setShareIdentityEnabled(boolean) + * @see Activity#getLaunchedFromUid() + */ + public int getUid() { + return ActivityClient.getInstance().getLaunchedFromUid(mActivityToken); + } + + /** + * Returns the package name of this component caller. + * + * <p><b>Note</b>, in {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} only + * {@link Activity} has access to {@link ComponentCaller} instances. + * <p> + * <h3>Requirements for {@link Activity} callers</h3> + * + * <p>In order to receive the calling app's package name, at least one of the following has to + * be met: + * <ul> + * <li>The calling app must call {@link ActivityOptions#setShareIdentityEnabled(boolean)} + * with a value of {@code true} and launch this activity with the resulting + * {@code ActivityOptions}. + * <li>The launched activity has the same uid as the calling app. + * <li>The launched activity is running in a package that is signed with the same key used + * to sign the platform (typically only system packages such as Settings will meet this + * meet this requirement). + * </ul> + * These are the same requirements for {@link #getUid()}; if any of these are met, then these + * methods can be used to obtain the uid and package name of the calling app. If none are met, + * then {@code null} is returned. + * + * <p>Note, even if the above conditions are not met, the calling app's identity may still be + * available from {@link Activity#getCallingPackage()} if this activity was started with + * {@code Activity#startActivityForResult} to allow validation of the result's recipient. + * + * @return the package name of the calling app or null if the current component cannot access + * the identity of the calling app or the caller is invalid + * + * @see ActivityOptions#setShareIdentityEnabled(boolean) + * @see Activity#getLaunchedFromPackage() + */ + @Nullable + public String getPackage() { + return ActivityClient.getInstance().getLaunchedFromPackage(mActivityToken); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == null || !(obj instanceof ComponentCaller other)) { + return false; + } + return this.mActivityToken == other.mActivityToken + && this.mCallerToken == other.mCallerToken; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Objects.hashCode(mActivityToken); + result = 31 * result + Objects.hashCode(mCallerToken); + return result; + } +} diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 68512b8bd771..454d60534476 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1423,8 +1423,8 @@ public class Instrumentation { info, title, parent, id, (Activity.NonConfigurationInstances)lastNonConfigurationInstance, new Configuration(), null /* referrer */, null /* voiceInteractor */, - null /* window */, null /* activityCallback */, null /*assistToken*/, - null /*shareableActivityToken*/); + null /* window */, null /* activityCallback */, null /* assistToken */, + null /* shareableActivityToken */, null /* initialCallerInfoAccessToken */); return activity; } diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java index 5e5526802fe9..6357a20f592b 100644 --- a/core/java/android/app/servertransaction/ClientTransaction.java +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -298,6 +298,33 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { 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/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java index 4d53701772e4..95f5ad0bd38f 100644 --- a/core/java/android/app/servertransaction/LaunchActivityItem.java +++ b/core/java/android/app/servertransaction/LaunchActivityItem.java @@ -80,6 +80,7 @@ public class LaunchActivityItem extends ClientTransactionItem { private IBinder mShareableActivityToken; private boolean mLaunchedFromBubble; private IBinder mTaskFragmentToken; + private IBinder mInitialCallerInfoAccessToken; /** * It is only non-null if the process is the first time to launch activity. It is only an * optimization for quick look up of the interface so the field is ignored for comparison. @@ -106,7 +107,7 @@ public class LaunchActivityItem extends ClientTransactionItem { mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState, mPendingResults, mPendingNewIntents, mSceneTransitionInfo, mIsForward, mProfilerInfo, client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble, - mTaskFragmentToken); + mTaskFragmentToken, mInitialCallerInfoAccessToken); client.handleLaunchActivity(r, pendingActions, mDeviceId, null /* customIntent */); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -140,7 +141,7 @@ public class LaunchActivityItem extends ClientTransactionItem { boolean isForward, @Nullable ProfilerInfo profilerInfo, @NonNull IBinder assistToken, @Nullable IActivityClientController activityClientController, @NonNull IBinder shareableActivityToken, boolean launchedFromBubble, - @Nullable IBinder taskFragmentToken) { + @Nullable IBinder taskFragmentToken, @NonNull IBinder initialCallerInfoAccessToken) { LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class); if (instance == null) { instance = new LaunchActivityItem(); @@ -155,7 +156,7 @@ public class LaunchActivityItem extends ClientTransactionItem { sceneTransitionInfo, isForward, profilerInfo != null ? new ProfilerInfo(profilerInfo) : null, assistToken, activityClientController, shareableActivityToken, - launchedFromBubble, taskFragmentToken); + launchedFromBubble, taskFragmentToken, initialCallerInfoAccessToken); return instance; } @@ -170,7 +171,7 @@ public class LaunchActivityItem extends ClientTransactionItem { @Override public void recycle() { setValues(this, null, null, 0, null, null, null, 0, null, null, 0, null, null, null, null, - null, false, null, null, null, null, false, null); + null, false, null, null, null, null, false, null, null); ObjectPool.recycle(this); } @@ -201,6 +202,7 @@ public class LaunchActivityItem extends ClientTransactionItem { dest.writeStrongBinder(mShareableActivityToken); dest.writeBoolean(mLaunchedFromBubble); dest.writeStrongBinder(mTaskFragmentToken); + dest.writeStrongBinder(mInitialCallerInfoAccessToken); } /** Read from Parcel. */ @@ -220,6 +222,7 @@ public class LaunchActivityItem extends ClientTransactionItem { IActivityClientController.Stub.asInterface(in.readStrongBinder()), in.readStrongBinder(), in.readBoolean(), + in.readStrongBinder(), in.readStrongBinder()); } @@ -259,7 +262,9 @@ public class LaunchActivityItem extends ClientTransactionItem { && Objects.equals(mProfilerInfo, other.mProfilerInfo) && Objects.equals(mAssistToken, other.mAssistToken) && Objects.equals(mShareableActivityToken, other.mShareableActivityToken) - && Objects.equals(mTaskFragmentToken, other.mTaskFragmentToken); + && Objects.equals(mTaskFragmentToken, other.mTaskFragmentToken) + && Objects.equals(mInitialCallerInfoAccessToken, + other.mInitialCallerInfoAccessToken); } @Override @@ -283,6 +288,7 @@ public class LaunchActivityItem extends ClientTransactionItem { result = 31 * result + Objects.hashCode(mAssistToken); result = 31 * result + Objects.hashCode(mShareableActivityToken); result = 31 * result + Objects.hashCode(mTaskFragmentToken); + result = 31 * result + Objects.hashCode(mInitialCallerInfoAccessToken); return result; } @@ -345,7 +351,7 @@ public class LaunchActivityItem extends ClientTransactionItem { @Nullable ProfilerInfo profilerInfo, @Nullable IBinder assistToken, @Nullable IActivityClientController activityClientController, @Nullable IBinder shareableActivityToken, boolean launchedFromBubble, - @Nullable IBinder taskFragmentToken) { + @Nullable IBinder taskFragmentToken, @Nullable IBinder initialCallerInfoAccessToken) { instance.mActivityToken = activityToken; instance.mIntent = intent; instance.mIdent = ident; @@ -368,5 +374,6 @@ public class LaunchActivityItem extends ClientTransactionItem { instance.mShareableActivityToken = shareableActivityToken; instance.mLaunchedFromBubble = launchedFromBubble; instance.mTaskFragmentToken = taskFragmentToken; + instance.mInitialCallerInfoAccessToken = initialCallerInfoAccessToken; } } diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index ba940770898a..406e00a84710 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -97,6 +97,10 @@ public class TransactionExecutor { 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 632c0f54375c..f84b46db144b 100644 --- a/core/java/android/content/pm/ProcessInfo.java +++ b/core/java/android/content/pm/ProcessInfo.java @@ -18,10 +18,8 @@ package android.content.pm; 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 class ProcessInfo implements Parcelable { */ 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 @@ public class ProcessInfo implements Parcelable { 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 @@ public class ProcessInfo implements Parcelable { * 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 @@ public class ProcessInfo implements Parcelable { @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 @@ public class ProcessInfo implements Parcelable { 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 @@ public class ProcessInfo implements Parcelable { // 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 @@ public class ProcessInfo implements Parcelable { // 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 @@ public class ProcessInfo implements Parcelable { 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 @@ public class ProcessInfo implements Parcelable { }; @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 d7e64b66763e..7ded7472bc1f 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -44,6 +44,20 @@ flag { } flag { + name: "start_user_before_scheduled_alarms" + namespace: "multiuser" + description: "Persist list of users with alarms scheduled and wakeup stopped users before alarms are due" + bug: "314907186" +} + +flag { + name: "add_ui_for_sounds_from_background_users" + namespace: "multiuser" + description: "Allow foreground user to dismiss sounds that are coming from background users" + bug: "314907186" +} + +flag { name: "enable_biometrics_to_unlock_private_space" namespace: "profile_experiences" description: "Add support to unlock the private space using biometrics" @@ -122,3 +136,10 @@ flag { 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 462667926053..69f9a7db6a85 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.AttributeSet; 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 @@ public class ApkLiteParseUtils { 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 @@ public class ApkLiteParseUtils { 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/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 71698e4f4469..281ee500f741 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -1256,7 +1256,9 @@ public class InputMethodService extends AbstractInputMethodService { private void updateEditorToolTypeInternal(int toolType) { if (Flags.useHandwritingListenerForTooltype()) { mLastUsedToolType = toolType; - mInputEditorInfo.setInitialToolType(toolType); + if (mInputEditorInfo != null) { + mInputEditorInfo.setInitialToolType(toolType); + } } onUpdateEditorToolType(toolType); } diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index c0d1fb9c6a88..800ba6d3a542 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -150,4 +150,5 @@ interface IUserManager { 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/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java index e6bfcd728a52..224b10d0eaca 100644 --- a/core/java/android/os/PerformanceHintManager.java +++ b/core/java/android/os/PerformanceHintManager.java @@ -313,25 +313,33 @@ public final class PerformanceHintManager { * close to the target duration. * * @param workDuration the work duration of each component. - * @throws IllegalArgumentException if work period start timestamp is not positive, or - * actual total duration is not positive, or actual CPU duration is not positive, - * or actual GPU duration is negative. + * @throws IllegalArgumentException if + * the work period start timestamp or the total duration are less than or equal to zero, + * if either the actual CPU duration or actual GPU duration is less than zero, + * or if both the CPU and GPU durations are zero. */ @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) public void reportActualWorkDuration(@NonNull WorkDuration workDuration) { if (workDuration.mWorkPeriodStartTimestampNanos <= 0) { throw new IllegalArgumentException( - "the work period start timestamp should be positive."); + "the work period start timestamp should be greater than zero."); } if (workDuration.mActualTotalDurationNanos <= 0) { - throw new IllegalArgumentException("the actual total duration should be positive."); + throw new IllegalArgumentException( + "the actual total duration should be greater than zero."); } - if (workDuration.mActualCpuDurationNanos <= 0) { - throw new IllegalArgumentException("the actual CPU duration should be positive."); + if (workDuration.mActualCpuDurationNanos < 0) { + throw new IllegalArgumentException( + "the actual CPU duration should be greater than or equal to zero."); } if (workDuration.mActualGpuDurationNanos < 0) { throw new IllegalArgumentException( - "the actual GPU duration should be non negative."); + "the actual GPU duration should be greater than or equal to zero."); + } + if (workDuration.mActualCpuDurationNanos + workDuration.mActualGpuDurationNanos <= 0) { + throw new IllegalArgumentException( + "either the actual CPU duration or the actual GPU duration should be greater" + + "than zero."); } nativeReportActualWorkDuration(mNativeSessionPtr, workDuration.mWorkPeriodStartTimestampNanos, diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index ad0f940f2a0b..0da19df0b0af 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -5357,6 +5357,25 @@ public class UserManager { } /** + * @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/os/WorkDuration.java b/core/java/android/os/WorkDuration.java index 2ebcd830be29..5a54e90372fc 100644 --- a/core/java/android/os/WorkDuration.java +++ b/core/java/android/os/WorkDuration.java @@ -83,7 +83,7 @@ public final class WorkDuration implements Parcelable { public void setWorkPeriodStartTimestampNanos(long workPeriodStartTimestampNanos) { if (workPeriodStartTimestampNanos <= 0) { throw new IllegalArgumentException( - "the work period start timestamp should be positive."); + "the work period start timestamp should be greater than zero."); } mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos; } @@ -95,7 +95,8 @@ public final class WorkDuration implements Parcelable { */ public void setActualTotalDurationNanos(long actualTotalDurationNanos) { if (actualTotalDurationNanos <= 0) { - throw new IllegalArgumentException("the actual total duration should be positive."); + throw new IllegalArgumentException( + "the actual total duration should be greater than zero."); } mActualTotalDurationNanos = actualTotalDurationNanos; } @@ -106,8 +107,9 @@ public final class WorkDuration implements Parcelable { * All timings should be in {@link SystemClock#uptimeNanos()}. */ public void setActualCpuDurationNanos(long actualCpuDurationNanos) { - if (actualCpuDurationNanos <= 0) { - throw new IllegalArgumentException("the actual CPU duration should be positive."); + if (actualCpuDurationNanos < 0) { + throw new IllegalArgumentException( + "the actual CPU duration should be greater than or equal to zero."); } mActualCpuDurationNanos = actualCpuDurationNanos; } @@ -119,7 +121,8 @@ public final class WorkDuration implements Parcelable { */ public void setActualGpuDurationNanos(long actualGpuDurationNanos) { if (actualGpuDurationNanos < 0) { - throw new IllegalArgumentException("the actual GPU duration should be non negative."); + throw new IllegalArgumentException( + "the actual GPU duration should be greater than or equal to zero."); } mActualGpuDurationNanos = actualGpuDurationNanos; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 76fda0636282..ef2d5ebd33fd 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -18161,6 +18161,16 @@ public final class Settings { "show_notification_channel_warnings"; /** + * Whether to disable app and notification screen share protections. + * + * The value 1 - enable, 0 - disable + * @hide + */ + @Readable + public static final String DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS = + "disable_screen_share_protections_for_apps_and_notifications"; + + /** * Whether cell is enabled/disabled * @hide */ diff --git a/core/java/android/tracing/transition/TransitionDataSource.java b/core/java/android/tracing/transition/TransitionDataSource.java index 82559dab0e5b..744f4469bc75 100644 --- a/core/java/android/tracing/transition/TransitionDataSource.java +++ b/core/java/android/tracing/transition/TransitionDataSource.java @@ -16,7 +16,6 @@ package android.tracing.transition; -import android.tracing.perfetto.CreateTlsStateArgs; import android.tracing.perfetto.DataSource; import android.tracing.perfetto.DataSourceInstance; import android.tracing.perfetto.FlushCallbackArguments; @@ -24,23 +23,17 @@ import android.tracing.perfetto.StartCallbackArguments; import android.tracing.perfetto.StopCallbackArguments; import android.util.proto.ProtoInputStream; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - /** * @hide */ public class TransitionDataSource - extends DataSource<DataSourceInstance, TransitionDataSource.TlsState, Void> { + extends DataSource<DataSourceInstance, Void, Void> { public static String DATA_SOURCE_NAME = "com.android.wm.shell.transition"; private final Runnable mOnStartStaticCallback; private final Runnable mOnFlushStaticCallback; private final Runnable mOnStopStaticCallback; - private final ConcurrentHashMap<Integer, ConcurrentHashMap<String, Integer>> mHandlerMappings = - new ConcurrentHashMap<>(); - public TransitionDataSource(Runnable onStart, Runnable onFlush, Runnable onStop) { super(DATA_SOURCE_NAME); this.mOnStartStaticCallback = onStart; @@ -49,20 +42,6 @@ public class TransitionDataSource } @Override - protected TlsState createTlsState(CreateTlsStateArgs<DataSourceInstance> args) { - return new TlsState(args.getDataSourceInstanceLocked().getInstanceIndex()); - } - - public class TlsState { - public final Map<String, Integer> handlerMapping; - - public TlsState(int instanceIndex) { - handlerMapping = mHandlerMappings - .computeIfAbsent(instanceIndex, index -> new ConcurrentHashMap<>()); - } - } - - @Override public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) { return new DataSourceInstance(this, instanceIndex) { @Override @@ -78,7 +57,6 @@ public class TransitionDataSource @Override protected void onStop(StopCallbackArguments args) { mOnStopStaticCallback.run(); - mHandlerMappings.remove(instanceIndex); } }; } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 38cf4907cbe7..ac2a66e79d1d 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1472,6 +1472,34 @@ public interface WindowManager extends ViewManager { "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/view/accessibility/MagnificationAnimationCallback.java b/core/java/android/view/accessibility/MagnificationAnimationCallback.java index 72518db3c60e..175549753228 100644 --- a/core/java/android/view/accessibility/MagnificationAnimationCallback.java +++ b/core/java/android/view/accessibility/MagnificationAnimationCallback.java @@ -16,6 +16,8 @@ package android.view.accessibility; +import android.view.MagnificationSpec; + /** * A callback for magnification animation result. * @hide @@ -31,4 +33,16 @@ public interface MagnificationAnimationCallback { * change. Otherwise {@code false} */ void onResult(boolean success); + + /** + * Called when the animation is finished or interrupted during animating. + * + * @param success {@code true} if animating successfully with given spec or the spec did not + * change. Otherwise {@code false} + * @param lastSpecSent the last spec that was sent to WindowManager for animation, in case you + * need to update the local copy + */ + default void onResult(boolean success, MagnificationSpec lastSpecSent) { + onResult(success); + } }
\ No newline at end of file diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java index cc875ad7ad1b..c76c7a482865 100644 --- a/core/java/android/window/SnapshotDrawerUtils.java +++ b/core/java/android/window/SnapshotDrawerUtils.java @@ -21,6 +21,7 @@ import static android.graphics.Color.alpha; 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 @@ public class SnapshotDrawerUtils { | 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 e32c8e58bb21..739cf0eeca0c 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.IBinder; 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 @@ public final class WindowMetricsController { 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 65075aea7b27..baefe7be1f54 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -226,6 +226,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { 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 2e20cceb64de..bc63881163f0 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -58,6 +58,14 @@ flag { 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 e5247f99f398..852ed1c3e2ac 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 @@ public interface ParsedProcess { @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 212fb867e7df..ff9b11a3d06a 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 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable { @ApplicationInfo.NativeHeapZeroInitialized private int nativeHeapZeroInitialized = ApplicationInfo.ZEROINIT_DEFAULT; + private boolean useEmbeddedDex; + public ParsedProcessImpl() { } @@ -65,6 +67,7 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable { gwpAsanMode = other.getGwpAsanMode(); memtagMode = other.getMemtagMode(); nativeHeapZeroInitialized = other.getNativeHeapZeroInitialized(); + useEmbeddedDex = other.isUseEmbeddedDex(); } public void addStateFrom(@NonNull ParsedProcess other) { @@ -72,6 +75,7 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable { 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 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable { @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 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable { 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 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable { } @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 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable { } @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 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable { // 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 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable { // 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 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable { 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 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable { }; @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 3b2056e7892e..dd58815fdc5e 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.ArrayMap; 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 @@ public class ParsedProcessUtils { 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 000000000000..ea9abdbc4388 --- /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/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp index 323f7b63246c..b5fbb22215b4 100644 --- a/core/jni/android_view_InputEventSender.cpp +++ b/core/jni/android_view_InputEventSender.cpp @@ -73,7 +73,7 @@ private: uint32_t mNextPublishedSeq; const std::string getInputChannelName() { - return mInputPublisher.getChannel()->getName(); + return mInputPublisher.getChannel().getName(); } int handleEvent(int receiveFd, int events, void* data) override; @@ -102,7 +102,7 @@ NativeInputEventSender::~NativeInputEventSender() { } status_t NativeInputEventSender::initialize() { - const int receiveFd = mInputPublisher.getChannel()->getFd(); + const int receiveFd = mInputPublisher.getChannel().getFd(); mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL); return OK; } @@ -112,7 +112,7 @@ void NativeInputEventSender::dispose() { LOG(DEBUG) << "channel '" << getInputChannelName() << "' ~ Disposing input event sender."; } - mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd()); + mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel().getFd()); } status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* event) { diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index d3f3af738273..286185864593 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -723,6 +723,7 @@ message GlobalSettingsProto { // encoded as a key=value list separated by commas. optional SettingProto smart_suggestions_in_notifications_flags = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto bubbles = 6 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto disable_screen_share_protections_for_apps_and_notifications = 7 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Notification notification = 82; diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index f154b571b3e4..e7b1d09d6ab0 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 6884fc0057d9..65c4d9fdab78 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 72bfa8ac9113..81a8908b37df 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/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java index 296c45136292..262f167078e2 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java @@ -31,6 +31,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertThrows; +import android.Manifest; import android.app.compat.CompatChanges; import android.graphics.Bitmap; import android.hardware.broadcastradio.ConfigFlag; @@ -57,6 +58,8 @@ import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import android.util.ArraySet; +import androidx.test.platform.app.InstrumentationRegistry; + import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase; import com.android.server.broadcastradio.RadioServiceUserController; @@ -171,6 +174,10 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { @Before public void setup() throws Exception { + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE, + Manifest.permission.READ_COMPAT_CHANGE_CONFIG); + doReturn(true).when(() -> CompatChanges.isChangeEnabled( eq(ConversionUtils.RADIO_U_VERSION_REQUIRED), anyInt())); doReturn(USER_ID_1).when(mUserHandleMock).getIdentifier(); diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index a796a0fbb7ab..c6447be71855 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java @@ -123,6 +123,7 @@ public class ObjectPoolTests { final IBinder shareableActivityToken = new Binder(); final int deviceId = 3; final IBinder taskFragmentToken = new Binder(); + final IBinder initialCallerInfoAccessToken = new Binder(); testRecycle(() -> new LaunchActivityItemBuilder( activityToken, intent, activityInfo) @@ -140,6 +141,7 @@ public class ObjectPoolTests { .setShareableActivityToken(shareableActivityToken) .setTaskFragmentToken(taskFragmentToken) .setDeviceId(deviceId) + .setInitialCallerInfoAccessToken(initialCallerInfoAccessToken) .build()); } diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java index 3823033d48a1..d641659e6a5f 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java +++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java @@ -132,6 +132,8 @@ class TestUtils { private boolean mLaunchedFromBubble; @Nullable private IBinder mTaskFragmentToken; + @Nullable + private IBinder mInitialCallerInfoAccessToken; LaunchActivityItemBuilder(@NonNull IBinder activityToken, @NonNull Intent intent, @NonNull ActivityInfo info) { @@ -251,13 +253,21 @@ class TestUtils { } @NonNull + LaunchActivityItemBuilder setInitialCallerInfoAccessToken( + @Nullable IBinder initialCallerInfoAccessToken) { + mInitialCallerInfoAccessToken = initialCallerInfoAccessToken; + return this; + } + + @NonNull LaunchActivityItem build() { return LaunchActivityItem.obtain(mActivityToken, mIntent, mIdent, mInfo, mCurConfig, mOverrideConfig, mDeviceId, mReferrer, mVoiceInteractor, mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents, mActivityOptions != null ? mActivityOptions.getSceneTransitionInfo() : null, mIsForward, mProfilerInfo, mAssistToken, null /* activityClientController */, - mShareableActivityToken, mLaunchedFromBubble, mTaskFragmentToken); + mShareableActivityToken, mLaunchedFromBubble, mTaskFragmentToken, + mInitialCallerInfoAccessToken); } } } diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index 952cdd986614..508c6b2bcbce 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -189,6 +189,7 @@ public class TransactionParcelTests { .setAssistToken(new Binder()) .setShareableActivityToken(new Binder()) .setTaskFragmentToken(new Binder()) + .setInitialCallerInfoAccessToken(new Binder()) .build(); writeAndPrepareForReading(item); diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java index a28bb69244eb..2bd563191134 100644 --- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java +++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java @@ -219,6 +219,22 @@ public class PerformanceHintManagerTest { workDuration.setActualGpuDurationNanos(6); s.reportActualWorkDuration(workDuration); } + { + WorkDuration workDuration = new WorkDuration(); + workDuration.setWorkPeriodStartTimestampNanos(1); + workDuration.setActualTotalDurationNanos(14); + workDuration.setActualCpuDurationNanos(0); + workDuration.setActualGpuDurationNanos(6); + s.reportActualWorkDuration(workDuration); + } + { + WorkDuration workDuration = new WorkDuration(); + workDuration.setWorkPeriodStartTimestampNanos(1); + workDuration.setActualTotalDurationNanos(14); + workDuration.setActualCpuDurationNanos(7); + workDuration.setActualGpuDurationNanos(0); + s.reportActualWorkDuration(workDuration); + } } @Test @@ -242,7 +258,7 @@ public class PerformanceHintManagerTest { s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6, 1)); }); assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6, 1)); + s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 0, 1)); }); assertThrows(IllegalArgumentException.class, () -> { s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1, 1)); diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index 52ff0d4037e8..a709d7be898b 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_DEFAULT; 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 @@ public class WindowOnBackInvokedDispatcherTest { } @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 @@ public class WindowOnBackInvokedDispatcherTest { 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/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java index 3e0e36d6f3b8..39cb616b1ed9 100644 --- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java +++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java @@ -353,7 +353,8 @@ public class ActivityThreadClientTest { null /* pendingResults */, null /* pendingNewIntents */, null /* activityOptions */, true /* isForward */, null /* profilerInfo */, mThread /* client */, null /* asssitToken */, null /* shareableActivityToken */, - false /* launchedFromBubble */, null /* taskfragmentToken */); + false /* launchedFromBubble */, null /* taskfragmentToken */, + null /* initialCallerInfoAccessToken */); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 50a58daa6363..a2a2914f969f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -19,6 +19,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.os.AsyncTask.Status.FINISHED; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import android.annotation.DimenRes; import android.annotation.Hide; @@ -47,6 +48,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; +import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.BubbleIconFactory; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; @@ -466,6 +468,7 @@ public class Bubble implements BubbleViewProvider { * Call when all the views should be removed/cleaned up. */ public void cleanupViews() { + ProtoLog.d(WM_SHELL_BUBBLES, "Bubble#cleanupViews=%s", getKey()); cleanupViews(true); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index a43a951b4574..1a6e48a6c852 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -23,7 +23,6 @@ import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED; @@ -435,6 +434,9 @@ public class BubbleController implements ConfigurationChangeListener, boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { for (Bubble b : mBubbleData.getBubbles()) { if (task.taskId == b.getTaskId()) { + ProtoLog.d(WM_SHELL_BUBBLES, + "onActivityRestartAttempt - taskId=%d selecting matching bubble=%s", + task.taskId, b.getKey()); mBubbleData.setSelectedBubble(b); mBubbleData.setExpanded(true); return; @@ -442,6 +444,9 @@ public class BubbleController implements ConfigurationChangeListener, } for (Bubble b : mBubbleData.getOverflowBubbles()) { if (task.taskId == b.getTaskId()) { + ProtoLog.d(WM_SHELL_BUBBLES, "onActivityRestartAttempt - taskId=%d " + + "selecting matching overflow bubble=%s", + task.taskId, b.getKey()); promoteBubbleFromOverflow(b); mBubbleData.setExpanded(true); return; @@ -581,10 +586,15 @@ public class BubbleController implements ConfigurationChangeListener, // Hide the stack temporarily if the status bar has been made invisible, and the stack // is collapsed. An expanded stack should remain visible until collapsed. mStackView.setTemporarilyInvisible(!visible && !isStackExpanded()); + ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarVisibilityChanged=%b stackExpanded=%b", + visible, isStackExpanded()); } } private void onZenStateChanged() { + if (hasBubbles()) { + ProtoLog.d(WM_SHELL_BUBBLES, "onZenStateChanged"); + } for (Bubble b : mBubbleData.getBubbles()) { b.setShowDot(b.showInShade()); } @@ -593,9 +603,10 @@ public class BubbleController implements ConfigurationChangeListener, @VisibleForTesting public void onStatusBarStateChanged(boolean isShade) { boolean didChange = mIsStatusBarShade != isShade; - if (DEBUG_BUBBLE_CONTROLLER) { - Log.d(TAG, "onStatusBarStateChanged isShade=" + isShade + " didChange=" + didChange); - } + ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarStateChanged " + + "isShade=%b didChange=%b mNotifEntryToExpandOnShadeUnlock=%s", + isShade, didChange, (mNotifEntryToExpandOnShadeUnlock != null + ? mNotifEntryToExpandOnShadeUnlock.getKey() : "null")); mIsStatusBarShade = isShade; if (!mIsStatusBarShade && didChange) { // Only collapse stack on change @@ -611,6 +622,8 @@ public class BubbleController implements ConfigurationChangeListener, @VisibleForTesting public void onBubbleMetadataFlagChanged(Bubble bubble) { + ProtoLog.d(WM_SHELL_BUBBLES, "onBubbleMetadataFlagChanged=%s flags=%d", + bubble.getKey(), bubble.getFlags()); // Make sure NoMan knows suppression state so that anyone querying it can tell. try { mBarService.onBubbleMetadataFlagChanged(bubble.getKey(), bubble.getFlags()); @@ -623,6 +636,8 @@ public class BubbleController implements ConfigurationChangeListener, /** Called when the current user changes. */ @VisibleForTesting public void onUserChanged(int newUserId) { + ProtoLog.d(WM_SHELL_BUBBLES, "onUserChanged currentUser=%d newUser=%d", + mCurrentUserId, newUserId); saveBubbles(mCurrentUserId); mCurrentUserId = newUserId; @@ -825,6 +840,7 @@ public class BubbleController implements ConfigurationChangeListener, */ void updateWindowFlagsForBackpress(boolean interceptBack) { if (mAddedToWindowManager) { + ProtoLog.d(WM_SHELL_BUBBLES, "updateFlagsForBackPress interceptBack=%b", interceptBack); mWmLayoutParams.flags = interceptBack ? 0 : WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE @@ -1014,8 +1030,9 @@ public class BubbleController implements ConfigurationChangeListener, } private void onNotificationPanelExpandedChanged(boolean expanded) { - ProtoLog.d(WM_SHELL_BUBBLES, "onNotificationPanelExpandedChanged: expanded=%b", expanded); if (mStackView != null && mStackView.isExpanded()) { + ProtoLog.d(WM_SHELL_BUBBLES, + "onNotificationPanelExpandedChanged expanded=%b", expanded); if (expanded) { mStackView.stopMonitoringSwipeUpGesture(); } else { @@ -1096,6 +1113,7 @@ public class BubbleController implements ConfigurationChangeListener, /** Promote the provided bubble from the overflow view. */ public void promoteBubbleFromOverflow(Bubble bubble) { mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK); + ProtoLog.d(WM_SHELL_BUBBLES, "promoteBubbleFromOverflow=%s", bubble.getKey()); bubble.setInflateSynchronously(mInflateSynchronously); bubble.setShouldAutoExpand(true); bubble.markAsAccessedAt(System.currentTimeMillis()); @@ -1211,11 +1229,8 @@ public class BubbleController implements ConfigurationChangeListener, // Skip update, but store it in user bubbles so it gets restored after user switch mSavedUserBubbleData.get(bubbleUserId, new UserBubbleData()).add(notif.getKey(), true /* shownInShade */); - if (DEBUG_BUBBLE_CONTROLLER) { - Log.d(TAG, - "Ignore update to bubble for not active user. Bubble userId=" + bubbleUserId - + " current userId=" + mCurrentUserId); - } + Log.w(TAG, "updateBubble, ignore update for non-active user=" + bubbleUserId + + " currentUser=" + mCurrentUserId); } } @@ -1769,18 +1784,19 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void applyUpdate(BubbleData.Update update) { - if (DEBUG_BUBBLE_CONTROLLER) { - Log.d(TAG, "applyUpdate:" + " bubbleAdded=" + (update.addedBubble != null) - + " bubbleRemoved=" - + (update.removedBubbles != null && update.removedBubbles.size() > 0) - + " bubbleUpdated=" + (update.updatedBubble != null) - + " orderChanged=" + update.orderChanged - + " expandedChanged=" + update.expandedChanged - + " selectionChanged=" + update.selectionChanged - + " suppressed=" + (update.suppressedBubble != null) - + " unsuppressed=" + (update.unsuppressedBubble != null) - + " shouldShowEducation=" + update.shouldShowEducation); - } + ProtoLog.d(WM_SHELL_BUBBLES, "mBubbleDataListener#applyUpdate:" + + " added=%s removed=%b updated=%s orderChanged=%b expansionChanged=%b" + + " expanded=%b selectionChanged=%b selected=%s" + + " suppressed=%s unsupressed=%s shouldShowEducation=%b", + update.addedBubble != null ? update.addedBubble.getKey() : "null", + update.removedBubbles.isEmpty(), + update.updatedBubble != null ? update.updatedBubble.getKey() : "null", + update.orderChanged, update.expandedChanged, update.expanded, + update.selectionChanged, + update.selectedBubble != null ? update.selectedBubble.getKey() : "null", + update.suppressedBubble != null ? update.suppressedBubble.getKey() : "null", + update.unsuppressedBubble != null ? update.unsuppressedBubble.getKey() : "null", + update.shouldShowEducation); ensureBubbleViewsAndWindowCreated(); @@ -1974,7 +1990,8 @@ public class BubbleController implements ConfigurationChangeListener, if (mStackView == null && mLayerView == null) { return; } - + ProtoLog.v(WM_SHELL_BUBBLES, "updateBubbleViews mIsStatusBarShade=%s hasBubbles=%s", + mIsStatusBarShade, hasBubbles()); if (!mIsStatusBarShade) { // Bubbles don't appear when the device is locked. if (mStackView != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index dbfa2606ea06..6d3f0c32bcbb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -16,10 +16,9 @@ package com.android.wm.shell.bubbles; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; -import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE; -import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import android.annotation.NonNull; import android.app.PendingIntent; @@ -36,6 +35,7 @@ import android.view.View; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.FrameworkStatsLog; import com.android.launcher3.icons.BubbleIconFactory; import com.android.wm.shell.R; @@ -333,9 +333,6 @@ public class BubbleData { } public void setExpanded(boolean expanded) { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "setExpanded: " + expanded); - } setExpandedInternal(expanded); dispatchPendingChanges(); } @@ -347,9 +344,8 @@ public class BubbleData { * updated to have the correct state. */ public void setSelectedBubbleFromLauncher(BubbleViewProvider bubble) { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "setSelectedBubbleFromLauncher: " + bubble); - } + ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleFromLauncher=%s", + (bubble != null ? bubble.getKey() : "null")); mExpanded = true; if (Objects.equals(bubble, mSelectedBubble)) { return; @@ -370,9 +366,6 @@ public class BubbleData { } public void setSelectedBubble(BubbleViewProvider bubble) { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "setSelectedBubble: " + bubble); - } setSelectedBubbleInternal(bubble); dispatchPendingChanges(); } @@ -430,12 +423,13 @@ public class BubbleData { * BubbleBarLayerView, BubbleIconFactory, boolean) */ void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "notificationEntryUpdated: " + bubble); - } mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey()); suppressFlyout |= !bubble.isTextChanged(); + ProtoLog.d(WM_SHELL_BUBBLES, + "notifEntryUpdated=%s prevBubble=%b suppressFlyout=%b showInShade=%b autoExpand=%b", + bubble.getKey(), (prevBubble != null), suppressFlyout, showInShade, + bubble.shouldAutoExpand()); if (prevBubble == null) { // Create a new bubble @@ -483,9 +477,6 @@ public class BubbleData { * Dismisses the bubble with the matching key, if it exists. */ public void dismissBubbleWithKey(String key, @DismissReason int reason) { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "notificationEntryRemoved: key=" + key + " reason=" + reason); - } doRemove(key, reason); dispatchPendingChanges(); } @@ -603,9 +594,7 @@ public class BubbleData { } private void doAdd(Bubble bubble) { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "doAdd: " + bubble); - } + ProtoLog.d(WM_SHELL_BUBBLES, "doAdd=%s", bubble.getKey()); mBubbles.add(0, bubble); mStateChange.addedBubble = bubble; // Adding the first bubble doesn't change the order @@ -634,9 +623,7 @@ public class BubbleData { } private void doUpdate(Bubble bubble, boolean reorder) { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "doUpdate: " + bubble); - } + ProtoLog.d(WM_SHELL_BUBBLES, "BubbleData - doUpdate=%s", bubble.getKey()); mStateChange.updatedBubble = bubble; if (!isExpanded() && reorder) { int prevPos = mBubbles.indexOf(bubble); @@ -663,9 +650,6 @@ public class BubbleData { } private void doRemove(String key, @DismissReason int reason) { - if (DEBUG_BUBBLE_DATA || (key != null && key.contains(KEY_APP_BUBBLE))) { - Log.d(TAG, "doRemove: " + key + " reason: " + reason); - } // If it was pending remove it if (mPendingBubbles.containsKey(key)) { mPendingBubbles.remove(key); @@ -686,9 +670,7 @@ public class BubbleData { && shouldRemoveHiddenBubble) { Bubble b = getOverflowBubbleWithKey(key); - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "Cancel overflow bubble: " + b); - } + ProtoLog.d(WM_SHELL_BUBBLES, "doRemove - cancel overflow bubble=%s", key); if (b != null) { b.stopInflation(); } @@ -699,9 +681,7 @@ public class BubbleData { } if (hasSuppressedBubbleWithKey(key) && shouldRemoveHiddenBubble) { Bubble b = getSuppressedBubbleWithKey(key); - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "Cancel suppressed bubble: " + b); - } + ProtoLog.d(WM_SHELL_BUBBLES, "doRemove - cancel suppressed bubble=%s", key); if (b != null) { mSuppressedBubbles.remove(b.getLocusId()); b.stopInflation(); @@ -711,6 +691,7 @@ public class BubbleData { return; } Bubble bubbleToRemove = mBubbles.get(indexToRemove); + ProtoLog.d(WM_SHELL_BUBBLES, "doRemove=%s", bubbleToRemove.getKey()); bubbleToRemove.stopInflation(); overflowBubble(reason, bubbleToRemove); @@ -744,17 +725,12 @@ public class BubbleData { } // Move selection to the new bubble at the same position. int newIndex = Math.min(indexOfSelected, mBubbles.size() - 1); - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "setNewSelectedIndex: " + indexOfSelected); - } BubbleViewProvider newSelected = mBubbles.get(newIndex); setSelectedBubbleInternal(newSelected); } private void doSuppress(Bubble bubble) { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "doSuppressed: " + bubble); - } + ProtoLog.d(WM_SHELL_BUBBLES, "doSuppress=%s", bubble.getKey()); mStateChange.suppressedBubble = bubble; bubble.setSuppressBubble(true); @@ -777,9 +753,7 @@ public class BubbleData { } private void doUnsuppress(Bubble bubble) { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "doUnsuppressed: " + bubble); - } + ProtoLog.d(WM_SHELL_BUBBLES, "doUnsuppress=%s", bubble.getKey()); bubble.setSuppressBubble(false); mStateChange.unsuppressedBubble = bubble; mBubbles.add(bubble); @@ -801,9 +775,7 @@ public class BubbleData { || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) { return; } - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "Overflowing: " + bubble); - } + ProtoLog.d(WM_SHELL_BUBBLES, "overflowBubble=%s", bubble.getKey()); mLogger.logOverflowAdd(bubble, reason); mOverflowBubbles.remove(bubble); mOverflowBubbles.add(0, bubble); @@ -812,9 +784,7 @@ public class BubbleData { if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) { // Remove oldest bubble. Bubble oldest = mOverflowBubbles.get(mOverflowBubbles.size() - 1); - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "Overflow full. Remove: " + oldest); - } + ProtoLog.d(WM_SHELL_BUBBLES, "overflow full, remove=%s", oldest.getKey()); mStateChange.bubbleRemoved(oldest, Bubbles.DISMISS_OVERFLOW_MAX_REACHED); mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_MAX_REACHED); mOverflowBubbles.remove(oldest); @@ -823,9 +793,7 @@ public class BubbleData { } public void dismissAll(@DismissReason int reason) { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "dismissAll: reason=" + reason); - } + ProtoLog.d(WM_SHELL_BUBBLES, "dismissAll reason=%d", reason); if (mBubbles.isEmpty() && mSuppressedBubbles.isEmpty()) { return; } @@ -851,9 +819,10 @@ public class BubbleData { * @param visible whether the task with the locusId is visible or not. */ public void onLocusVisibilityChanged(int taskId, LocusId locusId, boolean visible) { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "onLocusVisibilityChanged: " + locusId + " visible=" + visible); - } + if (locusId == null) return; + + ProtoLog.d(WM_SHELL_BUBBLES, "onLocusVisibilityChanged=%s visible=%b taskId=%d", + locusId.getId(), visible, taskId); Bubble matchingBubble = getBubbleInStackWithLocusId(locusId); // Don't add the locus if it's from a bubble'd activity, we only suppress for non-bubbled. @@ -910,9 +879,8 @@ public class BubbleData { * @param bubble the new selected bubble */ private void setSelectedBubbleInternal(@Nullable BubbleViewProvider bubble) { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "setSelectedBubbleInternal: " + bubble); - } + ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleInternal=%s", + (bubble != null ? bubble.getKey() : "null")); if (Objects.equals(bubble, mSelectedBubble)) { return; } @@ -969,12 +937,10 @@ public class BubbleData { * @param shouldExpand the new requested state */ private void setExpandedInternal(boolean shouldExpand) { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "setExpandedInternal: shouldExpand=" + shouldExpand); - } if (mExpanded == shouldExpand) { return; } + ProtoLog.d(WM_SHELL_BUBBLES, "setExpandedInternal=%b", shouldExpand); if (shouldExpand) { if (mBubbles.isEmpty() && !mShowingOverflow) { Log.e(TAG, "Attempt to expand stack when empty!"); @@ -1026,9 +992,6 @@ public class BubbleData { * @return true if the position of any bubbles changed as a result */ private boolean repackAll() { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "repackAll()"); - } if (mBubbles.isEmpty()) { return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java index f56b1712c5c1..f1a68e246fe1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java @@ -37,17 +37,7 @@ public class BubbleDebugConfig { // Default log tag for the Bubbles package. public static final String TAG_BUBBLES = "Bubbles"; - - static final boolean DEBUG_BUBBLE_CONTROLLER = false; - static final boolean DEBUG_BUBBLE_DATA = false; - static final boolean DEBUG_BUBBLE_STACK_VIEW = false; - static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false; - static final boolean DEBUG_EXPERIMENTS = true; - static final boolean DEBUG_OVERFLOW = false; public static final boolean DEBUG_USER_EDUCATION = false; - static final boolean DEBUG_POSITIONER = false; - public static final boolean DEBUG_COLLAPSE_ANIMATOR = false; - public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false; private static final boolean FORCE_SHOW_USER_EDUCATION = false; private static final String FORCE_SHOW_USER_EDUCATION_SETTING = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index efc4d8b95f4f..088660e4e8dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -23,10 +23,10 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import android.annotation.NonNull; import android.annotation.SuppressLint; @@ -67,6 +67,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.AlphaOptimizedButton; import com.android.wm.shell.common.TriangleShape; @@ -199,13 +200,9 @@ public class BubbleExpandedView extends LinearLayout { @Override public void onInitialized() { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "onInitialized: destroyed=" + mDestroyed - + " initialized=" + mInitialized - + " bubble=" + getBubbleKey()); - } - if (mDestroyed || mInitialized) { + ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: destroyed=%b initialized=%b bubble=%s", + mDestroyed, mInitialized, getBubbleKey()); return; } @@ -216,10 +213,8 @@ public class BubbleExpandedView extends LinearLayout { // TODO: I notice inconsistencies in lifecycle // Post to keep the lifecycle normal post(() -> { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "onInitialized: calling startActivity, bubble=" - + getBubbleKey()); - } + ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: calling startActivity, bubble=%s", + getBubbleKey()); try { Rect launchBounds = new Rect(); mTaskView.getBoundsOnScreen(launchBounds); @@ -279,10 +274,8 @@ public class BubbleExpandedView extends LinearLayout { @Override public void onTaskCreated(int taskId, ComponentName name) { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "onTaskCreated: taskId=" + taskId - + " bubble=" + getBubbleKey()); - } + ProtoLog.d(WM_SHELL_BUBBLES, "onTaskCreated: taskId=%d bubble=%s", + taskId, getBubbleKey()); // The taskId is saved to use for removeTask, preventing appearance in recent tasks. mTaskId = taskId; @@ -298,15 +291,15 @@ public class BubbleExpandedView extends LinearLayout { @Override public void onTaskVisibilityChanged(int taskId, boolean visible) { + ProtoLog.d(WM_SHELL_BUBBLES, "onTaskVisibilityChanged=%b bubble=%s taskId=%d", + visible, getBubbleKey(), taskId); setContentVisibility(visible); } @Override public void onTaskRemovalStarted(int taskId) { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId - + " bubble=" + getBubbleKey()); - } + ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s", + taskId, getBubbleKey()); if (mBubble != null) { mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED); } @@ -644,9 +637,6 @@ public class BubbleExpandedView extends LinearLayout { super.onDetachedFromWindow(); mImeVisible = false; mNeedsNewHeight = false; - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey()); - } } /** @@ -805,10 +795,6 @@ public class BubbleExpandedView extends LinearLayout { * and setting {@code false} actually means rendering the contents in transparent. */ public void setContentVisibility(boolean visibility) { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "setContentVisibility: visibility=" + visibility - + " bubble=" + getBubbleKey()); - } mIsContentVisible = visibility; if (mTaskView != null && !mIsAnimating) { mTaskView.setAlpha(visibility ? 1f : 0f); @@ -867,9 +853,6 @@ public class BubbleExpandedView extends LinearLayout { * Sets the bubble used to populate this view. */ void update(Bubble bubble) { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "update: bubble=" + bubble); - } if (mStackView == null) { Log.w(TAG, "Stack is null for bubble: " + bubble); return; @@ -958,11 +941,6 @@ public class BubbleExpandedView extends LinearLayout { } mNeedsNewHeight = false; } - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "updateHeight: bubble=" + getBubbleKey() - + " height=" + height - + " mNeedsNewHeight=" + mNeedsNewHeight); - } } } @@ -974,10 +952,6 @@ public class BubbleExpandedView extends LinearLayout { * waiting for layout. */ public void updateView(int[] containerLocationOnScreen) { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "updateView: bubble=" - + getBubbleKey()); - } mExpandedViewContainerLocation = containerLocationOnScreen; updateHeight(); if (mTaskView != null @@ -1103,9 +1077,6 @@ public class BubbleExpandedView extends LinearLayout { /** Hide the task view. */ public void cleanUpExpandedState() { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId); - } if (mTaskView != null) { mTaskView.setVisibility(GONE); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java index 9655470ce914..70cdc82c47ad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java @@ -16,9 +16,9 @@ package com.android.wm.shell.bubbles; -import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_OVERFLOW; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import android.annotation.NonNull; import android.content.Context; @@ -28,7 +28,6 @@ import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Rect; import android.util.AttributeSet; -import android.util.Log; import android.util.TypedValue; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -43,6 +42,7 @@ import androidx.annotation.Nullable; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ContrastColorUtil; import com.android.wm.shell.R; @@ -245,9 +245,6 @@ public class BubbleOverflowContainerView extends LinearLayout { Bubble toRemove = update.removedOverflowBubble; if (toRemove != null) { - if (DEBUG_OVERFLOW) { - Log.d(TAG, "remove: " + toRemove); - } toRemove.cleanupViews(); final int indexToRemove = mOverflowBubbles.indexOf(toRemove); mOverflowBubbles.remove(toRemove); @@ -257,9 +254,6 @@ public class BubbleOverflowContainerView extends LinearLayout { Bubble toAdd = update.addedOverflowBubble; if (toAdd != null) { final int indexToAdd = mOverflowBubbles.indexOf(toAdd); - if (DEBUG_OVERFLOW) { - Log.d(TAG, "add: " + toAdd + " prevIndex: " + indexToAdd); - } if (indexToAdd > 0) { mOverflowBubbles.remove(toAdd); mOverflowBubbles.add(0, toAdd); @@ -272,10 +266,9 @@ public class BubbleOverflowContainerView extends LinearLayout { updateEmptyStateVisibility(); - if (DEBUG_OVERFLOW) { - Log.d(TAG, BubbleDebugConfig.formatBubblesString( - mController.getOverflowBubbles(), null)); - } + ProtoLog.d(WM_SHELL_BUBBLES, "Apply overflow update, added=%s removed=%s", + (toAdd != null ? toAdd.getKey() : "null"), + (toRemove != null ? toRemove.getKey() : "null")); } }; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index d62c86ce386f..c03b6f805cf7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -16,18 +16,20 @@ package com.android.wm.shell.bubbles; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; + import android.content.Context; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; -import android.util.Log; import android.view.Surface; import android.view.WindowManager; import androidx.annotation.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconNormalizer; import com.android.wm.shell.R; @@ -36,9 +38,6 @@ import com.android.wm.shell.R; * placement and positioning calculations to refer to. */ public class BubblePositioner { - private static final String TAG = BubbleDebugConfig.TAG_WITH_CLASS_NAME - ? "BubblePositioner" - : BubbleDebugConfig.TAG_BUBBLES; /** The screen edge the bubble stack is pinned to */ public enum StackPinnedEdge { @@ -110,16 +109,12 @@ public class BubblePositioner { */ public void update(DeviceConfig deviceConfig) { mDeviceConfig = deviceConfig; - - if (BubbleDebugConfig.DEBUG_POSITIONER) { - Log.w(TAG, "update positioner:" - + " rotation: " + mRotation - + " insets: " + deviceConfig.getInsets() - + " isLargeScreen: " + deviceConfig.isLargeScreen() - + " isSmallTablet: " + deviceConfig.isSmallTablet() - + " showingInBubbleBar: " + mShowingInBubbleBar - + " bounds: " + deviceConfig.getWindowBounds()); - } + ProtoLog.d(WM_SHELL_BUBBLES, "update positioner: " + + "rotation=%d insets=%s largeScreen=%b " + + "smallTablet=%b isBubbleBar=%b bounds=%s", + mRotation, deviceConfig.getInsets(), deviceConfig.isLargeScreen(), + deviceConfig.isSmallTablet(), mShowingInBubbleBar, + deviceConfig.getWindowBounds()); updateInternal(mRotation, deviceConfig.getInsets(), deviceConfig.getWindowBounds()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 9facef36a74e..c877d4a2a019 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -21,7 +21,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.wm.shell.animation.Interpolators.ALPHA_IN; import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT; -import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING; @@ -1310,13 +1309,9 @@ public class BubbleStackView extends FrameLayout final boolean seen = getPrefBoolean(ManageEducationView.PREF_MANAGED_EDUCATION); final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext)) && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null; - if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { - Log.d(TAG, "Show manage edu: " + shouldShow); - } + ProtoLog.d(WM_SHELL_BUBBLES, "Show manage edu=%b", shouldShow); if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) { - if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { - Log.d(TAG, "Want to show manage edu, but it is forced hidden"); - } + Log.w(TAG, "Want to show manage edu, but it is forced hidden"); return false; } return shouldShow; @@ -1360,13 +1355,9 @@ public class BubbleStackView extends FrameLayout } final boolean seen = getPrefBoolean(StackEducationView.PREF_STACK_EDUCATION); final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext); - if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { - Log.d(TAG, "Show stack edu: " + shouldShow); - } + ProtoLog.d(WM_SHELL_BUBBLES, "Show stack edu=%b", shouldShow); if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) { - if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { - Log.d(TAG, "Want to show stack edu, but it is forced hidden"); - } + Log.w(TAG, "Want to show stack edu, but it is forced hidden"); return false; } return shouldShow; @@ -1513,7 +1504,7 @@ public class BubbleStackView extends FrameLayout mBubbleSize = mPositioner.getBubbleSize(); for (Bubble b : mBubbleData.getBubbles()) { if (b.getIconView() == null) { - Log.d(TAG, "Display size changed. Icon null: " + b); + Log.w(TAG, "Display size changed. Icon null: " + b); continue; } b.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize)); @@ -1819,10 +1810,6 @@ public class BubbleStackView extends FrameLayout // via BubbleData.Listener @SuppressLint("ClickableViewAccessibility") void addBubble(Bubble bubble) { - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "addBubble: " + bubble); - } - final boolean firstBubble = getBubbleCount() == 0; if (firstBubble && shouldShowStackEdu()) { @@ -1868,9 +1855,6 @@ public class BubbleStackView extends FrameLayout // via BubbleData.Listener void removeBubble(Bubble bubble) { - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "removeBubble: " + bubble); - } if (isExpanded() && getBubbleCount() == 1) { mRemovingLastBubbleWhileExpanded = true; // We're expanded while the last bubble is being removed. Let the scrim animate away @@ -1917,7 +1901,7 @@ public class BubbleStackView extends FrameLayout bubble.cleanupViews(); logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED); } else { - Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble); + Log.w(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble); } } @@ -1985,10 +1969,6 @@ public class BubbleStackView extends FrameLayout */ // via BubbleData.Listener public void setSelectedBubble(@Nullable BubbleViewProvider bubbleToSelect) { - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "setSelectedBubble: " + bubbleToSelect); - } - if (bubbleToSelect == null) { mBubbleData.setShowingOverflow(false); return; @@ -2081,10 +2061,6 @@ public class BubbleStackView extends FrameLayout */ // via BubbleData.Listener public void setExpanded(boolean shouldExpand) { - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "setExpanded: " + shouldExpand); - } - if (!shouldExpand) { // If we're collapsing, release the animating-out surface immediately since we have no // need for it, and this ensures it cannot remain visible as we collapse. @@ -2126,7 +2102,6 @@ public class BubbleStackView extends FrameLayout * Monitor for swipe up gesture that is used to collapse expanded view */ void startMonitoringSwipeUpGesture() { - ProtoLog.d(WM_SHELL_BUBBLES, "startMonitoringSwipeUpGesture"); stopMonitoringSwipeUpGestureInternal(); if (isGestureNavEnabled()) { @@ -2174,7 +2149,6 @@ public class BubbleStackView extends FrameLayout * Stop monitoring for swipe up gesture */ void stopMonitoringSwipeUpGesture() { - ProtoLog.d(WM_SHELL_BUBBLES, "stopMonitoringSwipeUpGesture"); stopMonitoringSwipeUpGestureInternal(); } @@ -2202,9 +2176,6 @@ public class BubbleStackView extends FrameLayout } void setBubbleSuppressed(Bubble bubble, boolean suppressed) { - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "setBubbleSuppressed: suppressed=" + suppressed + " bubble=" + bubble); - } if (suppressed) { int index = getBubbleIndex(bubble); mBubbleContainer.removeViewAt(index); @@ -2339,6 +2310,8 @@ public class BubbleStackView extends FrameLayout } private void animateExpansion() { + ProtoLog.d(WM_SHELL_BUBBLES, "animateExpansion, expandedBubble=%s", + mExpandedBubble != null ? mExpandedBubble.getKey() : "null"); cancelDelayedExpandCollapseSwitchAnimations(); final boolean showVertically = mPositioner.showBubblesVertically(); mIsExpanded = true; @@ -2465,7 +2438,7 @@ public class BubbleStackView extends FrameLayout private void animateCollapse() { cancelDelayedExpandCollapseSwitchAnimations(); - + ProtoLog.d(WM_SHELL_BUBBLES, "animateCollapse"); if (isManageEduVisible()) { mManageEduView.hide(); } @@ -2508,11 +2481,6 @@ public class BubbleStackView extends FrameLayout mManageEduView.hide(); } - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "animateCollapse"); - Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(), - mExpandedBubble)); - } updateZOrder(); updateBadges(true /* setBadgeForCollapsedStack */); afterExpandedViewAnimation(); @@ -2894,7 +2862,7 @@ public class BubbleStackView extends FrameLayout if (!shouldShowFlyout(bubble)) { return; } - + ProtoLog.d(WM_SHELL_BUBBLES, "animateFlyout=%s", bubble.getKey()); mFlyoutDragDeltaX = 0f; clearFlyoutOnHide(); mAfterFlyoutHidden = () -> { @@ -3036,6 +3004,9 @@ public class BubbleStackView extends FrameLayout @VisibleForTesting public void showManageMenu(boolean show) { if ((mManageMenu.getVisibility() == VISIBLE) == show) return; + ProtoLog.d(WM_SHELL_BUBBLES, "showManageMenu=%b for bubble=%s", + show, (mExpandedBubble != null ? mExpandedBubble.getKey() : "null")); + mShowingManage = show; // This should not happen, since the manage menu is only visible when there's an expanded @@ -3167,10 +3138,6 @@ public class BubbleStackView extends FrameLayout } private void updateExpandedBubble() { - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "updateExpandedBubble()"); - } - mExpandedViewContainer.removeAllViews(); if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { @@ -3318,9 +3285,6 @@ public class BubbleStackView extends FrameLayout } private void updateExpandedView() { - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "updateExpandedView: mIsExpanded=" + mIsExpanded); - } boolean isOverflowExpanded = mExpandedBubble != null && BubbleOverflow.KEY.equals(mExpandedBubble.getKey()); int[] paddings = mPositioner.getExpandedViewContainerPadding( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index dc271331c8f9..530ec5a6c9fe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -20,7 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; -import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import android.app.ActivityOptions; import android.app.ActivityTaskManager; @@ -35,6 +35,7 @@ import android.view.ViewGroup; import androidx.annotation.Nullable; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.taskview.TaskView; /** @@ -79,11 +80,8 @@ public class BubbleTaskViewHelper { @Override public void onInitialized() { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "onInitialized: destroyed=" + mDestroyed - + " initialized=" + mInitialized - + " bubble=" + getBubbleKey()); - } + ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: destroyed=%b initialized=%b bubble=%s", + mDestroyed, mInitialized, getBubbleKey()); if (mDestroyed || mInitialized) { return; @@ -99,10 +97,8 @@ public class BubbleTaskViewHelper { // TODO: I notice inconsistencies in lifecycle // Post to keep the lifecycle normal mParentView.post(() -> { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "onInitialized: calling startActivity, bubble=" - + getBubbleKey()); - } + ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: calling startActivity, bubble=%s", + getBubbleKey()); try { options.setTaskAlwaysOnTop(true); options.setLaunchedFromBubble(true); @@ -159,10 +155,8 @@ public class BubbleTaskViewHelper { @Override public void onTaskCreated(int taskId, ComponentName name) { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "onTaskCreated: taskId=" + taskId - + " bubble=" + getBubbleKey()); - } + ProtoLog.d(WM_SHELL_BUBBLES, "onTaskCreated: taskId=%d bubble=%s", + taskId, getBubbleKey()); // The taskId is saved to use for removeTask, preventing appearance in recent tasks. mTaskId = taskId; @@ -178,10 +172,8 @@ public class BubbleTaskViewHelper { @Override public void onTaskRemovalStarted(int taskId) { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId - + " bubble=" + getBubbleKey()); - } + ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s", + taskId, getBubbleKey()); if (mBubble != null) { mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java index 845dca34b41f..e43609fe8ff0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java @@ -15,14 +15,11 @@ */ package com.android.wm.shell.bubbles.animation; -import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_COLLAPSE_ANIMATOR; -import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_EXPANDED_VIEW_DRAGGING; -import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; -import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.wm.shell.bubbles.BubbleExpandedView.BACKGROUND_ALPHA; import static com.android.wm.shell.bubbles.BubbleExpandedView.BOTTOM_CLIP_PROPERTY; import static com.android.wm.shell.bubbles.BubbleExpandedView.CONTENT_ALPHA; import static com.android.wm.shell.bubbles.BubbleExpandedView.MANAGE_BUTTON_ALPHA; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -31,7 +28,6 @@ import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.content.Context; -import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.ViewConfiguration; @@ -41,6 +37,7 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat; import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.animation.FlingAnimationUtils; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.bubbles.BubbleExpandedView; @@ -55,8 +52,6 @@ import java.util.List; */ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimationController { - private static final String TAG = TAG_WITH_CLASS_NAME ? "ExpandedViewAnimCtrl" : TAG_BUBBLES; - private static final float COLLAPSE_THRESHOLD = 0.02f; private static final int COLLAPSE_DURATION_MS = 250; @@ -121,9 +116,6 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio @Override public void setExpandedView(BubbleExpandedView expandedView) { if (mExpandedView != null) { - if (DEBUG_COLLAPSE_ANIMATOR) { - Log.d(TAG, "updating expandedView, resetting previous"); - } if (mCollapseAnimation != null) { mCollapseAnimation.cancel(); } @@ -140,17 +132,14 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio if (mExpandedView != null) { mDraggedAmount = OverScroll.dampedScroll(distance, mExpandedView.getContentHeight()); - if (DEBUG_COLLAPSE_ANIMATOR && DEBUG_EXPANDED_VIEW_DRAGGING) { - Log.d(TAG, "updateDrag: distance=" + distance + " dragged=" + mDraggedAmount); - } + ProtoLog.d(WM_SHELL_BUBBLES, + "updateDrag: distance=%f dragged=%d", distance, mDraggedAmount); setCollapsedAmount(mDraggedAmount); if (!mNotifiedAboutThreshold && isPastCollapseThreshold()) { mNotifiedAboutThreshold = true; - if (DEBUG_COLLAPSE_ANIMATOR) { - Log.d(TAG, "notifying over collapse threshold"); - } + ProtoLog.d(WM_SHELL_BUBBLES, "notifying over collapse threshold"); vibrateIfEnabled(); } } @@ -172,45 +161,35 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio if (mSwipeDownVelocity > mMinFlingVelocity) { // Swipe velocity is positive and over fling velocity. // This is a swipe down, always reset to expanded state, regardless of dragged amount. - if (DEBUG_COLLAPSE_ANIMATOR) { - Log.d(TAG, - "not collapsing expanded view, swipe down velocity: " + mSwipeDownVelocity - + " minV: " + mMinFlingVelocity); - } + ProtoLog.d(WM_SHELL_BUBBLES, + "not collapsing expanded view, swipe down velocity=%f minV=%d", + mSwipeDownVelocity, mMinFlingVelocity); return false; } if (mSwipeUpVelocity > mMinFlingVelocity) { // Swiping up and over fling velocity, collapse the view. - if (DEBUG_COLLAPSE_ANIMATOR) { - Log.d(TAG, - "collapse expanded view, swipe up velocity: " + mSwipeUpVelocity + " minV: " - + mMinFlingVelocity); - } + ProtoLog.d(WM_SHELL_BUBBLES, + "collapse expanded view, swipe up velocity=%f minV=%d", + mSwipeUpVelocity, mMinFlingVelocity); return true; } if (isPastCollapseThreshold()) { - if (DEBUG_COLLAPSE_ANIMATOR) { - Log.d(TAG, "collapse expanded view, past threshold, dragged: " + mDraggedAmount); - } + ProtoLog.d(WM_SHELL_BUBBLES, + "collapse expanded view, past threshold, dragged=%d", mDraggedAmount); return true; } - if (DEBUG_COLLAPSE_ANIMATOR) { - Log.d(TAG, "not collapsing expanded view"); - } + ProtoLog.d(WM_SHELL_BUBBLES, "not collapsing expanded view"); return false; } @Override public void animateCollapse(Runnable startStackCollapse, Runnable after) { - if (DEBUG_COLLAPSE_ANIMATOR) { - Log.d(TAG, - "expandedView animate collapse swipeVel=" + mSwipeUpVelocity + " minFlingVel=" - + mMinFlingVelocity); - } + ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d", + mSwipeUpVelocity, mMinFlingVelocity); if (mExpandedView != null) { // Mark it as animating immediately to avoid updates to the view before animation starts mExpandedView.setAnimating(true); @@ -243,9 +222,7 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio @Override public void animateBackToExpanded() { - if (DEBUG_COLLAPSE_ANIMATOR) { - Log.d(TAG, "expandedView animate back to expanded"); - } + ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate back to expanded"); BubbleExpandedView expandedView = mExpandedView; if (expandedView == null) { return; @@ -298,9 +275,7 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio @Override public void reset() { - if (DEBUG_COLLAPSE_ANIMATOR) { - Log.d(TAG, "reset expandedView collapsed state"); - } + ProtoLog.d(WM_SHELL_BUBBLES, "reset expandedView collapsed state"); if (mExpandedView == null) { return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java index e86b62dee86d..6325c686a682 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java @@ -390,7 +390,7 @@ public class SplashScreenExitAnimationUtils { SurfaceControl firstWindowSurface, ViewGroup splashScreenView, TransactionPool transactionPool, Rect firstWindowFrame, int mainWindowShiftLength, float roundedCornerRadius) { - mFromYDelta = fromYDelta - roundedCornerRadius; + mFromYDelta = fromYDelta - Math.max(firstWindowFrame.top, roundedCornerRadius); mToYDelta = toYDelta; mOccludeHoleView = occludeHoleView; mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java index 0c25f27854bd..b3e8bd9bd23c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java @@ -19,16 +19,15 @@ package com.android.wm.shell.transition.tracing; import android.internal.perfetto.protos.PerfettoTrace; import android.os.SystemClock; import android.os.Trace; -import android.tracing.perfetto.DataSourceInstance; import android.tracing.perfetto.DataSourceParams; import android.tracing.perfetto.InitArguments; import android.tracing.perfetto.Producer; -import android.tracing.perfetto.TracingContext; import android.tracing.transition.TransitionDataSource; import android.util.proto.ProtoOutputStream; import com.android.wm.shell.transition.Transitions; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -41,6 +40,7 @@ public class PerfettoTransitionTracer implements TransitionTracer { mActiveTraces::incrementAndGet, this::onFlush, mActiveTraces::decrementAndGet); + private final Map<String, Integer> mHandlerMapping = new HashMap<>(); public PerfettoTransitionTracer() { Producer.init(InitArguments.DEFAULTS); @@ -69,7 +69,7 @@ public class PerfettoTransitionTracer implements TransitionTracer { private void doLogDispatched(int transitionId, Transitions.TransitionHandler handler) { mDataSource.trace(ctx -> { - final int handlerId = getHandlerId(handler, ctx); + final int handlerId = getHandlerId(handler); final ProtoOutputStream os = ctx.newTracePacket(); final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION); @@ -81,17 +81,16 @@ public class PerfettoTransitionTracer implements TransitionTracer { }); } - private static int getHandlerId(Transitions.TransitionHandler handler, - TracingContext<DataSourceInstance, TransitionDataSource.TlsState, Void> ctx) { - final Map<String, Integer> handlerMapping = - ctx.getCustomTlsState().handlerMapping; + private int getHandlerId(Transitions.TransitionHandler handler) { final int handlerId; - if (handlerMapping.containsKey(handler.getClass().getName())) { - handlerId = handlerMapping.get(handler.getClass().getName()); - } else { - // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto - handlerId = handlerMapping.size() + 1; - handlerMapping.put(handler.getClass().getName(), handlerId); + synchronized (mHandlerMapping) { + if (mHandlerMapping.containsKey(handler.getClass().getName())) { + handlerId = mHandlerMapping.get(handler.getClass().getName()); + } else { + // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto + handlerId = mHandlerMapping.size() + 1; + mHandlerMapping.put(handler.getClass().getName(), handlerId); + } } return handlerId; } @@ -194,22 +193,14 @@ public class PerfettoTransitionTracer implements TransitionTracer { } private void onFlush() { - Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "onFlush"); - try { - doOnFlush(); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); - } - } - - private void doOnFlush() { mDataSource.trace(ctx -> { final ProtoOutputStream os = ctx.newTracePacket(); - final Map<String, Integer> handlerMapping = ctx.getCustomTlsState().handlerMapping; - for (String handler : handlerMapping.keySet()) { + for (Map.Entry<String, Integer> entry : mHandlerMapping.entrySet()) { + final String handler = entry.getKey(); + final int handlerId = entry.getValue(); final long token = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS); - os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerMapping.get(handler)); + os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerId); os.write(PerfettoTrace.ShellHandlerMapping.NAME, handler); os.end(token); } diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index c5729444507e..279f9d682b9f 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -484,8 +484,9 @@ int APerformanceHint_reportActualWorkDuration2(APerformanceHintSession* session, WorkDuration& workDuration = *static_cast<WorkDuration*>(workDurationPtr); VALIDATE_INT(workDuration.workPeriodStartTimestampNanos, > 0) VALIDATE_INT(workDuration.actualTotalDurationNanos, > 0) - VALIDATE_INT(workDuration.actualCpuDurationNanos, > 0) + VALIDATE_INT(workDuration.actualCpuDurationNanos, >= 0) VALIDATE_INT(workDuration.actualGpuDurationNanos, >= 0) + VALIDATE_INT(workDuration.actualGpuDurationNanos + workDuration.actualCpuDurationNanos, > 0) return session->reportActualWorkDuration(workDurationPtr); } @@ -517,7 +518,7 @@ void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* aWorkDuration, void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* aWorkDuration, int64_t actualCpuDurationNanos) { VALIDATE_PTR(aWorkDuration) - WARN_INT(actualCpuDurationNanos, > 0) + WARN_INT(actualCpuDurationNanos, >= 0) static_cast<WorkDuration*>(aWorkDuration)->actualCpuDurationNanos = actualCpuDurationNanos; } diff --git a/nfc/api/current.txt b/nfc/api/current.txt index 9f94ef924eae..28cf250c40be 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -73,6 +73,7 @@ package android.nfc { 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 293e5d19e77c..c444740a5b1b 100644 --- a/nfc/java/android/nfc/INfcAdapter.aidl +++ b/nfc/java/android/nfc/INfcAdapter.aidl @@ -88,6 +88,7 @@ interface INfcAdapter @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 3d749801cb0a..782af5f3fe19 100644 --- a/nfc/java/android/nfc/NfcAdapter.java +++ b/nfc/java/android/nfc/NfcAdapter.java @@ -1208,6 +1208,22 @@ public final class NfcAdapter { } /** + * 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 426c5aac487b..3254a394c25a 100644 --- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -105,6 +105,8 @@ public final class ApduServiceInfo implements Parcelable { 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 @@ public final class ApduServiceInfo implements Parcelable { 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 @@ public final class ApduServiceInfo implements Parcelable { 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 @@ public final class ApduServiceInfo implements Parcelable { 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 @@ public final class ApduServiceInfo implements Parcelable { } /** + * 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 @@ public final class ApduServiceInfo implements Parcelable { } /** + * 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/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index 09e0d61010f7..bf69d3ba7603 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -45,11 +45,9 @@ <activity android:name=".v2.ui.InstallLaunch" android:configChanges="orientation|keyboardHidden|screenSize" - android:theme="@style/Theme.AlertDialogActivity" android:exported="false"/> <activity android:name=".InstallStart" - android:theme="@style/Theme.AlertDialogActivity" android:exported="true" android:excludeFromRecents="true"> <intent-filter android:priority="1"> @@ -79,14 +77,12 @@ android:exported="false" /> <activity android:name=".DeleteStagedFileOnResult" - android:theme="@style/Theme.AlertDialogActivity.NoActionBar" android:exported="false" /> <activity android:name=".PackageInstallerActivity" android:exported="false" /> <activity android:name=".InstallInstalling" - android:theme="@style/Theme.AlertDialogActivity.NoAnimation" android:exported="false" /> <receiver android:name=".common.InstallEventReceiver" @@ -98,16 +94,13 @@ </receiver> <activity android:name=".InstallSuccess" - android:theme="@style/Theme.AlertDialogActivity.NoAnimation" android:exported="false" /> <activity android:name=".InstallFailed" - android:theme="@style/Theme.AlertDialogActivity.NoAnimation" android:exported="false" /> <activity android:name=".UninstallerActivity" android:configChanges="orientation|keyboardHidden|screenSize" - android:theme="@style/Theme.AlertDialogActivity.NoActionBar" android:excludeFromRecents="true" android:noHistory="true" android:exported="true"> @@ -121,7 +114,6 @@ <activity android:name=".v2.ui.UninstallLaunch" android:configChanges="orientation|keyboardHidden|screenSize" - android:theme="@style/Theme.AlertDialogActivity.NoActionBar" android:excludeFromRecents="true" android:noHistory="true" android:exported="false"> @@ -144,7 +136,6 @@ </receiver> <activity android:name=".UninstallUninstalling" - android:theme="@style/Theme.AlertDialogActivity.NoActionBar" android:excludeFromRecents="true" android:exported="false" /> @@ -171,7 +162,6 @@ <activity android:name=".UnarchiveActivity" android:configChanges="orientation|keyboardHidden|screenSize" - android:theme="@style/Theme.AlertDialogActivity.NoActionBar" android:excludeFromRecents="true" android:noHistory="true" android:exported="true"> @@ -183,7 +173,6 @@ <activity android:name=".UnarchiveErrorActivity" android:configChanges="orientation|keyboardHidden|screenSize" - android:theme="@style/Theme.AlertDialogActivity.NoActionBar" android:excludeFromRecents="true" android:noHistory="true" android:exported="true"> diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml index 9a062295608d..f5af510111cd 100644 --- a/packages/PackageInstaller/res/values/themes.xml +++ b/packages/PackageInstaller/res/values/themes.xml @@ -17,19 +17,12 @@ <resources> - <style name="Theme.AlertDialogActivity.NoAnimation" - parent="@style/Theme.AlertDialogActivity.NoActionBar"> - <item name="android:windowAnimationStyle">@null</item> - </style> - <style name="Theme.AlertDialogActivity" parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert"> <item name="alertDialogStyle">@style/AlertDialog</item> - </style> - - <style name="Theme.AlertDialogActivity.NoActionBar"> <item name="android:windowActionBar">false</item> <item name="android:windowNoTitle">true</item> + <item name="android:windowAnimationStyle">@null</item> </style> </resources> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 1e146a51e21e..e4a762ae8118 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1113,6 +1113,10 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, GlobalSettingsProto.Notification.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS); + dumpSetting(s, p, + Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, + GlobalSettingsProto.Notification + .DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS); p.end(notificationToken); dumpSetting(s, p, diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 16de478d99c1..b58187d8e95e 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -185,6 +185,7 @@ public class SettingsBackupTest { Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH, Settings.Global.DEVICE_DEMO_MODE, Settings.Global.DEVICE_IDLE_CONSTANTS, + Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, Settings.Global.DISABLE_WINDOW_BLURS, Settings.Global.BATTERY_SAVER_CONSTANTS, Settings.Global.BATTERY_TIP_CONSTANTS, diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 0050676ace84..f877d7a89b92 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -475,6 +475,9 @@ <service android:name=".screenrecord.RecordingService" android:foregroundServiceType="systemExempted"/> + <service android:name=".recordissue.IssueRecordingService" + android:foregroundServiceType="systemExempted"/> + <receiver android:name=".SysuiRestartReceiver" android:exported="false"> <intent-filter> @@ -992,7 +995,6 @@ android:theme="@style/Theme.EditWidgetsActivity" android:excludeFromRecents="true" android:autoRemoveFromRecents="true" - android:showOnLockScreen="true" android:launchMode="singleTop" android:exported="false"> </activity> diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 6e4a4ea70959..2ad7192a3248 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -168,6 +168,9 @@ flag { "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/core/src/com/android/compose/ui/graphics/painter/DrawablePainter.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/painter/DrawablePainter.kt new file mode 100644 index 000000000000..846abf7e30bd --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/painter/DrawablePainter.kt @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.ui.graphics.painter + +import android.graphics.drawable.Animatable +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.view.View +import androidx.compose.runtime.Composable +import androidx.compose.runtime.RememberObserver +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.asAndroidColorFilter +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.graphics.painter.ColorPainter +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.withSave +import androidx.compose.ui.unit.LayoutDirection +import kotlin.math.roundToInt + +/** + * ************************************************************************************************* + * This file was forked from + * https://github.com/google/accompanist/blob/main/drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt + */ +private val MAIN_HANDLER by lazy(LazyThreadSafetyMode.NONE) { Handler(Looper.getMainLooper()) } + +/** + * A [Painter] which draws an Android [Drawable] and supports [Animatable] drawables. Instances + * should be remembered to be able to start and stop [Animatable] animations. + * + * Instances are usually retrieved from [rememberDrawablePainter]. + */ +public class DrawablePainter(public val drawable: Drawable) : Painter(), RememberObserver { + private var drawInvalidateTick by mutableStateOf(0) + private var drawableIntrinsicSize by mutableStateOf(drawable.intrinsicSize) + + private val callback: Drawable.Callback by lazy { + object : Drawable.Callback { + override fun invalidateDrawable(d: Drawable) { + // Update the tick so that we get re-drawn + drawInvalidateTick++ + // Update our intrinsic size too + drawableIntrinsicSize = drawable.intrinsicSize + } + + override fun scheduleDrawable(d: Drawable, what: Runnable, time: Long) { + MAIN_HANDLER.postAtTime(what, time) + } + + override fun unscheduleDrawable(d: Drawable, what: Runnable) { + MAIN_HANDLER.removeCallbacks(what) + } + } + } + + init { + if (drawable.intrinsicWidth >= 0 && drawable.intrinsicHeight >= 0) { + // Update the drawable's bounds to match the intrinsic size + drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight) + } + } + + override fun onRemembered() { + drawable.callback = callback + drawable.setVisible(true, true) + if (drawable is Animatable) drawable.start() + } + + override fun onAbandoned(): Unit = onForgotten() + + override fun onForgotten() { + if (drawable is Animatable) drawable.stop() + drawable.setVisible(false, false) + drawable.callback = null + } + + override fun applyAlpha(alpha: Float): Boolean { + drawable.alpha = (alpha * 255).roundToInt().coerceIn(0, 255) + return true + } + + override fun applyColorFilter(colorFilter: ColorFilter?): Boolean { + drawable.colorFilter = colorFilter?.asAndroidColorFilter() + return true + } + + override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean { + if (Build.VERSION.SDK_INT >= 23) { + return drawable.setLayoutDirection( + when (layoutDirection) { + LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR + LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL + } + ) + } + return false + } + + override val intrinsicSize: Size + get() = drawableIntrinsicSize + + override fun DrawScope.onDraw() { + drawIntoCanvas { canvas -> + // Reading this ensures that we invalidate when invalidateDrawable() is called + drawInvalidateTick + + // Update the Drawable's bounds + drawable.setBounds(0, 0, size.width.roundToInt(), size.height.roundToInt()) + + canvas.withSave { drawable.draw(canvas.nativeCanvas) } + } + } +} + +/** + * Remembers [Drawable] wrapped up as a [Painter]. This function attempts to un-wrap the drawable + * contents and use Compose primitives where possible. + * + * If the provided [drawable] is `null`, an empty no-op painter is returned. + * + * This function tries to dispatch lifecycle events to [drawable] as much as possible from within + * Compose. + * + * @sample com.google.accompanist.sample.drawablepainter.BasicSample + */ +@Composable +public fun rememberDrawablePainter(drawable: Drawable?): Painter = + remember(drawable) { + when (drawable) { + null -> EmptyPainter + is ColorDrawable -> ColorPainter(Color(drawable.color)) + // Since the DrawablePainter will be remembered and it implements RememberObserver, it + // will receive the necessary events + else -> DrawablePainter(drawable.mutate()) + } + } + +private val Drawable.intrinsicSize: Size + get() = + when { + // Only return a finite size if the drawable has an intrinsic size + intrinsicWidth >= 0 && intrinsicHeight >= 0 -> { + Size(width = intrinsicWidth.toFloat(), height = intrinsicHeight.toFloat()) + } + else -> Size.Unspecified + } + +internal object EmptyPainter : Painter() { + override val intrinsicSize: Size + get() = Size.Unspecified + override fun DrawScope.onDraw() {} +} 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 4cc7332aa8a9..51d2a03342b7 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.compose.ui.unit.dp 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 @@ object ComposeFacade : BaseComposeFacade { 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 index ed393c0a9cb5..76bd4ec2778a 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.CompositionLocalProvider 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 0e08a198c71e..3fb825471c57 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.graphics.graphicsLayer 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.ElementKey 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 @@ object Notifications { 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 @@ fun SceneScope.NotificationScrollingStack( 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 @@ fun SceneScope.NotificationScrollingStack( .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 @@ private fun SceneScope.NotificationPlaceholder( .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 SceneScope.NotificationPlaceholder( } } +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 5531f9cc5589..969dec31971c 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.height 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.getValue 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.battery.BatteryMeterViewController 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 @@ private fun SceneScope.QuickSettingsScene( 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 @@ private fun SceneScope.QuickSettingsScene( // 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 @@ private fun SceneScope.QuickSettingsScene( } } } - // 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 770d654a4c88..736ee1f235c6 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 @@ constructor( 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 0c2c5195becc..1223ace6957b 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 @@ package com.android.systemui.scene.ui.composable.transitions 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 @@ fun TransitionBuilder.goneToShadeTransition() { 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 ebc343dc6d76..2d5cf5c752bf 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 @@ import com.android.systemui.shade.ui.composable.Shade 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 497fe873e87d..677df7ead2da 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.platform.LocalDensity 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 Shade { 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 @@ private fun SceneScope.ShadeScene( 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 @@ private fun SceneScope.ShadeScene( 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 index 84b2c4bfed34..53b262bd29a1 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.content.res.Configuration 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 com.google.common.truth.Truth.assertThat 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 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, - dc, + sysUICutout, screenBounds, sbHeightLandscape, minLeftPadding, @@ -161,7 +165,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { } @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 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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,224 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { var bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, - dc, + 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_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, @@ -208,7 +429,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, - dc, + sysUICutout, screenBounds, sbHeightLandscape, minLeftPadding, @@ -231,7 +452,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, - dc, + sysUICutout, screenBounds, sbHeightPortrait, minLeftPadding, @@ -253,7 +474,118 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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_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 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { var bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, - dc, + sysUICutout = sysUICutout, screenBounds, sbHeightPortrait, minLeftPadding, @@ -364,7 +701,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, - dc, + sysUICutout = sysUICutout, screenBounds, sbHeightLandscape, minLeftPadding, @@ -385,7 +722,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, - dc, + sysUICutout = sysUICutout, screenBounds, sbHeightPortrait, minLeftPadding, @@ -406,7 +743,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, - dc, + sysUICutout = sysUICutout, screenBounds, sbHeightLandscape, minLeftPadding, @@ -528,7 +865,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { val bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, - dc, + sysUICutout, screenBounds, sbHeightPortrait, minLeftPadding, @@ -557,7 +894,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { // 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 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { @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/color/brightness_slider_track.xml b/packages/SystemUI/res/color/brightness_slider_track.xml new file mode 100644 index 000000000000..6028769f3789 --- /dev/null +++ b/packages/SystemUI/res/color/brightness_slider_track.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral2_500" android:lStar="40" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml index 95c7778c0e76..cae9d6b0513e 100644 --- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml +++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml @@ -24,7 +24,7 @@ <shape> <size android:height="@dimen/rounded_slider_track_width" /> <corners android:radius="@dimen/rounded_slider_track_corner_radius" /> - <solid android:color="?attr/shadeInactive" /> + <solid android:color="@color/brightness_slider_track" /> </shape> </inset> </item> diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml index 6e541a7a5f32..ce09385eaf45 100644 --- a/packages/SystemUI/res/layout/notification_snooze.xml +++ b/packages/SystemUI/res/layout/notification_snooze.xml @@ -49,7 +49,7 @@ android:layout_centerVertical="true" android:paddingTop="1dp" android:importantForAccessibility="yes" - android:tint="#9E9E9E" /> + android:tint="?android:attr/textColorPrimary"/> <TextView android:id="@+id/undo" diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 7a83070d1806..65c69f78e9d0 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 33bdca38074e..51012a4f3a21 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/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 8971859256e5..f0cbe7af4a89 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -305,6 +305,27 @@ <!-- A toast message shown when the screen recording cannot be started due to a generic error [CHAR LIMIT=NONE] --> <string name="screenrecord_start_error">Error starting screen recording</string> + <!-- Notification title displayed for issue recording [CHAR LIMIT=50]--> + <string name="issuerecord_title">Issue Recorder</string> + <!-- Processing issue recoding data in the background [CHAR LIMIT=30]--> + <string name="issuerecord_background_processing_label">Processing issue recording</string> + <!-- Description of the screen recording notification channel [CHAR LIMIT=NONE]--> + <string name="issuerecord_channel_description">Ongoing notification for an issue collection session</string> + + <!-- Notification text displayed when we are recording the screen [CHAR LIMIT=100]--> + <string name="issuerecord_ongoing_screen_only">Recording issue</string> + <!-- Label for notification action to share issue recording [CHAR LIMIT=35] --> + <string name="issuerecord_share_label">Share</string> + <!-- A toast message shown after successfully canceling a issue recording [CHAR LIMIT=NONE] --> + <!-- Notification text shown after saving a issue recording [CHAR LIMIT=100] --> + <string name="issuerecord_save_title">Issue recording saved</string> + <!-- Subtext for a notification shown after saving a issue recording to prompt the user to view it [CHAR_LIMIT=100] --> + <string name="issuerecord_save_text">Tap to view</string> + <!-- A toast message shown when there is an error saving a issue recording [CHAR LIMIT=NONE] --> + <string name="issuerecord_save_error">Error saving issue recording</string> + <!-- A toast message shown when the issue recording cannot be started due to a generic error [CHAR LIMIT=NONE] --> + <string name="issuerecord_start_error">Error starting issue recording</string> + <!-- Cling help message title when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] --> <string name="immersive_cling_title">Viewing full screen</string> <!-- Cling help message description when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] --> diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt index bbab4ded3fa2..6314bd9a5615 100644 --- a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt @@ -24,4 +24,5 @@ data class CameraProtectionInfo( 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 8fe9389b7b1d..6cee28be78f1 100644 --- a/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt +++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt @@ -25,15 +25,21 @@ import com.android.systemui.res.R import javax.inject.Inject import kotlin.math.roundToInt -class CameraProtectionLoader @Inject constructor(private val context: Context) { +interface CameraProtectionLoader { + fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> +} + +class CameraProtectionLoaderImpl @Inject constructor(private val context: Context) : + CameraProtectionLoader { - fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> { + 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 @@ class CameraProtectionLoader @Inject constructor(private val context: Context) { 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 @@ class CameraProtectionLoader @Inject constructor(private val context: Context) { 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 @@ class CameraProtectionLoader @Inject constructor(private val context: Context) { 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 000000000000..58680a88d670 --- /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 000000000000..fc0b97ea7013 --- /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 000000000000..aad934124dfb --- /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/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java index 92eeace648ad..904d5898fcf8 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java @@ -23,6 +23,7 @@ import com.android.systemui.doze.DozeService; import com.android.systemui.dreams.DreamOverlayService; import com.android.systemui.dump.SystemUIAuxiliaryDumpService; import com.android.systemui.keyguard.KeyguardService; +import com.android.systemui.recordissue.IssueRecordingService; import com.android.systemui.screenrecord.RecordingService; import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins; import com.android.systemui.wallpapers.ImageWallpaper; @@ -85,4 +86,11 @@ public abstract class DefaultServiceBinder { @IntoMap @ClassKey(RecordingService.class) public abstract Service bindRecordingService(RecordingService service); + + /** Inject into IssueRecordingService */ + @Binds + @IntoMap + @ClassKey(IssueRecordingService.class) + public abstract Service bindIssueRecordingService(IssueRecordingService service); + } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index efcbd47b67b4..28fd9a994f8a 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.ClockRegistryModule; 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 @@ import javax.inject.Named; BouncerInteractorModule.class, BouncerRepositoryModule.class, BouncerViewModule.class, + CameraProtectionModule.class, ClipboardOverlayModule.class, ClockRegistryModule.class, CommunalModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt index 16dfc2130402..47df021eaf83 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt @@ -32,6 +32,7 @@ import android.util.MathUtils.lerpInv import android.util.MathUtils.lerpInvSat import androidx.annotation.VisibleForTesting import com.android.app.animation.Interpolators +import com.android.app.tracing.traceSection import com.android.internal.graphics.ColorUtils import kotlin.math.abs import kotlin.math.cos @@ -127,6 +128,10 @@ class SquigglyProgress : Drawable() { } override fun draw(canvas: Canvas) { + traceSection("SquigglyProgress#draw") { drawTraced(canvas) } + } + + private fun drawTraced(canvas: Canvas) { if (animate) { invalidateSelf() val now = SystemClock.uptimeMillis() diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index 53f287b81be9..720120b630d5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -47,6 +47,8 @@ public class QSIconViewImpl extends QSIconView { public static final long QS_ANIM_LENGTH = 350; + private static final long ICON_APPLIED_TRANSACTION_ID = -1; + protected final View mIcon; protected int mIconSizePx; private boolean mAnimationEnabled = true; @@ -57,7 +59,8 @@ public class QSIconViewImpl extends QSIconView { @VisibleForTesting QSTile.Icon mLastIcon; - private boolean mIconChangeScheduled; + private long mScheduledIconChangeTransactionId = ICON_APPLIED_TRANSACTION_ID; + private long mHighestScheduledIconChangeTransactionId = ICON_APPLIED_TRANSACTION_ID; private ValueAnimator mColorAnimator = new ValueAnimator(); @@ -117,7 +120,7 @@ public class QSIconViewImpl extends QSIconView { } protected void updateIcon(ImageView iv, State state, boolean allowAnimations) { - mIconChangeScheduled = false; + mScheduledIconChangeTransactionId = ICON_APPLIED_TRANSACTION_ID; final QSTile.Icon icon = state.iconSupplier != null ? state.iconSupplier.get() : state.icon; if (!Objects.equals(icon, iv.getTag(R.id.qs_icon_tag))) { boolean shouldAnimate = allowAnimations && shouldAnimate(iv); @@ -173,9 +176,10 @@ public class QSIconViewImpl extends QSIconView { mState = state.state; mDisabledByPolicy = state.disabledByPolicy; if (mTint != 0 && allowAnimations && shouldAnimate(iv)) { - mIconChangeScheduled = true; + final long iconTransactionId = getNextIconTransactionId(); + mScheduledIconChangeTransactionId = iconTransactionId; animateGrayScale(mTint, color, iv, () -> { - if (mIconChangeScheduled) { + if (mScheduledIconChangeTransactionId == iconTransactionId) { updateIcon(iv, state, allowAnimations); } }); @@ -237,6 +241,11 @@ public class QSIconViewImpl extends QSIconView { child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight()); } + private long getNextIconTransactionId() { + mHighestScheduledIconChangeTransactionId++; + return mHighestScheduledIconChangeTransactionId; + } + /** * Color to tint the tile icon based on state */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt index 88863cbad1ee..a4748686a784 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt @@ -42,6 +42,7 @@ import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.recordissue.IssueRecordingService import com.android.systemui.recordissue.RecordIssueDialogDelegate import com.android.systemui.res.R import com.android.systemui.screenrecord.RecordingService @@ -107,7 +108,7 @@ constructor( PendingIntent.getService( userContextProvider.userContext, RecordingService.REQUEST_CODE, - RecordingService.getStopIntent(userContextProvider.userContext), + IssueRecordingService.getStopIntent(userContextProvider.userContext), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle()) diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt new file mode 100644 index 000000000000..f4872589b3bf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt @@ -0,0 +1,107 @@ +/* + * 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.recordissue + +import android.app.NotificationManager +import android.content.Context +import android.content.Intent +import android.content.res.Resources +import android.os.Handler +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.qualifiers.LongRunning +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.res.R +import com.android.systemui.screenrecord.RecordingController +import com.android.systemui.screenrecord.RecordingService +import com.android.systemui.screenrecord.RecordingServiceStrings +import com.android.systemui.settings.UserContextProvider +import com.android.systemui.statusbar.phone.KeyguardDismissUtil +import java.util.concurrent.Executor +import javax.inject.Inject + +class IssueRecordingService +@Inject +constructor( + controller: RecordingController, + @LongRunning executor: Executor, + @Main handler: Handler, + uiEventLogger: UiEventLogger, + notificationManager: NotificationManager, + userContextProvider: UserContextProvider, + keyguardDismissUtil: KeyguardDismissUtil +) : + RecordingService( + controller, + executor, + handler, + uiEventLogger, + notificationManager, + userContextProvider, + keyguardDismissUtil + ) { + + override fun getTag(): String = TAG + + override fun getChannelId(): String = CHANNEL_ID + + override fun provideRecordingServiceStrings(): RecordingServiceStrings = IrsStrings(resources) + + companion object { + private const val TAG = "IssueRecordingService" + private const val CHANNEL_ID = "issue_record" + + /** + * Get an intent to stop the issue recording service. + * + * @param context Context from the requesting activity + * @return + */ + fun getStopIntent(context: Context): Intent = + Intent(context, RecordingService::class.java) + .setAction(ACTION_STOP) + .putExtra(Intent.EXTRA_USER_HANDLE, context.userId) + + /** + * Get an intent to start the issue recording service. + * + * @param context Context from the requesting activity + */ + fun getStartIntent(context: Context): Intent = + Intent(context, RecordingService::class.java).setAction(ACTION_START) + } +} + +private class IrsStrings(private val res: Resources) : RecordingServiceStrings(res) { + override val title + get() = res.getString(R.string.issuerecord_title) + override val notificationChannelDescription + get() = res.getString(R.string.issuerecord_channel_description) + override val startErrorResId + get() = R.string.issuerecord_start_error + override val startError + get() = res.getString(R.string.issuerecord_start_error) + override val saveErrorResId + get() = R.string.issuerecord_save_error + override val saveError + get() = res.getString(R.string.issuerecord_save_error) + override val ongoingRecording + get() = res.getString(R.string.issuerecord_ongoing_screen_only) + override val backgroundProcessingLabel + get() = res.getString(R.string.issuerecord_background_processing_label) + override val saveTitle + get() = res.getString(R.string.issuerecord_save_title) +} diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt index e051df4e6c7b..80f11f1e1874 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt @@ -17,7 +17,6 @@ package com.android.systemui.recordissue import android.annotation.SuppressLint -import android.app.Activity import android.app.BroadcastOptions import android.app.PendingIntent import android.content.Context @@ -45,7 +44,6 @@ import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDi import com.android.systemui.qs.tiles.RecordIssueTile import com.android.systemui.res.R import com.android.systemui.screenrecord.RecordingService -import com.android.systemui.screenrecord.ScreenRecordingAudioSource import com.android.systemui.settings.UserContextProvider import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker @@ -183,13 +181,7 @@ constructor( PendingIntent.getForegroundService( userContextProvider.userContext, RecordingService.REQUEST_CODE, - RecordingService.getStartIntent( - userContextProvider.userContext, - Activity.RESULT_OK, - ScreenRecordingAudioSource.NONE.ordinal, - false, - null - ), + IssueRecordingService.getStartIntent(userContextProvider.userContext), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle()) diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index b5a13138a278..b355d2d6b4f8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -24,7 +24,6 @@ import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; -import android.content.res.Resources; import android.graphics.drawable.Icon; import android.media.MediaRecorder; import android.net.Uri; @@ -71,8 +70,8 @@ public class RecordingService extends Service implements ScreenMediaRecorderList private static final String EXTRA_SHOW_TAPS = "extra_showTaps"; private static final String EXTRA_CAPTURE_TARGET = "extra_captureTarget"; - private static final String ACTION_START = "com.android.systemui.screenrecord.START"; - private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP"; + protected static final String ACTION_START = "com.android.systemui.screenrecord.START"; + protected static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP"; private static final String ACTION_STOP_NOTIF = "com.android.systemui.screenrecord.STOP_FROM_NOTIF"; private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE"; @@ -90,6 +89,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList private final NotificationManager mNotificationManager; private final UserContextProvider mUserContextTracker; private int mNotificationId = NOTIF_BASE_ID; + private RecordingServiceStrings mStrings; @Inject public RecordingService(RecordingController controller, @LongRunning Executor executor, @@ -134,9 +134,9 @@ public class RecordingService extends Service implements ScreenMediaRecorderList return Service.START_NOT_STICKY; } String action = intent.getAction(); - Log.d(TAG, "onStartCommand " + action); + Log.d(getTag(), "onStartCommand " + action); NotificationChannel channel = new NotificationChannel( - CHANNEL_ID, + getChannelId(), getString(R.string.screenrecord_title), NotificationManager.IMPORTANCE_DEFAULT); channel.setDescription(getString(R.string.screenrecord_channel_description)); @@ -152,7 +152,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList mNotificationId = NOTIF_BASE_ID + (int) SystemClock.uptimeMillis(); mAudioSource = ScreenRecordingAudioSource .values()[intent.getIntExtra(EXTRA_AUDIO_SOURCE, 0)]; - Log.d(TAG, "recording with audio source " + mAudioSource); + Log.d(getTag(), "recording with audio source " + mAudioSource); mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false); MediaProjectionCaptureTarget captureTarget = intent.getParcelableExtra(EXTRA_CAPTURE_TARGET, @@ -207,7 +207,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList .setType("video/mp4") .putExtra(Intent.EXTRA_STREAM, shareUri); mKeyguardDismissUtil.executeWhenUnlocked(() -> { - String shareLabel = getResources().getString(R.string.screenrecord_share_label); + String shareLabel = strings().getShareLabel(); startActivity(Intent.createChooser(shareIntent, shareLabel) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); // Remove notification @@ -270,13 +270,11 @@ public class RecordingService extends Service implements ScreenMediaRecorderList */ @VisibleForTesting protected void createErrorNotification() { - Resources res = getResources(); Bundle extras = new Bundle(); - extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, - res.getString(R.string.screenrecord_title)); - String notificationTitle = res.getString(R.string.screenrecord_start_error); + extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle()); + String notificationTitle = strings().getStartError(); - Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) + Notification.Builder builder = new Notification.Builder(this, getChannelId()) .setSmallIcon(R.drawable.ic_screenrecord) .setContentTitle(notificationTitle) .addExtras(extras); @@ -290,14 +288,12 @@ public class RecordingService extends Service implements ScreenMediaRecorderList @VisibleForTesting protected void createRecordingNotification() { - Resources res = getResources(); Bundle extras = new Bundle(); - extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, - res.getString(R.string.screenrecord_title)); + extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle()); String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE - ? res.getString(R.string.screenrecord_ongoing_screen_only) - : res.getString(R.string.screenrecord_ongoing_screen_and_audio); + ? strings().getOngoingRecording() + : strings().getOngoingRecordingWithAudio(); PendingIntent pendingIntent = PendingIntent.getService( this, @@ -306,9 +302,9 @@ public class RecordingService extends Service implements ScreenMediaRecorderList PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); Notification.Action stopAction = new Notification.Action.Builder( Icon.createWithResource(this, R.drawable.ic_android), - getResources().getString(R.string.screenrecord_stop_label), + strings().getStopLabel(), pendingIntent).build(); - Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) + Notification.Builder builder = new Notification.Builder(this, getChannelId()) .setSmallIcon(R.drawable.ic_screenrecord) .setContentTitle(notificationTitle) .setUsesChronometer(true) @@ -323,19 +319,17 @@ public class RecordingService extends Service implements ScreenMediaRecorderList @VisibleForTesting protected Notification createProcessingNotification() { - Resources res = getApplicationContext().getResources(); String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE - ? res.getString(R.string.screenrecord_ongoing_screen_only) - : res.getString(R.string.screenrecord_ongoing_screen_and_audio); + ? strings().getOngoingRecording() + : strings().getOngoingRecordingWithAudio(); Bundle extras = new Bundle(); - extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, - res.getString(R.string.screenrecord_title)); + extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle()); - Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) + Notification.Builder builder = new Notification.Builder(this, getChannelId()) .setContentTitle(notificationTitle) .setContentText( - getResources().getString(R.string.screenrecord_background_processing_label)) + strings().getBackgroundProcessingLabel()) .setSmallIcon(R.drawable.ic_screenrecord) .setGroup(GROUP_KEY) .addExtras(extras); @@ -351,7 +345,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList Notification.Action shareAction = new Notification.Action.Builder( Icon.createWithResource(this, R.drawable.ic_screenrecord), - getResources().getString(R.string.screenrecord_share_label), + strings().getShareLabel(), PendingIntent.getService( this, REQUEST_CODE, @@ -360,13 +354,12 @@ public class RecordingService extends Service implements ScreenMediaRecorderList .build(); Bundle extras = new Bundle(); - extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, - getResources().getString(R.string.screenrecord_title)); + extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle()); - Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) + Notification.Builder builder = new Notification.Builder(this, getChannelId()) .setSmallIcon(R.drawable.ic_screenrecord) - .setContentTitle(getResources().getString(R.string.screenrecord_save_title)) - .setContentText(getResources().getString(R.string.screenrecord_save_text)) + .setContentTitle(strings().getSaveTitle()) + .setContentText(strings().getSaveText()) .setContentIntent(PendingIntent.getActivity( this, REQUEST_CODE, @@ -394,15 +387,15 @@ public class RecordingService extends Service implements ScreenMediaRecorderList private void postGroupNotification(UserHandle currentUser) { Bundle extras = new Bundle(); extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, - getResources().getString(R.string.screenrecord_title)); - Notification groupNotif = new Notification.Builder(this, CHANNEL_ID) + strings().getTitle()); + Notification groupNotif = new Notification.Builder(this, getChannelId()) .setSmallIcon(R.drawable.ic_screenrecord) - .setContentTitle(getResources().getString(R.string.screenrecord_save_title)) + .setContentTitle(strings().getSaveTitle()) .setGroup(GROUP_KEY) .setGroupSummary(true) .setExtras(extras) .build(); - mNotificationManager.notifyAsUser(TAG, NOTIF_BASE_ID, groupNotif, currentUser); + mNotificationManager.notifyAsUser(getTag(), NOTIF_BASE_ID, groupNotif, currentUser); } private void stopService() { @@ -413,7 +406,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList if (userId == USER_ID_NOT_SPECIFIED) { userId = mUserContextTracker.getUserContext().getUserId(); } - Log.d(TAG, "notifying for user " + userId); + Log.d(getTag(), "notifying for user " + userId); setTapsVisible(mOriginalShowTaps); if (getRecorder() != null) { try { @@ -424,7 +417,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList // let's release the recorder and delete all temporary files in this case getRecorder().release(); showErrorToast(R.string.screenrecord_start_error); - Log.e(TAG, "stopRecording called, but there was an error when ending" + Log.e(getTag(), "stopRecording called, but there was an error when ending" + "recording"); exception.printStackTrace(); createErrorNotification(); @@ -435,7 +428,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList throw new RuntimeException(throwable); } } else { - Log.e(TAG, "stopRecording called, but recorder was null"); + Log.e(getTag(), "stopRecording called, but recorder was null"); } updateState(false); stopForeground(STOP_FOREGROUND_DETACH); @@ -449,13 +442,13 @@ public class RecordingService extends Service implements ScreenMediaRecorderList mLongExecutor.execute(() -> { try { - Log.d(TAG, "saving recording"); + Log.d(getTag(), "saving recording"); Notification notification = createSaveNotification(getRecorder().save()); postGroupNotification(currentUser); mNotificationManager.notifyAsUser(null, mNotificationId, notification, currentUser); } catch (IOException | IllegalStateException e) { - Log.e(TAG, "Error saving screen recording: " + e.getMessage()); + Log.e(getTag(), "Error saving screen recording: " + e.getMessage()); e.printStackTrace(); showErrorToast(R.string.screenrecord_save_error); mNotificationManager.cancelAsUser(null, mNotificationId, currentUser); @@ -468,6 +461,26 @@ public class RecordingService extends Service implements ScreenMediaRecorderList Settings.System.putInt(getContentResolver(), Settings.System.SHOW_TOUCHES, value); } + protected String getTag() { + return TAG; + } + + protected String getChannelId() { + return CHANNEL_ID; + } + + private RecordingServiceStrings strings() { + if (mStrings == null) { + mStrings = provideRecordingServiceStrings(); + } + return mStrings; + } + + protected RecordingServiceStrings provideRecordingServiceStrings() { + return new RecordingServiceStrings(getResources()); + } + + /** * Get an intent to stop the recording service. * @param context Context from the requesting activity @@ -484,25 +497,25 @@ public class RecordingService extends Service implements ScreenMediaRecorderList * @param context * @return */ - protected static Intent getNotificationIntent(Context context) { - return new Intent(context, RecordingService.class).setAction(ACTION_STOP_NOTIF); + protected Intent getNotificationIntent(Context context) { + return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF); } - private static Intent getShareIntent(Context context, String path) { - return new Intent(context, RecordingService.class).setAction(ACTION_SHARE) + private Intent getShareIntent(Context context, String path) { + return new Intent(context, this.getClass()).setAction(ACTION_SHARE) .putExtra(EXTRA_PATH, path); } @Override public void onInfo(MediaRecorder mr, int what, int extra) { - Log.d(TAG, "Media recorder info: " + what); + Log.d(getTag(), "Media recorder info: " + what); onStartCommand(getStopIntent(this), 0, 0); } @Override public void onStopped() { if (mController.isRecording()) { - Log.d(TAG, "Stopping recording because the system requested the stop"); + Log.d(getTag(), "Stopping recording because the system requested the stop"); stopService(); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingServiceStrings.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingServiceStrings.kt new file mode 100644 index 000000000000..fdb1eb6af298 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingServiceStrings.kt @@ -0,0 +1,50 @@ +/* + * 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.screenrecord + +import android.content.res.Resources +import com.android.systemui.res.R + +open class RecordingServiceStrings(private val res: Resources) { + open val title + get() = res.getString(R.string.screenrecord_title) + open val notificationChannelDescription + get() = res.getString(R.string.screenrecord_channel_description) + open val startErrorResId + get() = R.string.screenrecord_start_error + open val startError + get() = res.getString(R.string.screenrecord_start_error) + open val saveErrorResId + get() = R.string.screenrecord_save_error + open val saveError + get() = res.getString(R.string.screenrecord_save_error) + open val ongoingRecording + get() = res.getString(R.string.screenrecord_ongoing_screen_only) + open val backgroundProcessingLabel + get() = res.getString(R.string.screenrecord_background_processing_label) + open val saveTitle + get() = res.getString(R.string.screenrecord_save_title) + + val saveText + get() = res.getString(R.string.screenrecord_save_text) + val ongoingRecordingWithAudio + get() = res.getString(R.string.screenrecord_ongoing_screen_and_audio) + val stopLabel + get() = res.getString(R.string.screenrecord_stop_label) + val shareLabel + get() = res.getString(R.string.screenrecord_share_label) +} 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 5e0110b8af03..0a11eb26cc07 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 a3e09417b34c..b38d619a4434 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 class StackStateAnimator { 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 @@ public class StackStateAnimator { 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 @@ public class StackStateAnimator { 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 01972646f394..311ba83e85f1 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 @@ class NotificationStackAppearanceRepository @Inject constructor() { /** 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 8307397c57da..9984ba9c32ce 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 @@ constructor( /** 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 50b08b8bb361..814146c329a6 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 @@ import kotlinx.coroutines.launch /** Binds the shared notification container to its view-model. */ object NotificationStackAppearanceViewBinder { + const val SCRIM_CORNER_RADIUS = 32f @JvmStatic fun bind( @@ -49,8 +50,8 @@ object NotificationStackAppearanceViewBinder { 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 fe5bdd41a94f..f3d0d2c2e9fc 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 @@ object SharedNotificationContainerBinder { .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 56ff7f9e50df..bdf1a6431549 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 @@ constructor( /** 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 8b723da65162..65d9c9f523f8 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.domain.interactor.Notif 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 @@ constructor( 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 877bd7c11e95..e84b7a077b21 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.systemui.util.leak.RotationUtils.getResourcesForRotation 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 @@ class StatusBarContentInsetsProvider @Inject constructor( val configurationController: ConfigurationController, val dumpManager: DumpManager, val commandRegistry: CommandRegistry, + val sysUICutoutProvider: SysUICutoutProvider, ) : CallbackController<StatusBarContentInsetsChangedListener>, ConfigurationController.ConfigurationListener, Dumpable { @@ -176,7 +179,8 @@ class StatusBarContentInsetsProvider @Inject constructor( */ 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 @@ class StatusBarContentInsetsProvider @Inject constructor( 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 @@ class StatusBarContentInsetsProvider @Inject constructor( 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 @@ class StatusBarContentInsetsProvider @Inject constructor( 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 @@ class StatusBarContentInsetsProvider @Inject constructor( return calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, - displayCutout, + sysUICutout, context.resources.configuration.windowConfiguration.maxBounds, SystemBarUtils.getStatusBarHeightForRotation(context, targetRotation), minLeft, @@ -415,7 +420,7 @@ fun getPrivacyChipBoundingRectForInsets( fun calculateInsetsForRotationWithRotatedResources( @Rotation currentRotation: Int, @Rotation targetRotation: Int, - displayCutout: DisplayCutout?, + sysUICutout: SysUICutoutInformation?, maxBounds: Rect, statusBarHeight: Int, minLeft: Int, @@ -434,7 +439,7 @@ fun calculateInsetsForRotationWithRotatedResources( val rotZeroBounds = getRotationZeroDisplayBounds(maxBounds, currentRotation) return getStatusBarContentBounds( - displayCutout, + sysUICutout, statusBarHeight, rotZeroBounds.right, rotZeroBounds.bottom, @@ -470,7 +475,7 @@ fun calculateInsetsForRotationWithRotatedResources( * rotation */ private fun getStatusBarContentBounds( - displayCutout: DisplayCutout?, + sysUICutout: SysUICutoutInformation?, sbHeight: Int, width: Int, height: Int, @@ -489,19 +494,17 @@ private fun getStatusBarContentBounds( 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 @@ private fun getStatusBarContentBounds( 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 @@ private fun getStatusBarContentBounds( 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 ceebcb77fde2..e5179dd96742 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 @@ class PendingTasksContainer { */ 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 @@ class PendingTasksContainer { fun getPendingCount(): Int = pendingTasksCount.get() } -private const val ENABLE_TRACE = false diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index 2acd4b92c5a2..139d190ae63c 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -111,6 +111,7 @@ public class BubblesManager { // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline private final List<NotifCallback> mCallbacks = new ArrayList<>(); private final StatusBarWindowCallback mStatusBarWindowCallback; + private boolean mPanelExpanded; /** * Creates {@link BubblesManager}, returns {@code null} if Optional {@link Bubbles} not present @@ -242,8 +243,12 @@ public class BubblesManager { // Store callback in a field so it won't get GC'd mStatusBarWindowCallback = (keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, isDozing, - panelExpanded, isDreaming) -> + panelExpanded, isDreaming) -> { + if (panelExpanded != mPanelExpanded) { + mPanelExpanded = panelExpanded; mBubbles.onNotificationPanelExpandedChanged(panelExpanded); + } + }; notificationShadeWindowController.registerCallback(mStatusBarWindowCallback); mSysuiProxy = new Bubbles.SysuiProxy() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt index 64cd5262eade..f776a63c54ae 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 @@ class CameraAvailabilityListenerTest : SysuiTestCase() { 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 index 238e5e9197a3..a19a0c7d12a3 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 @@ import org.junit.runner.RunWith @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 @@ class CameraProtectionLoaderTest : SysuiTestCase() { 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 @@ class CameraProtectionLoaderTest : SysuiTestCase() { 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 @@ class CameraProtectionLoaderTest : SysuiTestCase() { 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 @@ class CameraProtectionLoaderTest : SysuiTestCase() { val logicalCameraId: String, val physicalCameraId: String?, val cutoutBounds: Rect, + val displayUniqueId: String?, ) companion object { @@ -102,11 +108,13 @@ class CameraProtectionLoaderTest : SysuiTestCase() { 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 @@ class CameraProtectionLoaderTest : SysuiTestCase() { 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 000000000000..f769b4e5f2d0 --- /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 1f1fa7259cd5..c20367efa5da 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 @@ public class ScreenDecorationsTest extends SysuiTestCase { 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 000000000000..f37c4ae613ff --- /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/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt index e8aa8f0bdc5d..bbae0c90170a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt @@ -34,7 +34,7 @@ import kotlin.test.Test import org.junit.Rule import org.junit.runner.RunWith -/** Test for regression b/311121830 */ +/** Test for regression b/311121830 and b/323125376 */ @RunWith(AndroidTestingRunner::class) @UiThreadTest @SmallTest @@ -82,6 +82,55 @@ class QSIconViewImplTest_311121830 : SysuiTestCase() { assertThat(iconView.mLastIcon).isEqualTo(secondState.icon) } + @Test + fun alwaysLastIcon_twoStateChanges() { + // Need to inflate with the correct theme so the colors can be retrieved and the animations + // are run + val iconView = + AnimateQSIconViewImpl( + ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) + ) + + val initialState = + QSTile.State().apply { + state = Tile.STATE_ACTIVE + icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_FULL_ICONS[4]) + } + val firstState = + QSTile.State().apply { + state = Tile.STATE_INACTIVE + icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4]) + } + val secondState = + QSTile.State().apply { + state = Tile.STATE_ACTIVE + icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_FULL_ICONS[3]) + } + val thirdState = + QSTile.State().apply { + state = Tile.STATE_INACTIVE + icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_NO_NETWORK) + } + + // Start with the initial state + iconView.setIcon(initialState, /* allowAnimations= */ false) + + // Set the first state to animate, and advance time to one third of the animation + iconView.setIcon(firstState, /* allowAnimations= */ true) + animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH / 3) + + // Set the second state to animate and advance time by another third of animations length + iconView.setIcon(secondState, /* allowAnimations= */ true) + animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH / 3) + + // Set the third state to animate and advance time by two times the animation length + // to guarantee that all animations are done + iconView.setIcon(thirdState, /* allowAnimations= */ true) + animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH * 2) + + assertThat(iconView.mLastIcon).isEqualTo(thirdState.icon) + } + private class AnimateQSIconViewImpl(context: Context) : QSIconViewImpl(context) { override fun createIcon(): View { return object : ImageView(context) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java index 9ce77e58a5f2..deecc5bb5a03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java @@ -166,7 +166,7 @@ public class RecordingServiceTest extends SysuiTestCase { @Test public void testLogStopFromNotificationIntent() { - Intent stopIntent = RecordingService.getNotificationIntent(mContext); + Intent stopIntent = mRecordingService.getNotificationIntent(mContext); mRecordingService.onStartCommand(stopIntent, 0, 0); // Verify that we log the correct event diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 44682e2088f4..f902439c20e5 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -52,6 +52,13 @@ flag { } flag { + name: "fullscreen_fling_gesture" + namespace: "accessibility" + description: "When true, adds a fling gesture animation for fullscreen magnification" + bug: "319175022" +} + +flag { name: "pinch_zoom_zero_min_span" namespace: "accessibility" description: "Whether to set min span of ScaleGestureDetector to zero." diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index 805f6e390fd4..351760bd8865 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -25,7 +25,9 @@ import static com.android.server.accessibility.AccessibilityManagerService.INVAL import android.accessibilityservice.MagnificationConfig; import android.animation.Animator; +import android.animation.TimeAnimator; import android.animation.ValueAnimator; +import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.BroadcastReceiver; @@ -51,6 +53,7 @@ import android.view.View; import android.view.WindowManager; import android.view.accessibility.MagnificationAnimationCallback; import android.view.animation.DecelerateInterpolator; +import android.widget.Scroller; import com.android.internal.R; import com.android.internal.accessibility.common.MagnificationConstants; @@ -60,6 +63,7 @@ import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.AccessibilityTraceManager; +import com.android.server.accessibility.Flags; import com.android.server.wm.WindowManagerInternal; import java.util.ArrayList; @@ -86,6 +90,8 @@ public class FullScreenMagnificationController implements private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false; private final Object mLock; + private final Supplier<Scroller> mScrollerSupplier; + private final Supplier<TimeAnimator> mTimeAnimatorSupplier; private final ControllerContext mControllerCtx; @@ -149,7 +155,13 @@ public class FullScreenMagnificationController implements DisplayMagnification(int displayId) { mDisplayId = displayId; - mSpecAnimationBridge = new SpecAnimationBridge(mControllerCtx, mLock, mDisplayId); + mSpecAnimationBridge = + new SpecAnimationBridge( + mControllerCtx, + mLock, + mDisplayId, + mScrollerSupplier, + mTimeAnimatorSupplier); } /** @@ -406,6 +418,52 @@ public class FullScreenMagnificationController implements } } + void startFlingAnimation( + float xPixelsPerSecond, + float yPixelsPerSecond, + MagnificationAnimationCallback animationCallback + ) { + if (DEBUG) { + Slog.i(LOG_TAG, + "startFlingAnimation(spec = " + xPixelsPerSecond + ", animationCallback = " + + animationCallback + ")"); + } + if (Thread.currentThread().getId() == mMainThreadId) { + mSpecAnimationBridge.startFlingAnimation( + xPixelsPerSecond, + yPixelsPerSecond, + getMinOffsetXLocked(), + getMaxOffsetXLocked(), + getMinOffsetYLocked(), + getMaxOffsetYLocked(), + animationCallback); + } else { + final Message m = + PooledLambda.obtainMessage( + SpecAnimationBridge::startFlingAnimation, + mSpecAnimationBridge, + xPixelsPerSecond, + yPixelsPerSecond, + getMinOffsetXLocked(), + getMaxOffsetXLocked(), + getMinOffsetYLocked(), + getMaxOffsetYLocked(), + animationCallback); + mControllerCtx.getHandler().sendMessage(m); + } + } + + void cancelFlingAnimation() { + if (DEBUG) { + Slog.i(LOG_TAG, "cancelFlingAnimation()"); + } + if (Thread.currentThread().getId() == mMainThreadId) { + mSpecAnimationBridge.cancelFlingAnimation(); + } else { + mControllerCtx.getHandler().post(mSpecAnimationBridge::cancelFlingAnimation); + } + } + /** * Get the ID of the last service that changed the magnification spec. * @@ -759,6 +817,63 @@ public class FullScreenMagnificationController implements sendSpecToAnimation(mCurrentMagnificationSpec, null); } + @GuardedBy("mLock") + void startFling(float xPixelsPerSecond, float yPixelsPerSecond, int id) { + if (!mRegistered) { + return; + } + if (!isActivated()) { + return; + } + + if (id != INVALID_SERVICE_ID) { + mIdOfLastServiceToMagnify = id; + } + + startFlingAnimation( + xPixelsPerSecond, + yPixelsPerSecond, + new MagnificationAnimationCallback() { + @Override + public void onResult(boolean success) { + // never called + } + + @Override + public void onResult(boolean success, MagnificationSpec lastSpecSent) { + if (DEBUG) { + Slog.i( + LOG_TAG, + "startFlingAnimation finished( " + + success + + " = " + + lastSpecSent.offsetX + + ", " + + lastSpecSent.offsetY + + ")"); + } + synchronized (mLock) { + mCurrentMagnificationSpec.setTo(lastSpecSent); + onMagnificationChangedLocked(); + } + } + }); + } + + + @GuardedBy("mLock") + void cancelFling(int id) { + if (!mRegistered) { + return; + } + + if (id != INVALID_SERVICE_ID) { + mIdOfLastServiceToMagnify = id; + } + + cancelFlingAnimation(); + } + boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) { if (DEBUG) { Slog.i(LOG_TAG, @@ -838,7 +953,9 @@ public class FullScreenMagnificationController implements magnificationInfoChangedCallback, scaleProvider, /* thumbnailSupplier= */ null, - backgroundExecutor); + backgroundExecutor, + () -> new Scroller(context), + TimeAnimator::new); } /** Constructor for tests */ @@ -849,9 +966,13 @@ public class FullScreenMagnificationController implements @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback, @NonNull MagnificationScaleProvider scaleProvider, Supplier<MagnificationThumbnail> thumbnailSupplier, - @NonNull Executor backgroundExecutor) { + @NonNull Executor backgroundExecutor, + Supplier<Scroller> scrollerSupplier, + Supplier<TimeAnimator> timeAnimatorSupplier) { mControllerCtx = ctx; mLock = lock; + mScrollerSupplier = scrollerSupplier; + mTimeAnimatorSupplier = timeAnimatorSupplier; mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId(); mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this); addInfoChangedCallback(magnificationInfoChangedCallback); @@ -1437,6 +1558,42 @@ public class FullScreenMagnificationController implements } /** + * Call after a pan ends, if the velocity has passed the threshold, to start a fling animation. + * + * @param displayId The logical display id. + * @param xPixelsPerSecond the velocity of the last pan gesture in the X direction, in current + * screen pixels per second. + * @param yPixelsPerSecond the velocity of the last pan gesture in the Y direction, in current + * screen pixels per second. + * @param id the ID of the service requesting the change + */ + public void startFling(int displayId, float xPixelsPerSecond, float yPixelsPerSecond, int id) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return; + } + display.startFling(xPixelsPerSecond, yPixelsPerSecond, id); + } + } + + /** + * Call to cancel the fling animation if it is running. Call this on any ACTION_DOWN event. + * + * @param displayId The logical display id. + * @param id the ID of the service requesting the change + */ + public void cancelFling(int displayId, int id) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return; + } + display.cancelFling(id); + } + } + + /** * Get the ID of the last service that changed the magnification spec. * * @param displayId The logical display id. @@ -1698,7 +1855,15 @@ public class FullScreenMagnificationController implements @GuardedBy("mLock") private boolean mEnabled = false; - private SpecAnimationBridge(ControllerContext ctx, Object lock, int displayId) { + private final Scroller mScroller; + private final TimeAnimator mScrollAnimator; + + private SpecAnimationBridge( + ControllerContext ctx, + Object lock, + int displayId, + Supplier<Scroller> scrollerSupplier, + Supplier<TimeAnimator> timeAnimatorSupplier) { mControllerCtx = ctx; mLock = lock; mDisplayId = displayId; @@ -1709,6 +1874,37 @@ public class FullScreenMagnificationController implements mValueAnimator.setFloatValues(0.0f, 1.0f); mValueAnimator.addUpdateListener(this); mValueAnimator.addListener(this); + + if (Flags.fullscreenFlingGesture()) { + mScroller = scrollerSupplier.get(); + mScrollAnimator = timeAnimatorSupplier.get(); + mScrollAnimator.addListener(this); + mScrollAnimator.setTimeListener( + (animation, totalTime, deltaTime) -> { + synchronized (mLock) { + if (DEBUG) { + Slog.v( + LOG_TAG, + "onScrollAnimationUpdate: " + + mEnabled + " : " + totalTime); + } + + if (mEnabled) { + if (!mScroller.computeScrollOffset()) { + animation.end(); + return; + } + + mEndMagnificationSpec.offsetX = mScroller.getCurrX(); + mEndMagnificationSpec.offsetY = mScroller.getCurrY(); + setMagnificationSpecLocked(mEndMagnificationSpec); + } + } + }); + } else { + mScroller = null; + mScrollAnimator = null; + } } /** @@ -1735,16 +1931,20 @@ public class FullScreenMagnificationController implements } } - void updateSentSpecMainThread(MagnificationSpec spec, - MagnificationAnimationCallback animationCallback) { - if (mValueAnimator.isRunning()) { - mValueAnimator.cancel(); - } + @MainThread + void updateSentSpecMainThread( + MagnificationSpec spec, MagnificationAnimationCallback animationCallback) { + cancelAnimations(); mAnimationCallback = animationCallback; // If the current and sent specs don't match, update the sent spec. synchronized (mLock) { final boolean changed = !mSentMagnificationSpec.equals(spec); + if (DEBUG_SET_MAGNIFICATION_SPEC) { + Slog.d( + LOG_TAG, + "updateSentSpecMainThread: " + mEnabled + " : changed: " + changed); + } if (changed) { if (mAnimationCallback != null) { animateMagnificationSpecLocked(spec); @@ -1757,12 +1957,13 @@ public class FullScreenMagnificationController implements } } + @MainThread private void sendEndCallbackMainThread(boolean success) { if (mAnimationCallback != null) { if (DEBUG) { Slog.d(LOG_TAG, "sendEndCallbackMainThread: " + success); } - mAnimationCallback.onResult(success); + mAnimationCallback.onResult(success, mSentMagnificationSpec); mAnimationCallback = null; } } @@ -1830,6 +2031,77 @@ public class FullScreenMagnificationController implements public void onAnimationRepeat(Animator animation) { } + + /** + * Call after a pan ends, if the velocity has passed the threshold, to start a fling + * animation. + */ + @MainThread + public void startFlingAnimation( + float xPixelsPerSecond, + float yPixelsPerSecond, + float minX, + float maxX, + float minY, + float maxY, + MagnificationAnimationCallback animationCallback + ) { + if (!Flags.fullscreenFlingGesture()) { + return; + } + cancelAnimations(); + + mAnimationCallback = animationCallback; + + // We use this as a temp object to send updates every animation frame, so make sure it + // matches the current spec before we start. + mEndMagnificationSpec.setTo(mSentMagnificationSpec); + + if (DEBUG) { + Slog.d(LOG_TAG, "startFlingAnimation: " + + "offsetX " + mSentMagnificationSpec.offsetX + + "offsetY " + mSentMagnificationSpec.offsetY + + "xPixelsPerSecond " + xPixelsPerSecond + + "yPixelsPerSecond " + yPixelsPerSecond + + "minX " + minX + + "maxX " + maxX + + "minY " + minY + + "maxY " + maxY + ); + } + + mScroller.fling( + (int) mSentMagnificationSpec.offsetX, + (int) mSentMagnificationSpec.offsetY, + (int) xPixelsPerSecond, + (int) yPixelsPerSecond, + (int) minX, + (int) maxX, + (int) minY, + (int) maxY); + + mScrollAnimator.start(); + } + + @MainThread + void cancelAnimations() { + if (mValueAnimator.isRunning()) { + mValueAnimator.cancel(); + } + + cancelFlingAnimation(); + } + + @MainThread + void cancelFlingAnimation() { + if (!Flags.fullscreenFlingGesture()) { + return; + } + if (mScrollAnimator.isRunning()) { + mScrollAnimator.cancel(); + } + mScroller.forceFinished(true); + } } private static class ScreenStateObserver extends BroadcastReceiver { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index baae1d934e8c..f4ea754c21ba 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -62,6 +62,7 @@ import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector.OnScaleGestureListener; +import android.view.VelocityTracker; import android.view.ViewConfiguration; import com.android.internal.R; @@ -174,6 +175,10 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH private final boolean mIsWatch; + @Nullable private VelocityTracker mVelocityTracker; + private final int mMinimumVelocity; + private final int mMaximumVelocity; + public FullScreenMagnificationGestureHandler(@UiContext Context context, FullScreenMagnificationController fullScreenMagnificationController, AccessibilityTraceManager trace, @@ -184,15 +189,25 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH @NonNull WindowMagnificationPromptController promptController, int displayId, FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) { - this(context, fullScreenMagnificationController, trace, callback, - detectSingleFingerTripleTap, detectTwoFingerTripleTap, - detectShortcutTrigger, promptController, displayId, - fullScreenMagnificationVibrationHelper, /* magnificationLogger= */ null); + this( + context, + fullScreenMagnificationController, + trace, + callback, + detectSingleFingerTripleTap, + detectTwoFingerTripleTap, + detectShortcutTrigger, + promptController, + displayId, + fullScreenMagnificationVibrationHelper, + /* magnificationLogger= */ null, + ViewConfiguration.get(context)); } /** Constructor for tests. */ @VisibleForTesting - FullScreenMagnificationGestureHandler(@UiContext Context context, + FullScreenMagnificationGestureHandler( + @UiContext Context context, FullScreenMagnificationController fullScreenMagnificationController, AccessibilityTraceManager trace, Callback callback, @@ -202,7 +217,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH @NonNull WindowMagnificationPromptController promptController, int displayId, FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper, - MagnificationLogger magnificationLogger) { + MagnificationLogger magnificationLogger, + ViewConfiguration viewConfiguration) { super(displayId, detectSingleFingerTripleTap, detectTwoFingerTripleTap, detectShortcutTrigger, trace, callback); if (DEBUG_ALL) { @@ -212,6 +228,15 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH + ", detectTwoFingerTripleTap = " + detectTwoFingerTripleTap + ", detectShortcutTrigger = " + detectShortcutTrigger + ")"); } + + if (Flags.fullscreenFlingGesture()) { + mMinimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity(); + mMaximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity(); + } else { + mMinimumVelocity = 0; + mMaximumVelocity = 0; + } + mFullScreenMagnificationController = fullScreenMagnificationController; mMagnificationInfoChangedCallback = new FullScreenMagnificationController.MagnificationInfoChangedCallback() { @@ -299,6 +324,10 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH @Override void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (event.getActionMasked() == ACTION_DOWN) { + cancelFling(); + } + handleEventWith(mCurrentState, event, rawEvent, policyFlags); } @@ -501,6 +530,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } persistScaleAndTransitionTo(mViewportDraggingState); } else if (action == ACTION_UP || action == ACTION_CANCEL) { + onPanningFinished(event); // if feature flag is enabled, currently only true on watches if (mIsSinglePanningEnabled) { mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded(); @@ -578,6 +608,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH Slog.i(mLogTag, "Panned content by scrollX: " + distanceX + " scrollY: " + distanceY); } + onPan(second); mFullScreenMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX, distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); if (mIsSinglePanningEnabled) { @@ -973,7 +1004,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH && overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); - } + } // TODO(b/319537921): should there be an else here? //Primary pointer is swiping, so transit to PanningScalingState transitToPanningScalingStateAndClear(); } else if (mIsSinglePanningEnabled @@ -982,7 +1013,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH if (overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); - } + } // TODO(b/319537921): should there be an else here? transitToSinglePanningStateAndClear(); } else if (!mIsTwoFingerCountReached) { // If it is a two-finger gesture, do not transition to the @@ -1742,6 +1773,71 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } } + /** Call during MOVE events for a panning gesture. */ + private void onPan(MotionEvent event) { + if (!Flags.fullscreenFlingGesture()) { + return; + } + + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(event); + } + + /** + * Call during UP events for a panning gesture, so we can detect a fling and play a physics- + * based fling animation. + */ + private void onPanningFinished(MotionEvent event) { + if (!Flags.fullscreenFlingGesture()) { + return; + } + + if (mVelocityTracker == null) { + Log.e(mLogTag, "onPanningFinished: mVelocityTracker is null"); + return; + } + mVelocityTracker.addMovement(event); + mVelocityTracker.computeCurrentVelocity(/* units= */ 1000, mMaximumVelocity); + + float xPixelsPerSecond = mVelocityTracker.getXVelocity(); + float yPixelsPerSecond = mVelocityTracker.getYVelocity(); + + mVelocityTracker.recycle(); + mVelocityTracker = null; + + if (DEBUG_PANNING_SCALING) { + Slog.v( + mLogTag, + "onPanningFinished: pixelsPerSecond: " + + xPixelsPerSecond + + ", " + + yPixelsPerSecond + + " mMinimumVelocity: " + + mMinimumVelocity); + } + + if ((Math.abs(yPixelsPerSecond) > mMinimumVelocity) + || (Math.abs(xPixelsPerSecond) > mMinimumVelocity)) { + mFullScreenMagnificationController.startFling( + mDisplayId, + xPixelsPerSecond, + yPixelsPerSecond, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + } + } + + private void cancelFling() { + if (!Flags.fullscreenFlingGesture()) { + return; + } + + mFullScreenMagnificationController.cancelFling( + mDisplayId, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + } + final class SinglePanningState extends SimpleOnGestureListener implements State { @@ -1756,6 +1852,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH int action = event.getActionMasked(); switch (action) { case ACTION_UP: + onPanningFinished(event); + // fall-through! case ACTION_CANCEL: mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded(); mOverscrollHandler.clearEdgeState(); @@ -1770,6 +1868,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH if (mCurrentState != mSinglePanningState) { return true; } + onPan(second); mFullScreenMagnificationController.offsetMagnifiedRegion( mDisplayId, distanceX, diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java index 586aa8aaae98..af0777c74605 100644 --- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java +++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java @@ -16,6 +16,8 @@ package com.android.server.companion; +import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -25,8 +27,10 @@ import android.companion.CompanionDeviceService; import android.companion.DevicePresenceEvent; import android.content.ComponentName; import android.content.Context; +import android.hardware.power.Mode; import android.os.Handler; import android.os.ParcelUuid; +import android.os.PowerManagerInternal; import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -79,6 +83,8 @@ public class CompanionApplicationController { private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor; private final @NonNull CompanionServicesRegister mCompanionServicesRegister; + private final PowerManagerInternal mPowerManagerInternal; + @GuardedBy("mBoundCompanionApplications") private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>> mBoundCompanionApplications; @@ -87,11 +93,13 @@ public class CompanionApplicationController { CompanionApplicationController(Context context, AssociationStore associationStore, ObservableUuidStore observableUuidStore, - CompanionDevicePresenceMonitor companionDevicePresenceMonitor) { + CompanionDevicePresenceMonitor companionDevicePresenceMonitor, + PowerManagerInternal powerManagerInternal) { mContext = context; mAssociationStore = associationStore; mObservableUuidStore = observableUuidStore; mDevicePresenceMonitor = companionDevicePresenceMonitor; + mPowerManagerInternal = powerManagerInternal; mCompanionServicesRegister = new CompanionServicesRegister(); mBoundCompanionApplications = new AndroidPackageMap<>(); mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>(); @@ -364,9 +372,21 @@ public class CompanionApplicationController { boolean isPrimary = serviceConnector.isPrimary(); Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary); - // First: Only mark not BOUND for primary service. - synchronized (mBoundCompanionApplications) { - if (serviceConnector.isPrimary()) { + // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY. + if (isPrimary) { + final List<AssociationInfo> associations = + mAssociationStore.getAssociationsForPackage(userId, packageName); + + for (AssociationInfo association : associations) { + final String deviceProfile = association.getDeviceProfile(); + if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) { + Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile); + mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false); + break; + } + } + + synchronized (mBoundCompanionApplications) { mBoundCompanionApplications.removePackage(userId, packageName); } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 5019428c5323..0054bc87af77 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -20,7 +20,6 @@ package com.android.server.companion; import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES; import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES; import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; -import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION; import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE; import static android.Manifest.permission.USE_COMPANION_TRANSPORTS; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; @@ -270,7 +269,8 @@ public class CompanionDeviceManagerService extends SystemService { mAssociationStore, mObservableUuidStore, mDevicePresenceCallback); mCompanionAppController = new CompanionApplicationController( - context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor); + context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor, + mPowerManagerInternal); mTransportManager = new CompanionTransportManager(context, mAssociationStore); mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mPackageManagerInternal, mAssociationStore, @@ -1128,7 +1128,9 @@ public class CompanionDeviceManagerService extends SystemService { mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId); - if (association.getDeviceProfile() == REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) { + final String deviceProfile = association.getDeviceProfile(); + if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) { + Slog.i(TAG, "Enable hint mode for device device profile: " + deviceProfile); mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, true); } } @@ -1146,7 +1148,9 @@ public class CompanionDeviceManagerService extends SystemService { mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId); - if (association.getDeviceProfile() == REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) { + final String deviceProfile = association.getDeviceProfile(); + if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) { + Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile); mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false); } } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 10cd6e5bb136..d110349c9fc1 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 @@ public final class ProcessList { 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 e930627b55af..7ebc311b5ee8 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 @@ public final class DisplayManagerService extends SystemService { 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 087cacf9a570..1ca3923b325c 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } @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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } @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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mStopped) { return; } - handleSettingsChange(false /*userSwitch*/); + handleSettingsChange(); break; case MSG_UPDATE_RBC: @@ -2985,7 +2987,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call @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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 13acb3f79c67..ecf1635a0cd2 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 @@ import java.io.PrintWriter; * 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 @@ public interface DisplayPowerControllerInterface { * 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 @@ public interface DisplayPowerControllerInterface { /** * 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 3bb798689ea6..f6d02dbc46df 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 @@ import java.io.PrintWriter; * 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 @@ public final class DisplayBrightnessController { * 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 @@ public final class DisplayBrightnessController { } /** + * 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 3c23b5c10671..3e6e09da9753 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 @@ public class AutomaticBrightnessStrategy { * 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 82786001ab68..202c8941c154 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 @@ final class AbsoluteVolumeAudioStatusAction extends HdmiCecFeatureAction { 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/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index a61199ab908c..7726609e7075 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -170,9 +170,6 @@ public class InputManagerService extends IInputManager.Stub private InputMethodManagerInternal mInputMethodManagerInternal; - // Context cache used for loading pointer resources. - private Context mPointerIconDisplayContext; - private final File mDoubleTouchGestureEnableFile; private WindowManagerCallbacks mWindowManagerCallbacks; @@ -416,6 +413,8 @@ public class InputManagerService extends IInputManager.Stub new SparseArray<>(); @GuardedBy("mLoadedPointerIconsByDisplayAndType") boolean mUseLargePointerIcons = false; + @GuardedBy("mLoadedPointerIconsByDisplayAndType") + final SparseArray<Context> mDisplayContexts = new SparseArray<>(); final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() { @Override @@ -427,6 +426,7 @@ public class InputManagerService extends IInputManager.Stub public void onDisplayRemoved(int displayId) { synchronized (mLoadedPointerIconsByDisplayAndType) { mLoadedPointerIconsByDisplayAndType.remove(displayId); + mDisplayContexts.remove(displayId); } } @@ -440,6 +440,7 @@ public class InputManagerService extends IInputManager.Stub return; } iconsByType.clear(); + mDisplayContexts.remove(displayId); } mNative.reloadPointerIcons(); } @@ -1323,11 +1324,6 @@ public class InputManagerService extends IInputManager.Stub /** Clean up input window handles of the given display. */ public void onDisplayRemoved(int displayId) { - if (mPointerIconDisplayContext != null - && mPointerIconDisplayContext.getDisplay().getDisplayId() == displayId) { - mPointerIconDisplayContext = null; - } - updateAdditionalDisplayInputProperties(displayId, AdditionalDisplayInputProperties::reset); // TODO(b/320763728): Rely on WindowInfosListener to determine when a display has been @@ -2379,6 +2375,7 @@ public class InputManagerService extends IInputManager.Stub synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ } synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ } synchronized (mAdditionalDisplayInputPropertiesLock) { /* Test if blocked by props lock */ } + synchronized (mLoadedPointerIconsByDisplayAndType) { /* Test if blocked by pointer lock */} mBatteryController.monitor(); mNative.monitor(); } @@ -2782,7 +2779,7 @@ public class InputManagerService extends IInputManager.Stub } PointerIcon icon = iconsByType.get(type); if (icon == null) { - icon = PointerIcon.getLoadedSystemIcon(getContextForPointerIcon(displayId), type, + icon = PointerIcon.getLoadedSystemIcon(getContextForDisplay(displayId), type, mUseLargePointerIcons); iconsByType.put(type, icon); } @@ -2800,40 +2797,31 @@ public class InputManagerService extends IInputManager.Stub return sc.mNativeObject; } + @GuardedBy("mLoadedPointerIconsByDisplayAndType") @NonNull - private Context getContextForPointerIcon(int displayId) { - if (mPointerIconDisplayContext != null - && mPointerIconDisplayContext.getDisplay().getDisplayId() == displayId) { - return mPointerIconDisplayContext; - } - - // Create and cache context for non-default display. - mPointerIconDisplayContext = getContextForDisplay(displayId); - - // Fall back to default display if the requested displayId does not exist. - if (mPointerIconDisplayContext == null) { - mPointerIconDisplayContext = getContextForDisplay(Display.DEFAULT_DISPLAY); - } - return mPointerIconDisplayContext; - } - - @Nullable private Context getContextForDisplay(int displayId) { if (displayId == Display.INVALID_DISPLAY) { - return null; + // Fallback to using the default context. + return mContext; } - if (mContext.getDisplay().getDisplayId() == displayId) { + if (displayId == mContext.getDisplay().getDisplayId()) { return mContext; } - final DisplayManager displayManager = Objects.requireNonNull( - mContext.getSystemService(DisplayManager.class)); - final Display display = displayManager.getDisplay(displayId); - if (display == null) { - return null; - } + Context displayContext = mDisplayContexts.get(displayId); + if (displayContext == null) { + final DisplayManager displayManager = Objects.requireNonNull( + mContext.getSystemService(DisplayManager.class)); + final Display display = displayManager.getDisplay(displayId); + if (display == null) { + // Fallback to using the default context. + return mContext; + } - return mContext.createDisplayContext(display); + displayContext = mContext.createDisplayContext(display); + mDisplayContexts.put(displayId, displayContext); + } + return displayContext; } // Native callback. diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index f852b8173f30..097daf2e51e6 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 @@ public final class NotificationAttentionHelper { 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 @@ public final class NotificationAttentionHelper { 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 f1dc2849a391..b9aa28e58879 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.PackageInfo; 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 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { 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 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { 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 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { 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 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { 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 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { 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 c0596bb10823..796edde7d9bd 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 @@ public class UserManagerService extends IUserManager.Stub { @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 @@ public class UserManagerService extends IUserManager.Stub { * 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 @@ public class UserManagerService extends IUserManager.Stub { @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 @@ public class UserManagerService extends IUserManager.Stub { */ @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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { } 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 @@ public class UserManagerService extends IUserManager.Stub { @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 6ed2d3126455..a9e5a5434189 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 @@ public class PackageInfoUtils { 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/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index 6f754391f1ca..35717af962ef 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -691,16 +691,26 @@ public final class HintManagerService extends SystemService { TextUtils.formatSimple("Actual total duration (%d) should be greater than 0", workDuration.getActualTotalDurationNanos())); } - if (workDuration.getActualCpuDurationNanos() <= 0) { + if (workDuration.getActualCpuDurationNanos() < 0) { throw new IllegalArgumentException( - TextUtils.formatSimple("Actual CPU duration (%d) should be greater than 0", + TextUtils.formatSimple( + "Actual CPU duration (%d) should be greater than or equal to 0", workDuration.getActualCpuDurationNanos())); } if (workDuration.getActualGpuDurationNanos() < 0) { throw new IllegalArgumentException( - TextUtils.formatSimple("Actual GPU duration (%d) should be non negative", + TextUtils.formatSimple( + "Actual GPU duration (%d) should greater than or equal to 0", workDuration.getActualGpuDurationNanos())); } + if (workDuration.getActualCpuDurationNanos() + + workDuration.getActualGpuDurationNanos() <= 0) { + throw new IllegalArgumentException( + TextUtils.formatSimple( + "The actual CPU duration (%d) and the actual GPU duration (%d)" + + " should not both be 0", workDuration.getActualCpuDurationNanos(), + workDuration.getActualGpuDurationNanos())); + } } private void onProcStateChanged(boolean updateAllowed) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 8c27bb80d337..118985a729aa 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -145,7 +145,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Predicate; @@ -613,8 +612,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } private final Context mContext; - private final AtomicBoolean mIsInitialBinding = new AtomicBoolean(true); - private final ServiceThread mHandlerThread; + private boolean mInitialUserSwitch = true; + private ServiceThread mHandlerThread; private final WindowManagerInternal mWindowManagerInternal; private final PackageManagerInternal mPackageManagerInternal; private final IPackageManager mIPackageManager; @@ -1474,12 +1473,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public WallpaperManagerService(Context context) { if (DEBUG) Slog.v(TAG, "WallpaperService startup"); mContext = context; - if (Flags.bindWallpaperServiceOnItsOwnThreadDuringAUserSwitch()) { - mHandlerThread = new ServiceThread(TAG, THREAD_PRIORITY_FOREGROUND, true /*allowIo*/); - mHandlerThread.start(); - } else { - mHandlerThread = null; - } mShuttingDown = false; mImageWallpaper = ComponentName.unflattenFromString( context.getResources().getString(R.string.image_wallpaper_component)); @@ -1803,6 +1796,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub switchWallpaper(lockWallpaper, null); } switchWallpaper(systemWallpaper, reply); + mInitialUserSwitch = false; } // Offload color extraction to another thread since switchUser will be called @@ -3326,11 +3320,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub com.android.internal.R.bool.config_wallpaperTopApp)) { bindFlags |= Context.BIND_SCHEDULE_LIKE_TOP_APP; } - Handler handler = Flags.bindWallpaperServiceOnItsOwnThreadDuringAUserSwitch() - && !mIsInitialBinding.compareAndSet(true, false) - ? mHandlerThread.getThreadHandler() : mContext.getMainThreadHandler(); - boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn, bindFlags, handler, - new UserHandle(serviceUserId)); + boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn, bindFlags, + getHandlerForBindingWallpaperLocked(), new UserHandle(serviceUserId)); if (!bindSuccess) { String msg = "Unable to bind service: " + componentName; if (fromUser) { @@ -3358,6 +3349,20 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return true; } + private Handler getHandlerForBindingWallpaperLocked() { + if (!Flags.bindWallpaperServiceOnItsOwnThreadDuringAUserSwitch()) { + return mContext.getMainThreadHandler(); + } + if (mInitialUserSwitch) { + return mContext.getMainThreadHandler(); + } + if (mHandlerThread == null) { + mHandlerThread = new ServiceThread(TAG, THREAD_PRIORITY_FOREGROUND, true /*allowIo*/); + mHandlerThread.start(); + } + return mHandlerThread.getThreadHandler(); + } + // Updates tracking of the currently bound wallpapers. private void updateCurrentWallpapers(WallpaperData newWallpaper) { if (newWallpaper.userId != mCurrentUserId || newWallpaper.equals(mFallbackWallpaper)) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index b1d04c9ddb16..7b5975928308 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; 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.ActivityMetricsLogger.TransitionInfoSnapshot; 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; @@ -960,6 +962,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // without security checks final Binder shareableActivityToken = new Binder(); + // Token for accessing the initial caller who started the activity. + final IBinder initialCallerInfoAccessToken = new Binder(); + // Tracking cookie for the launch of this activity and it's task. IBinder mLaunchCookie; @@ -986,6 +991,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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 +2231,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // No such property name. } mAppActivityEmbeddingSplitsEnabled = appActivityEmbeddingEnabled; + mAllowUntrustedEmbeddingStateSharing = getAllowUntrustedEmbeddingStateSharingProperty(); mOptInOnBackInvoked = WindowOnBackInvokedDispatcher .isOnBackInvokedCallbackEnabled(info, info.applicationInfo, @@ -3078,6 +3087,32 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index ec0e3e7f178f..49df39664b1c 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -930,7 +930,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(), results, newIntents, r.takeSceneTransitionInfo(), isTransitionForward, proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController, - r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken); + r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken, + r.initialCallerInfoAccessToken); // Set desired final state. final ActivityLifecycleItem lifecycleItem; @@ -943,7 +944,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // Schedule transaction. mService.getLifecycleManager().scheduleTransactionAndLifecycleItems( - proc.getThread(), launchActivityItem, lifecycleItem); + proc.getThread(), launchActivityItem, lifecycleItem, + // Immediately dispatch the transaction, so that if it fails, the server can + // restart the process and retry now. + true /* shouldDispatchImmediately */); if (procConfig.seq > mRootWindowContainer.getConfiguration().seq) { // If the seq is increased, there should be something changed (e.g. registered diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java index c7df83a9ccf9..5b4fb3e6971f 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 @@ class ClientLifecycleManager { 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 @@ -106,7 +110,8 @@ class ClientLifecycleManager { final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client); clientTransaction.addTransactionItem(transactionItem); - onClientTransactionItemScheduled(clientTransaction); + onClientTransactionItemScheduled(clientTransaction, + false /* shouldDispatchImmediately */); } else { // TODO(b/260873529): cleanup after launch. final ClientTransaction clientTransaction = ClientTransaction.obtain(client); @@ -119,14 +124,30 @@ class ClientLifecycleManager { } } + void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client, + @NonNull ClientTransactionItem transactionItem, + @NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException { + scheduleTransactionAndLifecycleItems(client, transactionItem, lifecycleItem, + false /* shouldDispatchImmediately */); + } + /** * Schedules a single transaction item with a lifecycle request, delivery to client application. + * + * @param shouldDispatchImmediately whether or not to dispatch the transaction immediately. This + * should only be {@code true} when it is important to know the + * result of dispatching immediately. For example, when cold + * launches an app, the server needs to know if the transaction + * is dispatched successfully, and may restart the process if + * not. + * * @throws RemoteException * @see ClientTransactionItem */ void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client, @NonNull ClientTransactionItem transactionItem, - @NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException { + @NonNull ActivityLifecycleItem lifecycleItem, + boolean shouldDispatchImmediately) throws RemoteException { // The behavior is different depending on the flag. // When flag is on, we wait until RootWindowContainer#performSurfacePlacementNoTrace to // dispatch all pending transactions at once. @@ -135,7 +156,7 @@ class ClientLifecycleManager { clientTransaction.addTransactionItem(transactionItem); clientTransaction.addTransactionItem(lifecycleItem); - onClientTransactionItemScheduled(clientTransaction); + onClientTransactionItemScheduled(clientTransaction, shouldDispatchImmediately); } else { // TODO(b/260873529): cleanup after launch. final ClientTransaction clientTransaction = ClientTransaction.obtain(client); @@ -157,7 +178,8 @@ class ClientLifecycleManager { 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); + // TODO(b/323801078): apply cleanup for individual transaction item if needed. } } mPendingTransactions.clear(); @@ -194,8 +216,9 @@ class ClientLifecycleManager { /** Must only be called with WM lock. */ private void onClientTransactionItemScheduled( - @NonNull ClientTransaction clientTransaction) throws RemoteException { - if (shouldDispatchPendingTransactionsImmediately()) { + @NonNull ClientTransaction clientTransaction, + boolean shouldDispatchImmediately) throws RemoteException { + if (shouldDispatchImmediately || shouldDispatchPendingTransactionsImmediately()) { // Dispatch the pending transaction immediately. mPendingTransactions.remove(clientTransaction.getClient().asBinder()); scheduleTransaction(clientTransaction); diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index d9dda4aeb96a..cd968067e289 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 @@ class InsetsSourceProvider { 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/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 0e2d3d151db0..edf9da1e0bf5 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -58,6 +58,8 @@ import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED; import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED; import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; @@ -927,8 +929,7 @@ final class LetterboxUiController { } void updateLetterboxSurface(WindowState winHint, Transaction t) { - final WindowState w = mActivityRecord.findMainWindow(); - if (w != winHint && winHint != null && w != null) { + if (shouldNotLayoutLetterbox(winHint)) { return; } layoutLetterbox(winHint); @@ -937,20 +938,11 @@ final class LetterboxUiController { } } - void layoutLetterbox(WindowState winHint) { - final WindowState w = mActivityRecord.findMainWindow(); - if (w == null || winHint != null && w != winHint) { + void layoutLetterbox(WindowState w) { + if (shouldNotLayoutLetterbox(w)) { return; } updateRoundedCornersIfNeeded(w); - // If there is another main window that is not an application-starting window, we should - // update rounded corners for it as well, to avoid flickering rounded corners. - final WindowState nonStartingAppW = mActivityRecord.findMainWindow( - /* includeStartingApp= */ false); - if (nonStartingAppW != null && nonStartingAppW != w) { - updateRoundedCornersIfNeeded(nonStartingAppW); - } - updateWallpaperForLetterbox(w); if (shouldShowLetterboxUi(w)) { if (mLetterbox == null) { @@ -1023,6 +1015,18 @@ final class LetterboxUiController { return mActivityRecord.getSurfaceControl(); } + private static boolean shouldNotLayoutLetterbox(WindowState w) { + if (w == null) { + return true; + } + final int type = w.mAttrs.type; + // Allow letterbox to be displayed early for base application or application starting + // windows even if it is not on the top z order to prevent flickering when the + // letterboxed window is brought to the top + return (type != TYPE_BASE_APPLICATION && type != TYPE_APPLICATION_STARTING) + || w.mAnimatingExit; + } + private boolean shouldLetterboxHaveRoundedCorners() { // TODO(b/214030873): remove once background is drawn for transparent activities // Letterbox shouldn't have rounded corners if the activity is transparent diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 81fe453cc692..8d054db8994a 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 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr + " 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 44cd23d037c6..09c4f7ca3c6c 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 @@ class WindowStateAnimator { } 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 129efc630be6..005cad125115 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.os.UserManager; 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.pm.permission.PermissionManagerService; 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 @@ public class CrossProfileAppsServiceImplTest { private IApplicationThread mIApplicationThread; private SparseArray<Boolean> mUserEnabled = new SparseArray<>(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Before public void initCrossProfileAppsServiceImpl() { @@ -123,8 +127,9 @@ public class CrossProfileAppsServiceImplTest { 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 93bdeaeb90e3..d538f255c5aa 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 @@ class ParsedProcessTest : ParcelableComponentTest(ParsedProcess::class, ParsedPr 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 42bcb3367933..b142334db9e9 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.Context; 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.MessageQueue; 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 @@ public class DisplayManagerServiceTest { private int mPreferredHdrOutputType; + private Handler mPowerHandler; + + private UserManager mUserManager; + private final DisplayManagerService.Injector mShortMockedInjector = new DisplayManagerService.Injector() { @Override @@ -370,11 +377,14 @@ public class DisplayManagerServiceTest { 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 @@ public class DisplayManagerServiceTest { 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 class DisplayManagerServiceTest { 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 88a9758991f4..57b86324e171 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 @@ public final class DisplayPowerControllerTest { 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 @@ public final class DisplayPowerControllerTest { 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 @@ public final class DisplayPowerControllerTest { 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 @@ public final class DisplayPowerControllerTest { 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 @@ public final class DisplayPowerControllerTest { 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 @@ public final class DisplayPowerControllerTest { 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 @@ public final class DisplayPowerControllerTest { 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 @@ public final class DisplayPowerControllerTest { 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 @@ public final class DisplayPowerControllerTest { 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 @@ public final class DisplayPowerControllerTest { .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 2d0c3fdb6352..289d54b86c7d 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 @@ package com.android.server.display.brightness; 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 @@ public final class DisplayBrightnessControllerTest { 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 78ec2ff31161..5408e1143330 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 @@ public class AutomaticBrightnessStrategyTest { 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 @@ public class AutomaticBrightnessStrategyTest { 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 @@ public class AutomaticBrightnessStrategyTest { 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 @@ public class AutomaticBrightnessStrategyTest { 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 @@ public class AutomaticBrightnessStrategyTest { 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 @@ public class AutomaticBrightnessStrategyTest { 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 @@ public class AutomaticBrightnessStrategyTest { 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 @@ public class AutomaticBrightnessStrategyTest { 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 eaf0ffdc3cbd..3355a6cff2ad 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.ACTIVE_INDEX; 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.PackageManager; 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 com.android.server.usage.AppStandbyInternal; 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 @@ public class JobSchedulerServiceTest { @Mock private PackageManagerInternal mPackageManagerInternal; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private class TestJobSchedulerService extends JobSchedulerService { TestJobSchedulerService(Context context) { super(context); @@ -1711,29 +1720,283 @@ public class JobSchedulerServiceTest { /** Tests that rare job batching works as expected. */ @Test - public void testRareJobBatching() { + 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.MIN_READY_NON_ACTIVE_JOBS_COUNT = 5; - mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS; + 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( - "testRareJobBatching", + "testConnectivityJobBatching", createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)); - job.setStandbyBucket(RARE_INDEX); + 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()); + 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_NON_ACTIVE_JOBS_COUNT = 5; + mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS; // 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 @@ public class JobSchedulerServiceTest { 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 @@ public class JobSchedulerServiceTest { 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 @@ public class JobSchedulerServiceTest { - 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 4958f1c1c214..f27d0c2ce5a9 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.inOrder; 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.PackageManager; 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.junit.MockitoJUnitRunner; 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 @@ public class ConnectivityControllerTest { 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/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java index 52726caba2bf..b224773a9eb1 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java @@ -42,6 +42,7 @@ import static org.mockito.Mockito.when; import static org.mockito.hamcrest.MockitoHamcrest.argThat; import android.accessibilityservice.MagnificationConfig; +import android.animation.TimeAnimator; import android.animation.ValueAnimator; import android.content.BroadcastReceiver; import android.content.Context; @@ -52,11 +53,15 @@ import android.graphics.Region; import android.hardware.display.DisplayManagerInternal; import android.os.Looper; import android.os.UserHandle; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.test.mock.MockContentResolver; import android.view.DisplayInfo; import android.view.MagnificationSpec; import android.view.accessibility.MagnificationAnimationCallback; +import android.widget.Scroller; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -65,6 +70,7 @@ import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityTraceManager; +import com.android.server.accessibility.Flags; import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks; @@ -74,6 +80,7 @@ import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -104,6 +111,9 @@ public class FullScreenMagnificationControllerTest { static final int INVALID_DISPLAY = 2; private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + final FullScreenMagnificationController.ControllerContext mMockControllerCtx = mock(FullScreenMagnificationController.ControllerContext.class); final Context mMockContext = mock(Context.class); @@ -118,6 +128,7 @@ public class FullScreenMagnificationControllerTest { private MagnificationScaleProvider mScaleProvider; private MockContentResolver mResolver; private final MagnificationThumbnail mMockThumbnail = mock(MagnificationThumbnail.class); + private final Scroller mMockScroller = mock(Scroller.class); private final ArgumentCaptor<MagnificationConfig> mConfigCaptor = ArgumentCaptor.forClass( MagnificationConfig.class); @@ -126,6 +137,8 @@ public class FullScreenMagnificationControllerTest { ValueAnimator.AnimatorUpdateListener mTargetAnimationListener; ValueAnimator.AnimatorListener mStateListener; + private final TimeAnimator mMockTimeAnimator = mock(TimeAnimator.class); + FullScreenMagnificationController mFullScreenMagnificationController; public DisplayManagerInternal mDisplayManagerInternalMock = mock(DisplayManagerInternal.class); @@ -134,7 +147,8 @@ public class FullScreenMagnificationControllerTest { @Before public void setUp() { - Looper looper = InstrumentationRegistry.getContext().getMainLooper(); + Context realContext = InstrumentationRegistry.getContext(); + Looper looper = realContext.getMainLooper(); // Pretending ID of the Thread associated with looper as main thread ID in controller when(mMockContext.getMainLooper()).thenReturn(looper); when(mMockControllerCtx.getContext()).thenReturn(mMockContext); @@ -168,7 +182,9 @@ public class FullScreenMagnificationControllerTest { mRequestObserver, mScaleProvider, () -> mMockThumbnail, - ConcurrentUtils.DIRECT_EXECUTOR); + ConcurrentUtils.DIRECT_EXECUTOR, + () -> mMockScroller, + () -> mMockTimeAnimator); } @After @@ -428,7 +444,7 @@ public class FullScreenMagnificationControllerTest { mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); mStateListener.onAnimationEnd(mMockValueAnimator); verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec))); - verify(mAnimationCallback).onResult(true); + verify(mAnimationCallback).onResult(eq(true), any()); } @Test @@ -451,7 +467,7 @@ public class FullScreenMagnificationControllerTest { mMessageCapturingHandler.sendAllMessages(); verify(mMockValueAnimator, never()).start(); - verify(mAnimationCallback).onResult(true); + verify(mAnimationCallback).onResult(eq(true), any()); } @Test @@ -653,6 +669,85 @@ public class FullScreenMagnificationControllerTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE) + public void testStartFling_whileMagnifying_flings() throws InterruptedException { + for (int i = 0; i < DISPLAY_COUNT; i++) { + startFling_whileMagnifying_flings(i); + resetMockWindowManager(); + } + } + + private void startFling_whileMagnifying_flings(int displayId) throws InterruptedException { + register(displayId); + PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER; + float scale = 2.0f; + // First zoom in + assertTrue(mFullScreenMagnificationController + .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false, + SERVICE_ID_1)); + mMessageCapturingHandler.sendAllMessages(); + + PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER; + PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale); + mFullScreenMagnificationController.startFling(displayId, + /* xPixelsPerSecond= */ 400f, + /* yPixelsPerSecond= */ 100f, + SERVICE_ID_1 + ); + mMessageCapturingHandler.sendAllMessages(); + + verify(mMockTimeAnimator).start(); + verify(mMockScroller).fling( + /* startX= */ eq((int) newOffsets.x / 2), + /* startY= */ eq((int) newOffsets.y / 2), + /* velocityX= */ eq(400), + /* velocityY= */ eq(100), + /* minX= */ anyInt(), + /* minY= */ anyInt(), + /* maxX= */ anyInt(), + /* maxY= */ anyInt() + ); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE) + public void testStopFling_whileMagnifyingAndFlinging_stops() throws InterruptedException { + for (int i = 0; i < DISPLAY_COUNT; i++) { + stopFling_whileMagnifyingAndFlinging_stops(i); + resetMockWindowManager(); + } + } + + private void stopFling_whileMagnifyingAndFlinging_stops(int displayId) + throws InterruptedException { + register(displayId); + PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER; + float scale = 2.0f; + PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale); + // First zoom in + assertTrue(mFullScreenMagnificationController + .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false, + SERVICE_ID_1)); + mMessageCapturingHandler.sendAllMessages(); + + mFullScreenMagnificationController.startFling(displayId, + /* xPixelsPerSecond= */ 400f, + /* yPixelsPerSecond= */ 100f, + SERVICE_ID_1 + ); + mMessageCapturingHandler.sendAllMessages(); + + when(mMockTimeAnimator.isRunning()).thenReturn(true); + + mFullScreenMagnificationController.cancelFling(displayId, SERVICE_ID_1); + mMessageCapturingHandler.sendAllMessages(); + + verify(mMockTimeAnimator).cancel(); + // Can't verify forceFinished() because it's final +// verify(mMockScroller).forceFinished(eq(true)); + } + + @Test public void testGetIdOfLastServiceToChange_returnsCorrectValue() { for (int i = 0; i < DISPLAY_COUNT; i++) { getIdOfLastServiceToChange_returnsCorrectValue(i); @@ -736,7 +831,7 @@ public class FullScreenMagnificationControllerTest { verify(mRequestObserver, never()).onFullScreenMagnificationChanged(eq(displayId), any(Region.class), any(MagnificationConfig.class)); - verify(mAnimationCallback).onResult(true); + verify(mAnimationCallback).onResult(eq(true), any()); } @Test @@ -772,7 +867,7 @@ public class FullScreenMagnificationControllerTest { mMessageCapturingHandler.sendAllMessages(); // Verify expected actions. - verify(mAnimationCallback).onResult(false); + verify(mAnimationCallback).onResult(eq(false), any()); verify(mMockValueAnimator).start(); verify(mMockValueAnimator).cancel(); @@ -782,7 +877,7 @@ public class FullScreenMagnificationControllerTest { mStateListener.onAnimationEnd(mMockValueAnimator); checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId); - verify(lastAnimationCallback).onResult(true); + verify(lastAnimationCallback).onResult(eq(true), any()); } @Test @@ -1379,6 +1474,8 @@ public class FullScreenMagnificationControllerTest { private void resetMockWindowManager() { Mockito.reset(mMockWindowManager); Mockito.reset(mMockThumbnail); + Mockito.reset(mMockScroller); + Mockito.reset(mMockTimeAnimator); initMockWindowManager(); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 71d64cf4c8d4..8c0d44c46814 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -31,6 +31,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; +import static org.mockito.AdditionalMatchers.gt; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.any; @@ -40,10 +41,13 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.animation.TimeAnimator; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.content.pm.PackageManager; @@ -65,6 +69,7 @@ import android.util.DebugUtils; import android.view.InputDevice; import android.view.MotionEvent; import android.view.ViewConfiguration; +import android.widget.Scroller; import androidx.test.filters.FlakyTest; import androidx.test.runner.AndroidJUnit4; @@ -79,6 +84,8 @@ import com.android.server.testutils.OffsettableClock; import com.android.server.testutils.TestHandler; import com.android.server.wm.WindowManagerInternal; +import com.google.common.truth.Truth; + import org.junit.After; import org.junit.Before; import org.junit.Ignore; @@ -186,6 +193,8 @@ public class FullScreenMagnificationGestureHandlerTest { @Rule public final TestableContext mContext = new TestableContext(getInstrumentation().getContext()); + private final Scroller mMockScroller = spy(new Scroller(mContext)); + private OffsettableClock mClock; private FullScreenMagnificationGestureHandler mMgh; private TestHandler mHandler; @@ -218,18 +227,21 @@ public class FullScreenMagnificationGestureHandlerTest { Settings.Secure.putFloatForUser(mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f, UserHandle.USER_SYSTEM); - mFullScreenMagnificationController = new FullScreenMagnificationController( - mockController, - new Object(), - mMagnificationInfoChangedCallback, - new MagnificationScaleProvider(mContext), - () -> null, - ConcurrentUtils.DIRECT_EXECUTOR) { - @Override - public boolean magnificationRegionContains(int displayId, float x, float y) { - return true; - } - }; + mFullScreenMagnificationController = + new FullScreenMagnificationController( + mockController, + new Object(), + mMagnificationInfoChangedCallback, + new MagnificationScaleProvider(mContext), + () -> null, + ConcurrentUtils.DIRECT_EXECUTOR, + () -> mMockScroller, + TimeAnimator::new) { + @Override + public boolean magnificationRegionContains(int displayId, float x, float y) { + return true; + } + }; doAnswer((Answer<Void>) invocationOnMock -> { Object[] args = invocationOnMock.getArguments(); @@ -263,11 +275,20 @@ public class FullScreenMagnificationGestureHandlerTest { @NonNull private FullScreenMagnificationGestureHandler newInstance(boolean detectSingleFingerTripleTap, boolean detectTwoFingerTripleTap, boolean detectShortcutTrigger) { - FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler( - mContext, mFullScreenMagnificationController, mMockTraceManager, mMockCallback, - detectSingleFingerTripleTap, detectTwoFingerTripleTap, detectShortcutTrigger, - mWindowMagnificationPromptController, DISPLAY_0, - mMockFullScreenMagnificationVibrationHelper, mMockMagnificationLogger); + FullScreenMagnificationGestureHandler h = + new FullScreenMagnificationGestureHandler( + mContext, + mFullScreenMagnificationController, + mMockTraceManager, + mMockCallback, + detectSingleFingerTripleTap, + detectTwoFingerTripleTap, + detectShortcutTrigger, + mWindowMagnificationPromptController, + DISPLAY_0, + mMockFullScreenMagnificationVibrationHelper, + mMockMagnificationLogger, + ViewConfiguration.get(mContext)); if (isWatch()) { h.setSinglePanningEnabled(true); } else { @@ -724,7 +745,7 @@ public class FullScreenMagnificationGestureHandlerTest { //The minimum movement to transit to panningState. final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop(); pointer2.offset(sWipeMinDistance + 1, 0); - send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1)); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0)); fastForward(ViewConfiguration.getTapTimeout()); assertIn(STATE_PANNING); @@ -743,7 +764,7 @@ public class FullScreenMagnificationGestureHandlerTest { //The minimum movement to transit to panningState. final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop(); pointer2.offset(sWipeMinDistance + 1, 0); - send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1)); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0)); fastForward(ViewConfiguration.getTapTimeout()); assertIn(STATE_PANNING); @@ -762,7 +783,7 @@ public class FullScreenMagnificationGestureHandlerTest { //The minimum movement to transit to panningState. final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop(); pointer2.offset(sWipeMinDistance + 1, 0); - send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1)); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0)); assertIn(STATE_PANNING); returnToNormalFrom(STATE_PANNING); @@ -780,7 +801,7 @@ public class FullScreenMagnificationGestureHandlerTest { //The minimum movement to transit to panningState. final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop(); pointer2.offset(sWipeMinDistance + 1, 0); - send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1)); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0)); assertIn(STATE_PANNING); returnToNormalFrom(STATE_PANNING); @@ -972,6 +993,198 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + public void singleFinger_testScrollAfterMagnified_startsFling() { + assumeTrue(mMgh.mIsSinglePanningEnabled); + goFromStateIdleTo(STATE_ACTIVATED); + + swipeAndHold(); + fastForward(20); + swipe(DEFAULT_POINT, new PointF(DEFAULT_X * 2, DEFAULT_Y * 2), /* durationMs= */ 20); + + verify(mMockScroller).fling( + /* startX= */ anyInt(), + /* startY= */ anyInt(), + // The system fling velocity is configurable and hard to test across devices, so as + // long as there is some fling velocity, we are happy. + /* velocityX= */ gt(1000), + /* velocityY= */ gt(1000), + /* minX= */ anyInt(), + /* minY= */ anyInt(), + /* maxX= */ anyInt(), + /* maxY= */ anyInt() + ); + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE) + public void testTwoFingerPanDiagonalAfterMagnified_doesNotFlingXY() + throws InterruptedException { + goFromStateIdleTo(STATE_ACTIVATED); + PointF pointer1 = DEFAULT_POINT; + PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y); + + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, new PointF[]{pointer1, pointer2}, 1)); + + // first move triggers the panning state + pointer1.offset(100, 100); + pointer2.offset(100, 100); + fastForward(20); + send(pointerEvent(ACTION_MOVE, new PointF[]{pointer1, pointer2}, 0)); + + // second move actually pans + pointer1.offset(100, 100); + pointer2.offset(100, 100); + fastForward(20); + send(pointerEvent(ACTION_MOVE, new PointF[]{pointer1, pointer2}, 0)); + pointer1.offset(100, 100); + pointer2.offset(100, 100); + fastForward(20); + send(pointerEvent(ACTION_MOVE, new PointF[]{pointer1, pointer2}, 0)); + + assertIn(STATE_PANNING); + mHandler.timeAdvance(); + returnToNormalFrom(STATE_PANNING); + + mHandler.timeAdvance(); + + verifyNoMoreInteractions(mMockScroller); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE) + public void testTwoFingerPanDiagonalAfterMagnified_startsFlingXY() + throws InterruptedException { + goFromStateIdleTo(STATE_ACTIVATED); + PointF pointer1 = DEFAULT_POINT; + PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y); + + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1)); + + // first move triggers the panning state + pointer1.offset(100, 100); + pointer2.offset(100, 100); + fastForward(20); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0)); + + // second move actually pans + pointer1.offset(100, 100); + pointer2.offset(100, 100); + fastForward(20); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0)); + pointer1.offset(100, 100); + pointer2.offset(100, 100); + fastForward(20); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0)); + + assertIn(STATE_PANNING); + mHandler.timeAdvance(); + returnToNormalFrom(STATE_PANNING); + + mHandler.timeAdvance(); + + verify(mMockScroller).fling( + /* startX= */ anyInt(), + /* startY= */ anyInt(), + // The system fling velocity is configurable and hard to test across devices, so as + // long as there is some fling velocity, we are happy. + /* velocityX= */ gt(1000), + /* velocityY= */ gt(1000), + /* minX= */ anyInt(), + /* minY= */ anyInt(), + /* maxX= */ anyInt(), + /* maxY= */ anyInt() + ); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE) + public void testTwoFingerPanRightAfterMagnified_startsFlingXOnly() + throws InterruptedException { + goFromStateIdleTo(STATE_ACTIVATED); + PointF pointer1 = DEFAULT_POINT; + PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y); + + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1)); + + // first move triggers the panning state + pointer1.offset(100, 0); + pointer2.offset(100, 0); + fastForward(20); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0)); + + // second move actually pans + pointer1.offset(100, 0); + pointer2.offset(100, 0); + fastForward(20); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0)); + pointer1.offset(100, 0); + pointer2.offset(100, 0); + fastForward(20); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0)); + + assertIn(STATE_PANNING); + mHandler.timeAdvance(); + returnToNormalFrom(STATE_PANNING); + + mHandler.timeAdvance(); + + verify(mMockScroller).fling( + /* startX= */ anyInt(), + /* startY= */ anyInt(), + // The system fling velocity is configurable and hard to test across devices, so as + // long as there is some fling velocity, we are happy. + /* velocityX= */ gt(100), + /* velocityY= */ eq(0), + /* minX= */ anyInt(), + /* minY= */ anyInt(), + /* maxX= */ anyInt(), + /* maxY= */ anyInt() + ); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE) + public void testDownEvent_cancelsFling() + throws InterruptedException { + goFromStateIdleTo(STATE_ACTIVATED); + PointF pointer1 = DEFAULT_POINT; + PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y); + + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1)); + + // first move triggers the panning state + pointer1.offset(100, 0); + pointer2.offset(100, 0); + fastForward(20); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0)); + + // second move actually pans + pointer1.offset(100, 0); + pointer2.offset(100, 0); + fastForward(20); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0)); + pointer1.offset(100, 0); + pointer2.offset(100, 0); + fastForward(20); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0)); + + assertIn(STATE_PANNING); + mHandler.timeAdvance(); + returnToNormalFrom(STATE_PANNING); + + mHandler.timeAdvance(); + + send(downEvent()); + mHandler.timeAdvance(); + + verify(mMockScroller).forceFinished(eq(true)); + } + + @Test public void testShortcutTriggered_invokeShowWindowPromptAction() { goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED); @@ -1397,8 +1610,11 @@ public class FullScreenMagnificationGestureHandlerTest { send(upEvent()); } - private void swipe(PointF start, PointF end) { - swipeAndHold(start, end); + private void swipe(PointF start, PointF end, int durationMs) { + var mid = new PointF(start.x + (end.x - start.x) / 2f, start.y + (end.y - start.y) / 2f); + swipeAndHold(start, mid); + fastForward(durationMs); + send(moveEvent(end.x - start.x / 10f, end.y - start.y / 10f)); send(upEvent(end.x, end.y)); } @@ -1491,9 +1707,18 @@ public class FullScreenMagnificationGestureHandlerTest { private MotionEvent pointerEvent(int action, float x, float y) { - return pointerEvent(action, new PointF[] {DEFAULT_POINT, new PointF(x, y)}, 1); + return pointerEvent(action, new PointF[] {DEFAULT_POINT, new PointF(x, y)}, + (action == ACTION_POINTER_UP || action == ACTION_POINTER_DOWN) ? 1 : 0); } + /** + * Create a pointer event simulating the given pointer positions. + * + * @param action the action + * @param pointersPosition positions of the pointers + * @param changedIndex the index of the pointer associated with the ACTION_POINTER_UP or + * ACTION_POINTER_DOWN action. Must be 0 for all other actions. + */ private MotionEvent pointerEvent(int action, PointF[] pointersPosition, int changedIndex) { final MotionEvent.PointerProperties[] PointerPropertiesArray = new MotionEvent.PointerProperties[pointersPosition.length]; @@ -1513,12 +1738,12 @@ public class FullScreenMagnificationGestureHandlerTest { pointerCoordsArray[i] = pointerCoords; } - action += (changedIndex << ACTION_POINTER_INDEX_SHIFT); + var actionWithPointer = action | (changedIndex << ACTION_POINTER_INDEX_SHIFT); - return MotionEvent.obtain( + var event = MotionEvent.obtain( /* downTime */ mClock.now(), /* eventTime */ mClock.now(), - /* action */ action, + /* action */ actionWithPointer, /* pointerCount */ pointersPosition.length, /* pointerProperties */ PointerPropertiesArray, /* pointerCoords */ pointerCoordsArray, @@ -1530,6 +1755,14 @@ public class FullScreenMagnificationGestureHandlerTest { /* edgeFlags */ 0, /* source */ InputDevice.SOURCE_TOUCHSCREEN, /* flags */ 0); + + Truth.assertThat(event.getActionIndex()).isEqualTo(changedIndex); + Truth.assertThat(event.getActionMasked()).isEqualTo(action); + if (action == ACTION_DOWN) { + Truth.assertThat(changedIndex).isEqualTo(0); + } + + return event; } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index 28d07f995ad3..cd904eb562de 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -42,6 +42,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.accessibilityservice.MagnificationConfig; +import android.animation.TimeAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.content.pm.PackageManager; @@ -59,6 +60,7 @@ import android.view.Display; import android.view.DisplayInfo; import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.accessibility.MagnificationAnimationCallback; +import android.widget.Scroller; import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; @@ -119,6 +121,8 @@ public class MagnificationControllerTest { @Mock private ValueAnimator mValueAnimator; @Mock + private TimeAnimator mTimeAnimator; + @Mock private MessageCapturingHandler mMessageCapturingHandler; private FullScreenMagnificationController mScreenMagnificationController; @@ -195,14 +199,17 @@ public class MagnificationControllerTest { LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal); - mScreenMagnificationController = spy(new FullScreenMagnificationController( - mControllerCtx, - new Object(), - mScreenMagnificationInfoChangedCallbackDelegate, - mScaleProvider, - () -> null, - ConcurrentUtils.DIRECT_EXECUTOR - )); + mScreenMagnificationController = + spy( + new FullScreenMagnificationController( + mControllerCtx, + new Object(), + mScreenMagnificationInfoChangedCallbackDelegate, + mScaleProvider, + () -> null, + ConcurrentUtils.DIRECT_EXECUTOR, + () -> new Scroller(mContext), + () -> mTimeAnimator)); mScreenMagnificationController.register(TEST_DISPLAY); mMagnificationConnectionManager = spy( 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 e7da26ea38b1..98789ac96e98 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 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { 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 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { 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 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { 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 a410702e202d..9ad2652e9c63 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 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav 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 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav 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 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav 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 213e05eb788e..2b07d33ea541 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 @@ package com.android.server.job; 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 com.android.server.job.controllers.JobStatus; import org.junit.Test; -import java.util.ArrayList; -import java.util.List; import java.util.Random; public class PendingJobQueueTest { @@ -68,7 +67,7 @@ public class PendingJobQueueTest { @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 @@ public class PendingJobQueueTest { 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 @@ public class PendingJobQueueTest { @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 @@ public class PendingJobQueueTest { @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 @@ public class PendingJobQueueTest { @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 @@ public class PendingJobQueueTest { 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 @@ public class PendingJobQueueTest { @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 @@ public class PendingJobQueueTest { @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 @@ public class PendingJobQueueTest { 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 @@ public class PendingJobQueueTest { 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 @@ public class PendingJobQueueTest { 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 c81fbb443dce..cee738711473 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.os.UserManager; 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.pm.permission.PermissionManagerService; 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 @@ public class CrossProfileAppsServiceImplTest { private IApplicationThread mIApplicationThread; private SparseArray<Boolean> mUserEnabled = new SparseArray<>(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Before public void initCrossProfileAppsServiceImpl() { @@ -123,8 +127,9 @@ public class CrossProfileAppsServiceImplTest { 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 39cc6537c759..6decf438f95b 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 @@ public class UserManagerServiceCreateProfileTest { 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/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java index ffb3bce33a27..4ab9d3ecf624 100644 --- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -90,10 +90,12 @@ public class HintManagerServiceTest { private static final long[] DURATIONS_ZERO = new long[] {}; private static final long[] TIMESTAMPS_ZERO = new long[] {}; private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L}; - private static final WorkDuration[] WORK_DURATIONS_THREE = new WorkDuration[] { + private static final WorkDuration[] WORK_DURATIONS_FIVE = new WorkDuration[] { new WorkDuration(1L, 11L, 8L, 4L, 1L), new WorkDuration(2L, 13L, 8L, 6L, 2L), new WorkDuration(3L, 333333333L, 8L, 333333333L, 3L), + new WorkDuration(2L, 13L, 0L, 6L, 2L), + new WorkDuration(2L, 13L, 8L, 0L, 2L), }; @Mock private Context mContext; @@ -609,9 +611,9 @@ public class HintManagerServiceTest { .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); a.updateTargetWorkDuration(100L); - a.reportActualWorkDuration2(WORK_DURATIONS_THREE); + a.reportActualWorkDuration2(WORK_DURATIONS_FIVE); verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(), - eq(WORK_DURATIONS_THREE)); + eq(WORK_DURATIONS_FIVE)); assertThrows(IllegalArgumentException.class, () -> { a.reportActualWorkDuration2(new WorkDuration[] {}); @@ -627,7 +629,7 @@ public class HintManagerServiceTest { }); assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 4L, 1L)}); + a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 0L, 1L)}); }); assertThrows(IllegalArgumentException.class, () -> { @@ -648,7 +650,7 @@ public class HintManagerServiceTest { latch.await(); assertFalse(service.mUidObserver.isUidForeground(a.mUid)); - a.reportActualWorkDuration2(WORK_DURATIONS_THREE); + a.reportActualWorkDuration2(WORK_DURATIONS_FIVE); verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any()); } } diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java index 03cdbbd8397b..1c2dab867de2 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -39,6 +39,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.SystemConfig; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -710,6 +711,7 @@ public class SystemConfigTest { @Test @RequiresFlagsEnabled( android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) + @Ignore("b/323603510") public void getEnhancedConfirmationTrustedInstallers_returnsTrustedInstallers() throws IOException { String pkgName = "com.example.app"; diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java index 46e14d51b5fd..5f18f848df75 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java @@ -208,6 +208,21 @@ public class ClientLifecycleManagerTests extends SystemServiceTestsBase { } @Test + public void testScheduleTransactionAndLifecycleItems_shouldDispatchImmediately() + throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG); + spyOn(mWms.mWindowPlacerLocked); + doReturn(true).when(mWms.mWindowPlacerLocked).isTraversalScheduled(); + + // Use non binder client to get non-recycled ClientTransaction. + mLifecycleManager.scheduleTransactionAndLifecycleItems(mNonBinderClient, mTransactionItem, + mLifecycleItem, true /* shouldDispatchImmediately */); + + verify(mLifecycleManager).scheduleTransaction(any()); + assertTrue(mLifecycleManager.mPendingTransactions.isEmpty()); + } + + @Test public void testDispatchPendingTransactions() throws RemoteException { mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 0c1a9c3ab5b9..f42cdb88021e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -845,6 +845,30 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testLetterboxDisplayedForWindowBelow() { + setUpDisplaySizeWithApp(1000, 2500); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); + // Prepare two windows, one base app window below the splash screen + final WindowState appWindow = addWindowToActivity(mActivity); + final WindowState startWindow = addWindowToActivity(mActivity, TYPE_APPLICATION_STARTING); + spyOn(appWindow); + // Base app window is letterboxed for display cutout and splash screen is fullscreen + doReturn(true).when(appWindow).isLetterboxedForDisplayCutout(); + + mActivity.mRootWindowContainer.performSurfacePlacement(); + + assertEquals(2, mActivity.mChildren.size()); + // Splash screen is still the activity's main window + assertEquals(startWindow, mActivity.findMainWindow()); + assertFalse(startWindow.isLetterboxedForDisplayCutout()); + + final Rect letterboxInnerBounds = new Rect(); + mActivity.getLetterboxInnerBounds(letterboxInnerBounds); + // Letterboxed is still displayed for app window below splash screen + assertFalse(letterboxInnerBounds.isEmpty()); + } + + @Test public void testLetterboxFullscreenBoundsAndNotImeAttachable() { final int displayWidth = 2500; setUpDisplaySizeWithApp(displayWidth, 1000); @@ -4773,8 +4797,12 @@ public class SizeCompatTests extends WindowTestsBase { } private WindowState addWindowToActivity(ActivityRecord activity) { + return addWindowToActivity(activity, WindowManager.LayoutParams.TYPE_BASE_APPLICATION); + } + + private WindowState addWindowToActivity(ActivityRecord activity, int type) { final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); - params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; + params.type = type; params.setFitInsetsSides(0); params.setFitInsetsTypes(0); final TestWindowState w = new TestWindowState( 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 a88285ac4c8f..897a3da07473 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 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @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 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @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 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { 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); diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 536e458159d1..01448c3bcd9e 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -17,6 +17,7 @@ package android.telecom; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -43,6 +44,7 @@ import com.android.internal.os.SomeArgs; import com.android.internal.telecom.IConnectionService; import com.android.internal.telecom.IConnectionServiceAdapter; import com.android.internal.telecom.RemoteServiceCallback; +import com.android.server.telecom.flags.Flags; import java.util.ArrayList; import java.util.Collection; @@ -3235,27 +3237,27 @@ public abstract class ConnectionService extends Service { } /** - * Called after the {@link Connection} returned by + * Called by Telecom after the {@link Connection} returned by * {@link #onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)} * or {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)} has been * added to the {@link ConnectionService} and sent to Telecom. * - * @param connection the {@link Connection}. - * @hide + * @param connection the {@link Connection} which was added to Telecom. */ - public void onCreateConnectionComplete(Connection connection) { + @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) + public void onCreateConnectionComplete(@NonNull Connection connection) { } /** - * Called after the {@link Conference} returned by + * Called by Telecom after the {@link Conference} returned by * {@link #onCreateIncomingConference(PhoneAccountHandle, ConnectionRequest)} * or {@link #onCreateOutgoingConference(PhoneAccountHandle, ConnectionRequest)} has been * added to the {@link ConnectionService} and sent to Telecom. * - * @param conference the {@link Conference}. - * @hide + * @param conference the {@link Conference} which was added to Telecom. */ - public void onCreateConferenceComplete(Conference conference) { + @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) + public void onCreateConferenceComplete(@NonNull Conference conference) { } diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java index 2b0d6261886f..d5db61233230 100644 --- a/telephony/java/android/telephony/CarrierRestrictionRules.java +++ b/telephony/java/android/telephony/CarrierRestrictionRules.java @@ -174,6 +174,7 @@ public final class CarrierRestrictionRules implements Parcelable { private int mMultiSimPolicy; @CarrierRestrictionStatus private int mCarrierRestrictionStatus; + private boolean mUseCarrierLockInfo; private CarrierRestrictionRules() { mAllowedCarriers = new ArrayList<CarrierIdentifier>(); @@ -183,6 +184,7 @@ public final class CarrierRestrictionRules implements Parcelable { mCarrierRestrictionDefault = CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED; mMultiSimPolicy = MULTISIM_POLICY_NONE; mCarrierRestrictionStatus = TelephonyManager.CARRIER_RESTRICTION_STATUS_UNKNOWN; + mUseCarrierLockInfo = false; } private CarrierRestrictionRules(Parcel in) { @@ -198,6 +200,7 @@ public final class CarrierRestrictionRules implements Parcelable { if (Flags.carrierRestrictionRulesEnhancement()) { in.readTypedList(mAllowedCarrierInfo, CarrierInfo.CREATOR); in.readTypedList(mExcludedCarrierInfo, CarrierInfo.CREATOR); + mUseCarrierLockInfo = in.readBoolean(); } } @@ -213,6 +216,14 @@ public final class CarrierRestrictionRules implements Parcelable { * Indicates if all carriers are allowed */ public boolean isAllCarriersAllowed() { + if (Flags.carrierRestrictionStatus() && mCarrierRestrictionStatus + == TelephonyManager.CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED) { + return true; + } + if (Flags.carrierRestrictionRulesEnhancement() && mUseCarrierLockInfo) { + return (mAllowedCarrierInfo.isEmpty() && mExcludedCarrierInfo.isEmpty() + && mCarrierRestrictionDefault == CARRIER_RESTRICTION_DEFAULT_ALLOWED); + } return (mAllowedCarriers.isEmpty() && mExcludedCarriers.isEmpty() && mCarrierRestrictionDefault == CARRIER_RESTRICTION_DEFAULT_ALLOWED); } @@ -419,6 +430,7 @@ public final class CarrierRestrictionRules implements Parcelable { if (Flags.carrierRestrictionRulesEnhancement()) { out.writeTypedList(mAllowedCarrierInfo); out.writeTypedList(mExcludedCarrierInfo); + out.writeBoolean(mUseCarrierLockInfo); } } @@ -451,7 +463,8 @@ public final class CarrierRestrictionRules implements Parcelable { public String toString() { return "CarrierRestrictionRules(allowed:" + mAllowedCarriers + ", excluded:" + mExcludedCarriers + ", default:" + mCarrierRestrictionDefault - + ", multisim policy:" + mMultiSimPolicy + getCarrierInfoList() + ")"; + + ", MultiSim policy:" + mMultiSimPolicy + getCarrierInfoList() + + " mIsCarrierLockInfoSupported = " + mUseCarrierLockInfo + ")"; } private String getCarrierInfoList() { @@ -490,6 +503,7 @@ public final class CarrierRestrictionRules implements Parcelable { TelephonyManager.CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED; mRules.mAllowedCarrierInfo.clear(); mRules.mExcludedCarrierInfo.clear(); + mRules.mUseCarrierLockInfo = false; } return this; } @@ -572,5 +586,16 @@ public final class CarrierRestrictionRules implements Parcelable { mRules.mExcludedCarrierInfo = new ArrayList<CarrierInfo>(excludedCarrierInfo); return this; } + + /** + * set whether the HAL radio supports the advanced carrier lock features or not. + * + * @param carrierLockInfoSupported advanced carrierInfo changes supported or not + * @hide + */ + public @NonNull Builder setCarrierLockInfoFeature(boolean carrierLockInfoSupported) { + mRules.mUseCarrierLockInfo = carrierLockInfoSupported; + return this; + } } } |