Merge "Add some logging about stack visibility in app bubble logs" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 0fdd880..cd991c7 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -69,6 +69,7 @@
":com.android.hardware.input-aconfig-java{.generated_srcjars}",
":com.android.input.flags-aconfig-java{.generated_srcjars}",
":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.internal.pm.pkg.component.flags-aconfig-java{.generated_srcjars}",
":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
":com.android.media.flags.editing-aconfig-java{.generated_srcjars}",
":com.android.net.thread.flags-aconfig-java{.generated_srcjars}",
@@ -1138,3 +1139,22 @@
aconfig_declarations: "android.app.wearable.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+aconfig_declarations {
+ name: "com.android.internal.pm.pkg.component.flags-aconfig",
+ package: "com.android.internal.pm.pkg.component.flags",
+ srcs: ["core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "com.android.internal.pm.pkg.component.flags-aconfig-java",
+ aconfig_declarations: "com.android.internal.pm.pkg.component.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+java_aconfig_library {
+ name: "com.android.internal.pm.pkg.component.flags-aconfig-java-host",
+ aconfig_declarations: "com.android.internal.pm.pkg.component.flags-aconfig",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 5d65d9d..8a5206f 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -1,6 +1,20 @@
package: "com.android.server.job"
flag {
+ name: "batch_active_bucket_jobs"
+ namespace: "backstage_power"
+ description: "Include jobs in the ACTIVE bucket in the job batching effort. Don't let them run as freely as they're ready."
+ bug: "299329948"
+}
+
+flag {
+ name: "batch_connectivity_jobs_per_network"
+ namespace: "backstage_power"
+ description: "Have JobScheduler attempt to delay the start of some connectivity jobs until there are several ready or the network is active"
+ bug: "28382445"
+}
+
+flag {
name: "do_not_force_rush_execution_at_boot"
namespace: "backstage_power"
description: "Don't force rush job execution right after boot completion"
@@ -20,10 +34,3 @@
description: "Throw an exception if an unsupported app uses JobInfo.setBias"
bug: "300477393"
}
-
-flag {
- name: "batch_jobs_on_network_activation"
- namespace: "backstage_power"
- description: "Have JobScheduler attempt to delay the start of some connectivity jobs until the network is actually active"
- bug: "318394184"
-}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 6550f26..012ede2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -96,7 +96,6 @@
static final String CONFIG_KEY_PREFIX_CONCURRENCY = "concurrency_";
private static final String KEY_CONCURRENCY_LIMIT = CONFIG_KEY_PREFIX_CONCURRENCY + "limit";
- @VisibleForTesting
static final int DEFAULT_CONCURRENCY_LIMIT;
static {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 57467e3..cea16d6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -65,6 +65,7 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
@@ -89,6 +90,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
+import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -538,7 +540,9 @@
apiQuotaScheduleUpdated = true;
}
break;
+ case Constants.KEY_MIN_READY_CPU_ONLY_JOBS_COUNT:
case Constants.KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT:
+ case Constants.KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS:
case Constants.KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS:
mConstants.updateBatchingConstantsLocked();
break;
@@ -554,6 +558,8 @@
case Constants.KEY_CONN_CONGESTION_DELAY_FRAC:
case Constants.KEY_CONN_PREFETCH_RELAX_FRAC:
case Constants.KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC:
+ case Constants.KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS:
+ case Constants.KEY_CONN_TRANSPORT_BATCH_THRESHOLD:
case Constants.KEY_CONN_USE_CELL_SIGNAL_STRENGTH:
case Constants.KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS:
mConstants.updateConnectivityConstantsLocked();
@@ -602,6 +608,8 @@
sc.onConstantsUpdatedLocked();
}
}
+
+ mHandler.sendEmptyMessage(MSG_CHECK_JOB);
}
@Override
@@ -646,8 +654,12 @@
*/
public static class Constants {
// Key names stored in the settings value.
+ private static final String KEY_MIN_READY_CPU_ONLY_JOBS_COUNT =
+ "min_ready_cpu_only_jobs_count";
private static final String KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT =
"min_ready_non_active_jobs_count";
+ private static final String KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS =
+ "max_cpu_only_job_batch_delay_ms";
private static final String KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS =
"max_non_active_job_batch_delay_ms";
private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor";
@@ -665,6 +677,10 @@
"conn_update_all_jobs_min_interval_ms";
private static final String KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
"conn_low_signal_strength_relax_frac";
+ private static final String KEY_CONN_TRANSPORT_BATCH_THRESHOLD =
+ "conn_transport_batch_threshold";
+ private static final String KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS =
+ "conn_max_connectivity_job_batch_delay_ms";
private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
"prefetch_force_batch_relax_threshold_ms";
// This has been enabled for 3+ full releases. We're unlikely to disable it.
@@ -713,7 +729,11 @@
private static final String KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS =
"max_num_persisted_job_work_items";
- private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
+ private static final int DEFAULT_MIN_READY_CPU_ONLY_JOBS_COUNT =
+ Math.min(3, JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3);
+ private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT =
+ Math.min(5, JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3);
+ private static final long DEFAULT_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
private static final float DEFAULT_MODERATE_USE_FACTOR = .5f;
@@ -725,6 +745,15 @@
private static final boolean DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH = true;
private static final long DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = MINUTE_IN_MILLIS;
private static final float DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = 0.5f;
+ private static final SparseIntArray DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD =
+ new SparseIntArray();
+ private static final long DEFAULT_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS =
+ 31 * MINUTE_IN_MILLIS;
+ static {
+ DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.put(
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ Math.min(3, JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3));
+ }
private static final long DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = HOUR_IN_MILLIS;
private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
@@ -762,11 +791,23 @@
static final int DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 100_000;
/**
- * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early.
+ * Minimum # of jobs that have to be ready for JS to be happy running work.
+ * Only valid if {@link Flags#batchActiveBucketJobs()} is true.
+ */
+ int MIN_READY_CPU_ONLY_JOBS_COUNT = DEFAULT_MIN_READY_CPU_ONLY_JOBS_COUNT;
+
+ /**
+ * Minimum # of non-ACTIVE jobs that have to be ready for JS to be happy running work.
*/
int MIN_READY_NON_ACTIVE_JOBS_COUNT = DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT;
/**
+ * Don't batch a CPU-only job if it's been delayed due to force batching attempts for
+ * at least this amount of time.
+ */
+ long MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = DEFAULT_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS;
+
+ /**
* Don't batch a non-ACTIVE job if it's been delayed due to force batching attempts for
* at least this amount of time.
*/
@@ -822,6 +863,17 @@
*/
public float CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC;
+ /**
+ * The minimum batch requirement per each transport type before allowing a network to run
+ * on a network with that transport.
+ */
+ public SparseIntArray CONN_TRANSPORT_BATCH_THRESHOLD = new SparseIntArray();
+ /**
+ * Don't batch a connectivity job if it's been delayed due to force batching attempts for
+ * at least this amount of time.
+ */
+ public long CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS =
+ DEFAULT_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS;
/**
* The amount of time within which we would consider the app to be launching relatively soon
@@ -972,11 +1024,31 @@
public boolean USE_TARE_POLICY = EconomyManager.DEFAULT_ENABLE_POLICY_JOB_SCHEDULER
&& EconomyManager.DEFAULT_ENABLE_TARE_MODE == EconomyManager.ENABLED_MODE_ON;
+ public Constants() {
+ copyTransportBatchThresholdDefaults();
+ }
+
private void updateBatchingConstantsLocked() {
- MIN_READY_NON_ACTIVE_JOBS_COUNT = DeviceConfig.getInt(
+ // The threshold should be in the range
+ // [0, DEFAULT_CONCURRENCY_LIMIT / 3].
+ MIN_READY_CPU_ONLY_JOBS_COUNT =
+ Math.max(0, Math.min(JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3,
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_MIN_READY_CPU_ONLY_JOBS_COUNT,
+ DEFAULT_MIN_READY_CPU_ONLY_JOBS_COUNT)));
+ // The threshold should be in the range
+ // [0, DEFAULT_CONCURRENCY_LIMIT / 3].
+ MIN_READY_NON_ACTIVE_JOBS_COUNT =
+ Math.max(0, Math.min(JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3,
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
+ DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT)));
+ MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
- KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
- DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT);
+ KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS,
+ DEFAULT_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS);
MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS,
@@ -1024,6 +1096,46 @@
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC,
DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC);
+ final String batchThresholdConfigString = DeviceConfig.getString(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_CONN_TRANSPORT_BATCH_THRESHOLD,
+ null);
+ final KeyValueListParser parser = new KeyValueListParser(',');
+ CONN_TRANSPORT_BATCH_THRESHOLD.clear();
+ try {
+ parser.setString(batchThresholdConfigString);
+
+ for (int t = parser.size() - 1; t >= 0; --t) {
+ final String transportString = parser.keyAt(t);
+ try {
+ final int transport = Integer.parseInt(transportString);
+ // The threshold should be in the range
+ // [0, DEFAULT_CONCURRENCY_LIMIT / 3].
+ CONN_TRANSPORT_BATCH_THRESHOLD.put(transport, Math.max(0,
+ Math.min(JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3,
+ parser.getInt(transportString, 1))));
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Bad transport string", e);
+ }
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Bad string for " + KEY_CONN_TRANSPORT_BATCH_THRESHOLD, e);
+ // Use the defaults.
+ copyTransportBatchThresholdDefaults();
+ }
+ CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS = Math.max(0, Math.min(24 * HOUR_IN_MILLIS,
+ DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS,
+ DEFAULT_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS)));
+ }
+
+ private void copyTransportBatchThresholdDefaults() {
+ for (int i = DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.size() - 1; i >= 0; --i) {
+ CONN_TRANSPORT_BATCH_THRESHOLD.put(
+ DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.keyAt(i),
+ DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.valueAt(i));
+ }
}
private void updatePersistingConstantsLocked() {
@@ -1168,8 +1280,11 @@
void dump(IndentingPrintWriter pw) {
pw.println("Settings:");
pw.increaseIndent();
+ pw.print(KEY_MIN_READY_CPU_ONLY_JOBS_COUNT, MIN_READY_CPU_ONLY_JOBS_COUNT).println();
pw.print(KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
MIN_READY_NON_ACTIVE_JOBS_COUNT).println();
+ pw.print(KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS,
+ MAX_CPU_ONLY_JOB_BATCH_DELAY_MS).println();
pw.print(KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS,
MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS).println();
pw.print(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
@@ -1185,6 +1300,10 @@
.println();
pw.print(KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC, CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC)
.println();
+ pw.print(KEY_CONN_TRANSPORT_BATCH_THRESHOLD, CONN_TRANSPORT_BATCH_THRESHOLD.toString())
+ .println();
+ pw.print(KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS,
+ CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS).println();
pw.print(KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS).println();
@@ -2835,9 +2954,9 @@
mJobPackageTracker.notePending(job);
}
- void noteJobsPending(List<JobStatus> jobs) {
- for (int i = jobs.size() - 1; i >= 0; i--) {
- noteJobPending(jobs.get(i));
+ void noteJobsPending(ArraySet<JobStatus> jobs) {
+ for (int i = jobs.size() - 1; i >= 0; --i) {
+ noteJobPending(jobs.valueAt(i));
}
}
@@ -3463,7 +3582,7 @@
}
final class ReadyJobQueueFunctor implements Consumer<JobStatus> {
- final ArrayList<JobStatus> newReadyJobs = new ArrayList<>();
+ final ArraySet<JobStatus> newReadyJobs = new ArraySet<>();
@Override
public void accept(JobStatus job) {
@@ -3491,9 +3610,27 @@
* policies on when we want to execute jobs.
*/
final class MaybeReadyJobQueueFunctor implements Consumer<JobStatus> {
- int forceBatchedCount;
- int unbatchedCount;
+ /**
+ * Set of jobs that will be force batched, mapped by network. A {@code null} network is
+ * reserved/intended for CPU-only (non-networked) jobs.
+ * The set may include already running jobs.
+ */
+ @VisibleForTesting
+ final ArrayMap<Network, ArraySet<JobStatus>> mBatches = new ArrayMap<>();
+ /** List of all jobs that could run if allowed. Already running jobs are excluded. */
+ @VisibleForTesting
final List<JobStatus> runnableJobs = new ArrayList<>();
+ /**
+ * Convenience holder of all jobs ready to run that won't be force batched.
+ * Already running jobs are excluded.
+ */
+ final ArraySet<JobStatus> mUnbatchedJobs = new ArraySet<>();
+ /**
+ * Count of jobs that won't be force batched, mapped by network. A {@code null} network is
+ * reserved/intended for CPU-only (non-networked) jobs.
+ * The set may include already running jobs.
+ */
+ final ArrayMap<Network, Integer> mUnbatchedJobCount = new ArrayMap<>();
public MaybeReadyJobQueueFunctor() {
reset();
@@ -3540,27 +3677,77 @@
shouldForceBatchJob = false;
} else {
final long nowElapsed = sElapsedRealtimeClock.millis();
- final boolean batchDelayExpired = job.getFirstForceBatchedTimeElapsed() > 0
- && nowElapsed - job.getFirstForceBatchedTimeElapsed()
- >= mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
- shouldForceBatchJob =
- mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
- && job.getEffectiveStandbyBucket() != ACTIVE_INDEX
- && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX
- && !batchDelayExpired;
+ final long timeUntilDeadlineMs = job.hasDeadlineConstraint()
+ ? job.getLatestRunTimeElapsed() - nowElapsed
+ : Long.MAX_VALUE;
+ // Differentiate behavior based on whether the job needs network or not.
+ if (Flags.batchConnectivityJobsPerNetwork()
+ && job.hasConnectivityConstraint()) {
+ // For connectivity jobs, let them run immediately if the network is already
+ // active (in a state for job run), otherwise, only run them if there are
+ // enough to meet the batching requirement or the job has been waiting
+ // long enough.
+ final boolean batchDelayExpired =
+ job.getFirstForceBatchedTimeElapsed() > 0
+ && nowElapsed - job.getFirstForceBatchedTimeElapsed()
+ >= mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS;
+ shouldForceBatchJob = !batchDelayExpired
+ && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX
+ && timeUntilDeadlineMs
+ > mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS / 2
+ && !mConnectivityController.isNetworkInStateForJobRunLocked(job);
+ } else {
+ final boolean batchDelayExpired;
+ final boolean batchingEnabled;
+ if (Flags.batchActiveBucketJobs()) {
+ batchingEnabled = mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT > 1
+ && timeUntilDeadlineMs
+ > mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS / 2
+ // Active UIDs' jobs were by default treated as in the ACTIVE
+ // bucket, so we must explicitly exclude them when batching
+ // ACTIVE jobs.
+ && !job.uidActive
+ && !job.getJob().isExemptedFromAppStandby();
+ batchDelayExpired = job.getFirstForceBatchedTimeElapsed() > 0
+ && nowElapsed - job.getFirstForceBatchedTimeElapsed()
+ >= mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS;
+ } else {
+ batchingEnabled = mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
+ && job.getEffectiveStandbyBucket() != ACTIVE_INDEX;
+ batchDelayExpired = job.getFirstForceBatchedTimeElapsed() > 0
+ && nowElapsed - job.getFirstForceBatchedTimeElapsed()
+ >= mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
+ }
+ shouldForceBatchJob = batchingEnabled
+ && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX
+ && !batchDelayExpired;
+ }
}
+ // If connectivity job batching isn't enabled, treat every job as
+ // a non-connectivity job since that mimics the old behavior.
+ final Network network =
+ Flags.batchConnectivityJobsPerNetwork() ? job.network : null;
+ ArraySet<JobStatus> batch = mBatches.get(network);
+ if (batch == null) {
+ batch = new ArraySet<>();
+ mBatches.put(network, batch);
+ }
+ batch.add(job);
+
if (shouldForceBatchJob) {
- // Force batching non-ACTIVE jobs. Don't include them in the other counts.
- forceBatchedCount++;
if (job.getFirstForceBatchedTimeElapsed() == 0) {
job.setFirstForceBatchedTimeElapsed(sElapsedRealtimeClock.millis());
}
} else {
- unbatchedCount++;
+ mUnbatchedJobCount.put(network,
+ mUnbatchedJobCount.getOrDefault(job.network, 0) + 1);
}
if (!isRunning) {
runnableJobs.add(job);
+ if (!shouldForceBatchJob) {
+ mUnbatchedJobs.add(job);
+ }
}
} else {
if (isRunning) {
@@ -3600,34 +3787,131 @@
@GuardedBy("mLock")
@VisibleForTesting
void postProcessLocked() {
- if (unbatchedCount > 0
- || forceBatchedCount >= mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT) {
- if (DEBUG) {
- Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running jobs.");
+ final ArraySet<JobStatus> jobsToRun = mUnbatchedJobs;
+
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: "
+ + mUnbatchedJobs.size() + " unbatched jobs.");
+ }
+
+ int unbatchedCount = 0;
+
+ for (int n = mBatches.size() - 1; n >= 0; --n) {
+ final Network network = mBatches.keyAt(n);
+
+ // Count all of the unbatched jobs, including the ones without a network.
+ final Integer unbatchedJobCountObj = mUnbatchedJobCount.get(network);
+ final int unbatchedJobCount;
+ if (unbatchedJobCountObj != null) {
+ unbatchedJobCount = unbatchedJobCountObj;
+ unbatchedCount += unbatchedJobCount;
+ } else {
+ unbatchedJobCount = 0;
}
- noteJobsPending(runnableJobs);
- mPendingJobQueue.addAll(runnableJobs);
+
+ // Skip the non-networked jobs here. They'll be handled after evaluating
+ // everything else.
+ if (network == null) {
+ continue;
+ }
+
+ final ArraySet<JobStatus> batchedJobs = mBatches.valueAt(n);
+ if (unbatchedJobCount > 0) {
+ // Some job is going to activate the network anyway. Might as well run all
+ // the other jobs that will use this network.
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: piggybacking "
+ + batchedJobs.size() + " jobs on " + network
+ + " because of unbatched job");
+ }
+ jobsToRun.addAll(batchedJobs);
+ continue;
+ }
+
+ final NetworkCapabilities networkCapabilities =
+ mConnectivityController.getNetworkCapabilities(network);
+ if (networkCapabilities == null) {
+ Slog.e(TAG, "Couldn't get NetworkCapabilities for network " + network);
+ continue;
+ }
+
+ final int[] transports = networkCapabilities.getTransportTypes();
+ int maxNetworkBatchReq = 1;
+ for (int transport : transports) {
+ maxNetworkBatchReq = Math.max(maxNetworkBatchReq,
+ mConstants.CONN_TRANSPORT_BATCH_THRESHOLD.get(transport));
+ }
+
+ if (batchedJobs.size() >= maxNetworkBatchReq) {
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: "
+ + batchedJobs.size()
+ + " batched network jobs meet requirement for " + network);
+ }
+ jobsToRun.addAll(batchedJobs);
+ }
+ }
+
+ final ArraySet<JobStatus> batchedNonNetworkedJobs = mBatches.get(null);
+ if (batchedNonNetworkedJobs != null) {
+ final int minReadyCount = Flags.batchActiveBucketJobs()
+ ? mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT
+ : mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT;
+ if (jobsToRun.size() > 0) {
+ // Some job is going to use the CPU anyway. Might as well run all the other
+ // CPU-only jobs.
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: piggybacking "
+ + batchedNonNetworkedJobs.size() + " non-network jobs");
+ }
+ jobsToRun.addAll(batchedNonNetworkedJobs);
+ } else if (batchedNonNetworkedJobs.size() >= minReadyCount) {
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: adding "
+ + batchedNonNetworkedJobs.size() + " batched non-network jobs.");
+ }
+ jobsToRun.addAll(batchedNonNetworkedJobs);
+ }
+ }
+
+ // In order to properly determine an accurate batch count, the running jobs must be
+ // included in the earlier lists and can only be removed after checking if the batch
+ // count requirement is satisfied.
+ jobsToRun.removeIf(JobSchedulerService.this::isCurrentlyRunningLocked);
+
+ if (unbatchedCount > 0 || jobsToRun.size() > 0) {
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running "
+ + jobsToRun + " jobs.");
+ }
+ noteJobsPending(jobsToRun);
+ mPendingJobQueue.addAll(jobsToRun);
} else {
if (DEBUG) {
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
}
- final int numRunnableJobs = runnableJobs.size();
- if (numRunnableJobs > 0) {
- synchronized (mPendingJobReasonCache) {
- for (int i = 0; i < numRunnableJobs; ++i) {
- final JobStatus job = runnableJobs.get(i);
- SparseIntArray reasons =
- mPendingJobReasonCache.get(job.getUid(), job.getNamespace());
- if (reasons == null) {
- reasons = new SparseIntArray();
- mPendingJobReasonCache
- .add(job.getUid(), job.getNamespace(), reasons);
- }
- // We're force batching these jobs, so consider it an optimization
- // policy reason.
- reasons.put(job.getJobId(),
- JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
+ }
+
+ // Update the pending reason for any jobs that aren't going to be run.
+ final int numRunnableJobs = runnableJobs.size();
+ if (numRunnableJobs > 0 && numRunnableJobs != jobsToRun.size()) {
+ synchronized (mPendingJobReasonCache) {
+ for (int i = 0; i < numRunnableJobs; ++i) {
+ final JobStatus job = runnableJobs.get(i);
+ if (jobsToRun.contains(job)) {
+ // We're running this job. Skip updating the pending reason.
+ continue;
}
+ SparseIntArray reasons =
+ mPendingJobReasonCache.get(job.getUid(), job.getNamespace());
+ if (reasons == null) {
+ reasons = new SparseIntArray();
+ mPendingJobReasonCache.add(job.getUid(), job.getNamespace(), reasons);
+ }
+ // We're force batching these jobs, so consider it an optimization
+ // policy reason.
+ reasons.put(job.getJobId(),
+ JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
}
}
}
@@ -3638,9 +3922,10 @@
@VisibleForTesting
void reset() {
- forceBatchedCount = 0;
- unbatchedCount = 0;
runnableJobs.clear();
+ mBatches.clear();
+ mUnbatchedJobs.clear();
+ mUnbatchedJobCount.clear();
}
}
@@ -5468,8 +5753,14 @@
pw.println("Aconfig flags:");
pw.increaseIndent();
+ pw.print(Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS, Flags.batchActiveBucketJobs());
+ pw.println();
+ pw.print(Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK,
+ Flags.batchConnectivityJobsPerNetwork());
+ pw.println();
pw.print(Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT,
Flags.doNotForceRushExecutionAtBoot());
+ pw.println();
pw.print(Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE,
Flags.throwOnUnsupportedBiasUsage());
pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 0cf6a7a..90b4630 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -350,6 +350,12 @@
case android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS:
pw.println(android.app.job.Flags.jobDebugInfoApis());
break;
+ case com.android.server.job.Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS:
+ pw.println(com.android.server.job.Flags.batchActiveBucketJobs());
+ break;
+ case com.android.server.job.Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK:
+ pw.println(com.android.server.job.Flags.batchConnectivityJobsPerNetwork());
+ break;
case com.android.server.job.Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT:
pw.println(com.android.server.job.Flags.doNotForceRushExecutionAtBoot());
break;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
index 4f4096f..813cf87 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.ArraySet;
import android.util.Pools;
import android.util.SparseArray;
@@ -96,10 +97,10 @@
}
}
- void addAll(@NonNull List<JobStatus> jobs) {
+ void addAll(@NonNull ArraySet<JobStatus> jobs) {
final SparseArray<List<JobStatus>> jobsByUid = new SparseArray<>();
for (int i = jobs.size() - 1; i >= 0; --i) {
- final JobStatus job = jobs.get(i);
+ final JobStatus job = jobs.valueAt(i);
List<JobStatus> appJobs = jobsByUid.get(job.getSourceUid());
if (appJobs == null) {
appJobs = new ArrayList<>();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index f405083..6ed42ec 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -22,11 +22,11 @@
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.Flags.FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER;
-import static com.android.server.job.Flags.relaxPrefetchConnectivityConstraintOnlyOnCharger;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -66,6 +66,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.AppSchedulingModuleThread;
import com.android.server.LocalServices;
+import com.android.server.job.Flags;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
import com.android.server.job.StateControllerProto;
@@ -166,6 +167,10 @@
@GuardedBy("mLock")
private final ArrayMap<Network, CachedNetworkMetadata> mAvailableNetworks = new ArrayMap<>();
+ @GuardedBy("mLock")
+ @Nullable
+ private Network mSystemDefaultNetwork;
+
private final SparseArray<UidDefaultNetworkCallback> mCurrentDefaultNetworkCallbacks =
new SparseArray<>();
private final Comparator<UidStats> mUidStatsComparator = new Comparator<UidStats>() {
@@ -286,6 +291,7 @@
private static final int MSG_UPDATE_ALL_TRACKED_JOBS = 1;
private static final int MSG_DATA_SAVER_TOGGLED = 2;
private static final int MSG_UID_POLICIES_CHANGED = 3;
+ private static final int MSG_PROCESS_ACTIVE_NETWORK = 4;
private final Handler mHandler;
@@ -313,6 +319,14 @@
}
}
+ @Override
+ public void startTrackingLocked() {
+ if (Flags.batchConnectivityJobsPerNetwork()) {
+ mConnManager.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
+ mConnManager.addDefaultNetworkActiveListener(this);
+ }
+ }
+
@GuardedBy("mLock")
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
@@ -911,8 +925,8 @@
return true;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
- // Exclude VPNs because it's currently not possible to determine the VPN's underlying
- // network, and thus the correct signal strength of the VPN's network.
+ // VPNs may have multiple underlying networks and determining the correct strength
+ // may not be straightforward.
// Transmitting data over a VPN is generally more battery-expensive than on the
// underlying network, so:
// TODO: find a good way to reduce job use of VPN when it'll be very expensive
@@ -1007,7 +1021,7 @@
// Need to at least know the estimated download bytes for a prefetch job.
return false;
}
- if (relaxPrefetchConnectivityConstraintOnlyOnCharger()) {
+ if (Flags.relaxPrefetchConnectivityConstraintOnlyOnCharger()) {
// Since the constraint relaxation isn't required by the job, only do it when the
// device is charging and the battery level is above the "low battery" threshold.
if (!mService.isBatteryCharging() || !mService.isBatteryNotLow()) {
@@ -1309,7 +1323,7 @@
}
@Nullable
- private NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
+ public NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
final CachedNetworkMetadata metadata = getNetworkMetadata(network);
return metadata == null ? null : metadata.networkCapabilities;
}
@@ -1527,26 +1541,138 @@
}
/**
+ * Returns {@code true} if the job's assigned network is active or otherwise considered to be
+ * in a good state to run the job now.
+ */
+ @GuardedBy("mLock")
+ public boolean isNetworkInStateForJobRunLocked(@NonNull JobStatus jobStatus) {
+ if (jobStatus.network == null) {
+ return false;
+ }
+ if (jobStatus.shouldTreatAsExpeditedJob() || jobStatus.shouldTreatAsUserInitiatedJob()
+ || mService.getUidProcState(jobStatus.getSourceUid())
+ <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+ // EJs, UIJs, and BFGS+ jobs should be able to activate the network.
+ return true;
+ }
+ return isNetworkInStateForJobRunLocked(jobStatus.network);
+ }
+
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ boolean isNetworkInStateForJobRunLocked(@NonNull Network network) {
+ if (!Flags.batchConnectivityJobsPerNetwork()) {
+ // Active network batching isn't enabled. We don't care about the network state.
+ return true;
+ }
+
+ CachedNetworkMetadata cachedNetworkMetadata = mAvailableNetworks.get(network);
+ if (cachedNetworkMetadata == null) {
+ return false;
+ }
+
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ if (cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed
+ + mCcConfig.NETWORK_ACTIVATION_EXPIRATION_MS > nowElapsed) {
+ // Network is still presumed to be active.
+ return true;
+ }
+
+ final boolean inactiveForTooLong =
+ cachedNetworkMetadata.capabilitiesFirstAcquiredTimeElapsed
+ < nowElapsed - mCcConfig.NETWORK_ACTIVATION_MAX_WAIT_TIME_MS
+ && cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed
+ < nowElapsed - mCcConfig.NETWORK_ACTIVATION_MAX_WAIT_TIME_MS;
+ // We can only know the state of the system default network. If that's not available
+ // or the network in question isn't the system default network,
+ // then return true if we haven't gotten an active signal in a long time.
+ if (mSystemDefaultNetwork == null) {
+ return inactiveForTooLong;
+ }
+
+ if (!mSystemDefaultNetwork.equals(network)) {
+ final NetworkCapabilities capabilities = cachedNetworkMetadata.networkCapabilities;
+ if (capabilities != null
+ && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
+ // VPNs won't have an active signal sent for them. Check their underlying networks
+ // instead, prioritizing the system default if it's one of them.
+ final List<Network> underlyingNetworks = capabilities.getUnderlyingNetworks();
+ if (underlyingNetworks == null) {
+ return inactiveForTooLong;
+ }
+
+ if (underlyingNetworks.contains(mSystemDefaultNetwork)) {
+ if (DEBUG) {
+ Slog.i(TAG, "Substituting system default network "
+ + mSystemDefaultNetwork + " for VPN " + network);
+ }
+ return isNetworkInStateForJobRunLocked(mSystemDefaultNetwork);
+ }
+
+ for (int i = underlyingNetworks.size() - 1; i >= 0; --i) {
+ if (isNetworkInStateForJobRunLocked(underlyingNetworks.get(i))) {
+ return true;
+ }
+ }
+ }
+ return inactiveForTooLong;
+ }
+
+ if (cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed
+ + mCcConfig.NETWORK_ACTIVATION_EXPIRATION_MS < nowElapsed) {
+ // We haven't checked the state recently enough. Let's check if the network is active.
+ // However, if we checked after the last confirmed active time and it wasn't active,
+ // then the network is still not active (we would be told when it becomes active
+ // via onNetworkActive()).
+ if (cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed
+ > cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed) {
+ return inactiveForTooLong;
+ }
+ // We need to explicitly check because there's no callback telling us when the network
+ // leaves the high power state.
+ cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed = nowElapsed;
+ final boolean isActive = mConnManager.isDefaultNetworkActive();
+ if (isActive) {
+ cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed = nowElapsed;
+ return true;
+ }
+ return inactiveForTooLong;
+ }
+
+ // We checked the state recently enough, but the network wasn't active. Assume it still
+ // isn't active.
+ return false;
+ }
+
+ /**
* We know the network has just come up. We want to run any jobs that are ready.
*/
@Override
public void onNetworkActive() {
synchronized (mLock) {
- for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
- final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
- for (int j = jobs.size() - 1; j >= 0; j--) {
- final JobStatus js = jobs.valueAt(j);
- if (js.isReady()) {
- if (DEBUG) {
- Slog.d(TAG, "Running " + js + " due to network activity.");
- }
- mStateChangedListener.onRunJobNow(js);
- }
- }
+ if (mSystemDefaultNetwork == null) {
+ Slog.wtf(TAG, "System default network is unknown but active");
+ return;
}
+
+ CachedNetworkMetadata cachedNetworkMetadata =
+ mAvailableNetworks.get(mSystemDefaultNetwork);
+ if (cachedNetworkMetadata == null) {
+ Slog.wtf(TAG, "System default network capabilities are unknown but active");
+ return;
+ }
+
+ // This method gets called on the system's main thread (not the
+ // AppSchedulingModuleThread), so shift the processing work to a handler to avoid
+ // blocking important operations on the main thread.
+ cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed =
+ cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed =
+ sElapsedRealtimeClock.millis();
+ mHandler.sendEmptyMessage(MSG_PROCESS_ACTIVE_NETWORK);
}
}
+ /** NetworkCallback to track all network changes. */
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
@Override
public void onAvailable(Network network) {
@@ -1565,6 +1691,7 @@
CachedNetworkMetadata cnm = mAvailableNetworks.get(network);
if (cnm == null) {
cnm = new CachedNetworkMetadata();
+ cnm.capabilitiesFirstAcquiredTimeElapsed = sElapsedRealtimeClock.millis();
mAvailableNetworks.put(network, cnm);
} else {
final NetworkCapabilities oldCaps = cnm.networkCapabilities;
@@ -1700,6 +1827,29 @@
}
};
+ /** NetworkCallback to track only changes to the default network. */
+ private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ if (DEBUG) Slog.v(TAG, "systemDefault-onAvailable: " + network);
+ synchronized (mLock) {
+ mSystemDefaultNetwork = network;
+ }
+ }
+
+ @Override
+ public void onLost(Network network) {
+ if (DEBUG) {
+ Slog.v(TAG, "systemDefault-onLost: " + network);
+ }
+ synchronized (mLock) {
+ if (network.equals(mSystemDefaultNetwork)) {
+ mSystemDefaultNetwork = null;
+ }
+ }
+ }
+ };
+
private final INetworkPolicyListener mNetPolicyListener = new NetworkPolicyManager.Listener() {
@Override
public void onRestrictBackgroundChanged(boolean restrictBackground) {
@@ -1762,6 +1912,66 @@
}
}
break;
+
+ case MSG_PROCESS_ACTIVE_NETWORK:
+ removeMessages(MSG_PROCESS_ACTIVE_NETWORK);
+ synchronized (mLock) {
+ if (mSystemDefaultNetwork == null) {
+ break;
+ }
+ if (!Flags.batchConnectivityJobsPerNetwork()) {
+ break;
+ }
+ if (!isNetworkInStateForJobRunLocked(mSystemDefaultNetwork)) {
+ break;
+ }
+
+ final ArrayMap<Network, Boolean> includeInProcessing = new ArrayMap<>();
+ // Try to get the jobs to piggyback on the active network.
+ for (int u = mTrackedJobs.size() - 1; u >= 0; --u) {
+ final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(u);
+ for (int j = jobs.size() - 1; j >= 0; --j) {
+ final JobStatus js = jobs.valueAt(j);
+ if (!mSystemDefaultNetwork.equals(js.network)) {
+ final NetworkCapabilities capabilities =
+ getNetworkCapabilities(js.network);
+ if (capabilities == null
+ || !capabilities.hasTransport(
+ NetworkCapabilities.TRANSPORT_VPN)) {
+ includeInProcessing.put(js.network, Boolean.FALSE);
+ continue;
+ }
+ if (includeInProcessing.containsKey(js.network)) {
+ if (!includeInProcessing.get(js.network)) {
+ continue;
+ }
+ } else {
+ // VPNs most likely use the system default network as
+ // their underlying network. If so, process the job.
+ final List<Network> underlyingNetworks =
+ capabilities.getUnderlyingNetworks();
+ final boolean isSystemDefaultInUnderlying =
+ underlyingNetworks != null
+ && underlyingNetworks.contains(
+ mSystemDefaultNetwork);
+ includeInProcessing.put(js.network,
+ isSystemDefaultInUnderlying);
+ if (!isSystemDefaultInUnderlying) {
+ continue;
+ }
+ }
+ }
+ if (js.isReady()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Potentially running " + js
+ + " due to network activity");
+ }
+ mStateChangedListener.onRunJobNow(js);
+ }
+ }
+ }
+ }
+ break;
}
}
}
@@ -1782,8 +1992,15 @@
@VisibleForTesting
static final String KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY =
CC_CONFIG_PREFIX + "avoid_undefined_transport_affinity";
+ private static final String KEY_NETWORK_ACTIVATION_EXPIRATION_MS =
+ CC_CONFIG_PREFIX + "network_activation_expiration_ms";
+ private static final String KEY_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS =
+ CC_CONFIG_PREFIX + "network_activation_max_wait_time_ms";
private static final boolean DEFAULT_AVOID_UNDEFINED_TRANSPORT_AFFINITY = false;
+ private static final long DEFAULT_NETWORK_ACTIVATION_EXPIRATION_MS = 10000L;
+ private static final long DEFAULT_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS =
+ 31 * MINUTE_IN_MILLIS;
/**
* If true, will avoid network transports that don't have an explicitly defined affinity.
@@ -1791,6 +2008,19 @@
public boolean AVOID_UNDEFINED_TRANSPORT_AFFINITY =
DEFAULT_AVOID_UNDEFINED_TRANSPORT_AFFINITY;
+ /**
+ * Amount of time that needs to pass before needing to determine if the network is still
+ * active.
+ */
+ public long NETWORK_ACTIVATION_EXPIRATION_MS = DEFAULT_NETWORK_ACTIVATION_EXPIRATION_MS;
+
+ /**
+ * Max time to wait since the network was last activated before deciding to allow jobs to
+ * run even if the network isn't active
+ */
+ public long NETWORK_ACTIVATION_MAX_WAIT_TIME_MS =
+ DEFAULT_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS;
+
@GuardedBy("mLock")
public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
@NonNull String key) {
@@ -1803,6 +2033,22 @@
mShouldReprocessNetworkCapabilities = true;
}
break;
+ case KEY_NETWORK_ACTIVATION_EXPIRATION_MS:
+ final long gracePeriodMs = properties.getLong(key,
+ DEFAULT_NETWORK_ACTIVATION_EXPIRATION_MS);
+ if (NETWORK_ACTIVATION_EXPIRATION_MS != gracePeriodMs) {
+ NETWORK_ACTIVATION_EXPIRATION_MS = gracePeriodMs;
+ // This doesn't need to trigger network capability reprocessing.
+ }
+ break;
+ case KEY_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS:
+ final long maxWaitMs = properties.getLong(key,
+ DEFAULT_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS);
+ if (NETWORK_ACTIVATION_MAX_WAIT_TIME_MS != maxWaitMs) {
+ NETWORK_ACTIVATION_MAX_WAIT_TIME_MS = maxWaitMs;
+ mShouldReprocessNetworkCapabilities = true;
+ }
+ break;
}
}
@@ -1814,6 +2060,10 @@
pw.print(KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY,
AVOID_UNDEFINED_TRANSPORT_AFFINITY).println();
+ pw.print(KEY_NETWORK_ACTIVATION_EXPIRATION_MS,
+ NETWORK_ACTIVATION_EXPIRATION_MS).println();
+ pw.print(KEY_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS,
+ NETWORK_ACTIVATION_MAX_WAIT_TIME_MS).println();
pw.decreaseIndent();
}
@@ -1925,11 +2175,24 @@
private static class CachedNetworkMetadata {
public NetworkCapabilities networkCapabilities;
public boolean satisfiesTransportAffinities;
+ /**
+ * Track the first time ConnectivityController was informed about the capabilities of the
+ * network after it became available.
+ */
+ public long capabilitiesFirstAcquiredTimeElapsed;
+ public long defaultNetworkActivationLastCheckTimeElapsed;
+ public long defaultNetworkActivationLastConfirmedTimeElapsed;
public String toString() {
return "CNM{"
+ networkCapabilities.toString()
+ ", satisfiesTransportAffinities=" + satisfiesTransportAffinities
+ + ", capabilitiesFirstAcquiredTimeElapsed="
+ + capabilitiesFirstAcquiredTimeElapsed
+ + ", defaultNetworkActivationLastCheckTimeElapsed="
+ + defaultNetworkActivationLastCheckTimeElapsed
+ + ", defaultNetworkActivationLastConfirmedTimeElapsed="
+ + defaultNetworkActivationLastConfirmedTimeElapsed
+ "}";
}
}
@@ -2017,7 +2280,7 @@
pw.println("Aconfig flags:");
pw.increaseIndent();
pw.print(FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER,
- relaxPrefetchConnectivityConstraintOnlyOnCharger());
+ Flags.relaxPrefetchConnectivityConstraintOnlyOnCharger());
pw.println();
pw.decreaseIndent();
pw.println();
diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java
index 787055c..25d3a34 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Device.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Device.java
@@ -172,8 +172,10 @@
RuntimeException ex = new RuntimeException(
"Could not create uinput device \"" + name + "\"");
Log.e(TAG, "Couldn't create uinput device, exiting.", ex);
+ args.recycle();
throw ex;
}
+ args.recycle();
break;
case MSG_INJECT_EVENT:
if (mPtr != 0) {
diff --git a/core/api/current.txt b/core/api/current.txt
index a3775b0..283e429 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -506,6 +506,7 @@
field public static final int autoSizeTextType = 16844085; // 0x1010535
field public static final int autoStart = 16843445; // 0x10102b5
field @Deprecated public static final int autoText = 16843114; // 0x101016a
+ field @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") public static final int autoTransact;
field public static final int autoUrlDetect = 16843404; // 0x101028c
field public static final int autoVerify = 16844014; // 0x10104ee
field public static final int autofillHints = 16844118; // 0x1010556
@@ -1796,6 +1797,7 @@
field public static final int updatePeriodMillis = 16843344; // 0x1010250
field public static final int use32bitAbi = 16844053; // 0x1010515
field public static final int useAppZygote = 16844183; // 0x1010597
+ field @FlaggedApi("com.android.text.flags.use_bounds_for_width") public static final int useBoundsForWidth;
field public static final int useDefaultMargins = 16843641; // 0x1010379
field public static final int useEmbeddedDex = 16844190; // 0x101059e
field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310
@@ -4378,6 +4380,7 @@
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();
@@ -5418,6 +5421,12 @@
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);
@@ -42369,6 +42378,8 @@
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);
@@ -43536,13 +43547,44 @@
}
public static final class CarrierConfigManager.ImsEmergency {
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int DOMAIN_CS = 1; // 0x1
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int DOMAIN_PS_3GPP = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int DOMAIN_PS_NON_3GPP = 3; // 0x3
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT = "imsemergency.cross_stack_redial_timer_sec_int";
field public static final String KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL = "imsemergency.emergency_callback_mode_supported_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT = "imsemergency.emergency_call_setup_timer_on_current_network_sec_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY = "imsemergency.emergency_cdma_preferred_numbers_string_array";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY = "imsemergency.emergency_domain_preference_int_array";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY = "imsemergency.emergency_domain_preference_roaming_int_array";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL = "imsemergency.emergency_lte_preferred_after_nr_failed_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT = "imsemergency.emergency_network_scan_type_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY = "imsemergency.emergency_over_cs_roaming_supported_access_network_types_int_array";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY = "imsemergency.emergency_over_cs_supported_access_network_types_int_array";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY = "imsemergency.emergency_over_ims_roaming_supported_3gpp_network_types_int_array";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY = "imsemergency.emergency_over_ims_supported_3gpp_network_types_int_array";
field public static final String KEY_EMERGENCY_OVER_IMS_SUPPORTED_RATS_INT_ARRAY = "imsemergency.emergency_over_ims_supported_rats_int_array";
field public static final String KEY_EMERGENCY_QOS_PRECONDITION_SUPPORTED_BOOL = "imsemergency.emergency_qos_precondition_supported_bool";
field public static final String KEY_EMERGENCY_REGISTRATION_TIMER_MILLIS_INT = "imsemergency.emergency_registration_timer_millis_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL = "imsemergency.emergency_requires_ims_registration_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL = "imsemergency.emergency_requires_volte_enabled_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_SCAN_TIMER_SEC_INT = "imsemergency.emergency_scan_timer_sec_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT = "imsemergency.emergency_vowifi_requires_condition_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT = "imsemergency.maximum_cellular_search_timer_sec_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT = "imsemergency.maximum_number_of_emergency_tries_over_vowifi_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL = "imsemergency.prefer_ims_emergency_when_voice_calls_on_cs_bool";
field public static final String KEY_PREFIX = "imsemergency.";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT = "imsemergency.quick_cross_stack_redial_timer_sec_int";
field public static final String KEY_REFRESH_GEOLOCATION_TIMEOUT_MILLIS_INT = "imsemergency.refresh_geolocation_timeout_millis_int";
field public static final String KEY_RETRY_EMERGENCY_ON_IMS_PDN_BOOL = "imsemergency.retry_emergency_on_ims_pdn_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL = "imsemergency.scan_limited_service_after_volte_failure_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL = "imsemergency.start_quick_cross_stack_redial_timer_when_registered_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int REDIAL_TIMER_DISABLED = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int SCAN_TYPE_FULL_SERVICE = 1; // 0x1
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int SCAN_TYPE_NO_PREFERENCE = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int VOWIFI_REQUIRES_NONE = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int VOWIFI_REQUIRES_SETTING_ENABLED = 1; // 0x1
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int VOWIFI_REQUIRES_VALID_EID = 2; // 0x2
}
public static final class CarrierConfigManager.ImsRtt {
@@ -53873,6 +53915,7 @@
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
+ field @FlaggedApi("com.android.window.flags.untrusted_embedding_state_sharing") public static final String PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING = "android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING";
field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
field public static final String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
@@ -59602,7 +59645,7 @@
method public void setRadioGroupChecked(@IdRes int, @IdRes int);
method public void setRelativeScrollPosition(@IdRes int, int);
method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent);
- method @Deprecated public void setRemoteAdapter(@IdRes int, android.content.Intent);
+ method public void setRemoteAdapter(@IdRes int, android.content.Intent);
method public void setRemoteAdapter(@IdRes int, @NonNull android.widget.RemoteViews.RemoteCollectionItems);
method public void setScrollPosition(@IdRes int, int);
method public void setShort(@IdRes int, String, short);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 50764b2..66e03db 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -287,7 +287,7 @@
field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG";
field public static final String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE";
field public static final String READ_GLOBAL_APP_SEARCH_DATA = "android.permission.READ_GLOBAL_APP_SEARCH_DATA";
- field public static final String READ_INSTALLED_SESSION_PATHS = "android.permission.READ_INSTALLED_SESSION_PATHS";
+ field @FlaggedApi("android.content.pm.get_resolved_apk_path") public static final String READ_INSTALLED_SESSION_PATHS = "android.permission.READ_INSTALLED_SESSION_PATHS";
field public static final String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS";
field public static final String READ_NETWORK_USAGE_HISTORY = "android.permission.READ_NETWORK_USAGE_HISTORY";
field public static final String READ_OEM_UNLOCK_STATE = "android.permission.READ_OEM_UNLOCK_STATE";
@@ -4397,6 +4397,59 @@
package android.credentials.selection {
+ @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class AuthenticationEntry implements android.os.Parcelable {
+ ctor public AuthenticationEntry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, int, @NonNull android.content.Intent);
+ method public int describeContents();
+ method @Nullable public android.content.Intent getFrameworkExtrasIntent();
+ method @NonNull public String getKey();
+ method @NonNull public android.app.slice.Slice getSlice();
+ method @NonNull public int getStatus();
+ method @NonNull public String getSubkey();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.AuthenticationEntry> CREATOR;
+ field public static final int STATUS_LOCKED = 0; // 0x0
+ field public static final int STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT = 1; // 0x1
+ field public static final int STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT = 2; // 0x2
+ }
+
+ @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CancelSelectionRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getAppPackageName();
+ method @NonNull public android.credentials.selection.RequestToken getRequestToken();
+ method public boolean shouldShowCancellationExplanation();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.CancelSelectionRequest> CREATOR;
+ }
+
+ @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CreateCredentialProviderInfo {
+ method @NonNull public String getProviderName();
+ method @Nullable public android.credentials.selection.Entry getRemoteEntry();
+ method @NonNull public java.util.List<android.credentials.selection.Entry> getSaveEntries();
+ }
+
+ @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public static final class CreateCredentialProviderInfo.Builder {
+ ctor public CreateCredentialProviderInfo.Builder(@NonNull String);
+ method @NonNull public android.credentials.selection.CreateCredentialProviderInfo build();
+ method @NonNull public android.credentials.selection.CreateCredentialProviderInfo.Builder setRemoteEntry(@Nullable android.credentials.selection.Entry);
+ method @NonNull public android.credentials.selection.CreateCredentialProviderInfo.Builder setSaveEntries(@NonNull java.util.List<android.credentials.selection.Entry>);
+ }
+
+ @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class DisabledProviderInfo {
+ ctor public DisabledProviderInfo(@NonNull String);
+ method @NonNull public String getProviderName();
+ }
+
+ @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class Entry implements android.os.Parcelable {
+ ctor public Entry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, @NonNull android.content.Intent);
+ method public int describeContents();
+ method @Nullable public android.content.Intent getFrameworkExtrasIntent();
+ method @NonNull public String getKey();
+ method @NonNull public android.app.slice.Slice getSlice();
+ method @NonNull public String getSubkey();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.Entry> CREATOR;
+ }
+
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class FailureResult {
ctor public FailureResult(int, @Nullable String);
method public int getErrorCode();
@@ -4406,6 +4459,32 @@
field public static final int ERROR_CODE_UI_FAILURE = 0; // 0x0
}
+ @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class GetCredentialProviderInfo {
+ method @NonNull public java.util.List<android.credentials.selection.Entry> getActionChips();
+ method @NonNull public java.util.List<android.credentials.selection.AuthenticationEntry> getAuthenticationEntries();
+ method @NonNull public java.util.List<android.credentials.selection.Entry> getCredentialEntries();
+ method @NonNull public String getProviderName();
+ method @Nullable public android.credentials.selection.Entry getRemoteEntry();
+ }
+
+ @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public static final class GetCredentialProviderInfo.Builder {
+ ctor public GetCredentialProviderInfo.Builder(@NonNull String);
+ method @NonNull public android.credentials.selection.GetCredentialProviderInfo build();
+ method @NonNull public android.credentials.selection.GetCredentialProviderInfo.Builder setActionChips(@NonNull java.util.List<android.credentials.selection.Entry>);
+ method @NonNull public android.credentials.selection.GetCredentialProviderInfo.Builder setAuthenticationEntries(@NonNull java.util.List<android.credentials.selection.AuthenticationEntry>);
+ method @NonNull public android.credentials.selection.GetCredentialProviderInfo.Builder setCredentialEntries(@NonNull java.util.List<android.credentials.selection.Entry>);
+ method @NonNull public android.credentials.selection.GetCredentialProviderInfo.Builder setRemoteEntry(@Nullable android.credentials.selection.Entry);
+ }
+
+ @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class IntentHelper {
+ method @Nullable public static android.credentials.selection.CancelSelectionRequest extractCancelUiRequest(@NonNull android.content.Intent);
+ method @NonNull public static java.util.List<android.credentials.selection.CreateCredentialProviderInfo> extractCreateCredentialProviderInfoList(@NonNull android.content.Intent);
+ method @NonNull public static java.util.List<android.credentials.selection.DisabledProviderInfo> extractDisabledProviderInfoList(@NonNull android.content.Intent);
+ method @NonNull public static java.util.List<android.credentials.selection.GetCredentialProviderInfo> extractGetCredentialProviderInfoList(@NonNull android.content.Intent);
+ method @Nullable public static android.credentials.selection.RequestInfo extractRequestInfo(@NonNull android.content.Intent);
+ method @Nullable public static android.os.ResultReceiver extractResultReceiver(@NonNull android.content.Intent);
+ }
+
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class ProviderPendingIntentResponse implements android.os.Parcelable {
ctor public ProviderPendingIntentResponse(int, @Nullable android.content.Intent);
method public int describeContents();
@@ -4415,6 +4494,26 @@
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.ProviderPendingIntentResponse> CREATOR;
}
+ @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getAppPackageName();
+ method @Nullable public android.credentials.CreateCredentialRequest getCreateCredentialRequest();
+ method @NonNull public java.util.List<java.lang.String> getDefaultProviderIds();
+ method @Nullable public android.credentials.GetCredentialRequest getGetCredentialRequest();
+ method @NonNull public java.util.List<java.lang.String> getRegistryProviderIds();
+ method @NonNull public android.credentials.selection.RequestToken getRequestToken();
+ method @NonNull public String getType();
+ method public boolean hasPermissionToOverrideDefault();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.RequestInfo> CREATOR;
+ field @NonNull public static final String TYPE_CREATE = "android.credentials.selection.TYPE_CREATE";
+ field @NonNull public static final String TYPE_GET = "android.credentials.selection.TYPE_GET";
+ field @NonNull public static final String TYPE_UNDEFINED = "android.credentials.selection.TYPE_UNDEFINED";
+ }
+
+ @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestToken {
+ }
+
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class ResultHelper {
method public static void sendFailureResult(@NonNull android.os.ResultReceiver, @NonNull android.credentials.selection.FailureResult);
method public static void sendUserSelectionResult(@NonNull android.os.ResultReceiver, @NonNull android.credentials.selection.UserSelectionResult);
@@ -10069,6 +10168,7 @@
@FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable {
ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String);
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilterToAutoTransact(@NonNull String);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
@@ -10082,6 +10182,7 @@
method @FlaggedApi("android.nfc.nfc_read_polling_loop") @NonNull public java.util.List<java.lang.String> getPollingLoopFilters();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getPrefixAids();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSettingsActivityName();
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean getShouldAutoTransact(@NonNull String);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getSubsetAids();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getUid();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean hasCategory(@NonNull String);
@@ -14073,6 +14174,73 @@
method @NonNull public android.telephony.DataThrottlingRequest.Builder setDataThrottlingAction(int);
}
+ @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public class DomainSelectionService extends android.app.Service {
+ ctor public DomainSelectionService();
+ method public void onBarringInfoUpdated(int, int, @NonNull android.telephony.BarringInfo);
+ method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
+ method @NonNull public java.util.concurrent.Executor onCreateExecutor();
+ method public void onDomainSelection(@NonNull android.telephony.DomainSelectionService.SelectionAttributes, @NonNull android.telephony.TransportSelectorCallback);
+ method public void onServiceStateUpdated(int, int, @NonNull android.telephony.ServiceState);
+ field public static final int SCAN_TYPE_FULL_SERVICE = 2; // 0x2
+ field public static final int SCAN_TYPE_LIMITED_SERVICE = 1; // 0x1
+ field public static final int SCAN_TYPE_NO_PREFERENCE = 0; // 0x0
+ field public static final int SELECTOR_TYPE_CALLING = 1; // 0x1
+ field public static final int SELECTOR_TYPE_SMS = 2; // 0x2
+ }
+
+ @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final class DomainSelectionService.SelectionAttributes implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.net.Uri getAddress();
+ method @Nullable public String getCallId();
+ method public int getCsDisconnectCause();
+ method @Nullable public android.telephony.EmergencyRegResult getEmergencyRegResult();
+ method @Nullable public android.telephony.ims.ImsReasonInfo getPsDisconnectCause();
+ method public int getSelectorType();
+ method public int getSlotIndex();
+ method public int getSubscriptionId();
+ method public boolean isEmergency();
+ method public boolean isExitedFromAirplaneMode();
+ method public boolean isTestEmergencyNumber();
+ method public boolean isVideoCall();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.DomainSelectionService.SelectionAttributes> CREATOR;
+ }
+
+ @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final class DomainSelectionService.SelectionAttributes.Builder {
+ ctor public DomainSelectionService.SelectionAttributes.Builder(int, int, int);
+ method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes build();
+ method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setAddress(@NonNull android.net.Uri);
+ method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setCallId(@NonNull String);
+ method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setCsDisconnectCause(int);
+ method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setEmergency(boolean);
+ method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setEmergencyRegResult(@NonNull android.telephony.EmergencyRegResult);
+ method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setExitedFromAirplaneMode(boolean);
+ method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setPsDisconnectCause(@NonNull android.telephony.ims.ImsReasonInfo);
+ method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setTestEmergencyNumber(boolean);
+ method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setVideoCall(boolean);
+ }
+
+ @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public interface DomainSelector {
+ method public void finishSelection();
+ method public void reselectDomain(@NonNull android.telephony.DomainSelectionService.SelectionAttributes);
+ }
+
+ @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public final class EmergencyRegResult implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAccessNetwork();
+ method @NonNull public String getCountryIso();
+ method public int getDomain();
+ method @NonNull public String getMcc();
+ method @NonNull public String getMnc();
+ method public int getNwProvidedEmc();
+ method public int getNwProvidedEmf();
+ method public int getRegState();
+ method public boolean isEmcBearerSupported();
+ method public boolean isVopsSupported();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.EmergencyRegResult> CREATOR;
+ }
+
public final class ImsiEncryptionInfo implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getKeyIdentifier();
@@ -14343,6 +14511,8 @@
field public static final int CHANNEL_UNACCEPTABLE = 6; // 0x6
field public static final int CONDITIONAL_IE_ERROR = 100; // 0x64
field public static final int DESTINATION_OUT_OF_ORDER = 27; // 0x1b
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int EMERGENCY_PERM_FAILURE = 326; // 0x146
+ field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int EMERGENCY_TEMP_FAILURE = 325; // 0x145
field public static final int ERROR_UNSPECIFIED = 65535; // 0xffff
field public static final int FACILITY_REJECTED = 29; // 0x1d
field public static final int FDN_BLOCKED = 241; // 0xf1
@@ -14825,6 +14995,7 @@
method @FlaggedApi("com.android.internal.telephony.flags.enable_identifier_disclosure_transparency") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isCellularIdentifierDisclosureNotificationsEnabled();
method public boolean isDataConnectivityPossible();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
+ method @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDomainSelectionSupported();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean isIccLockEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
@@ -15070,6 +15241,13 @@
method @NonNull public android.telephony.ThermalMitigationRequest.Builder setThermalMitigationAction(int);
}
+ @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public interface TransportSelectorCallback {
+ method public void onCreated(@NonNull android.telephony.DomainSelector);
+ method public void onSelectionTerminated(int);
+ method public void onWlanSelected(boolean);
+ method public void onWwanSelected(@NonNull java.util.function.Consumer<android.telephony.WwanSelectorCallback>);
+ }
+
public final class UiccAccessRule implements android.os.Parcelable {
ctor public UiccAccessRule(byte[], @Nullable String, long);
method public int describeContents();
@@ -15125,6 +15303,11 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.VopsSupportInfo> CREATOR;
}
+ @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public interface WwanSelectorCallback {
+ method public void onDomainSelected(int, boolean);
+ method public void onRequestEmergencyNetworkScan(@NonNull java.util.List<java.lang.Integer>, int, boolean, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.telephony.EmergencyRegResult>);
+ }
+
}
package android.telephony.cdma {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 850f149..a21d7c4 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1267,22 +1267,6 @@
package android.credentials.selection {
- @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class AuthenticationEntry implements android.os.Parcelable {
- ctor public AuthenticationEntry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, int);
- ctor public AuthenticationEntry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, int, @NonNull android.content.Intent);
- method public int describeContents();
- method @Nullable public android.content.Intent getFrameworkExtrasIntent();
- method @NonNull public String getKey();
- method @NonNull public android.app.slice.Slice getSlice();
- method @NonNull public int getStatus();
- method @NonNull public String getSubkey();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.AuthenticationEntry> CREATOR;
- field public static final int STATUS_LOCKED = 0; // 0x0
- field public static final int STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT = 1; // 0x1
- field public static final int STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT = 2; // 0x2
- }
-
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public class BaseDialogResult implements android.os.Parcelable {
ctor public BaseDialogResult(@Nullable android.os.IBinder);
ctor protected BaseDialogResult(@NonNull android.os.Parcel);
@@ -1298,6 +1282,10 @@
field public static final int RESULT_CODE_DIALOG_USER_CANCELED = 0; // 0x0
}
+ @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CancelSelectionRequest implements android.os.Parcelable {
+ ctor @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public CancelSelectionRequest(@NonNull android.os.IBinder, boolean, @NonNull String);
+ }
+
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CreateCredentialProviderData extends android.credentials.selection.ProviderData implements android.os.Parcelable {
ctor public CreateCredentialProviderData(@NonNull String, @NonNull java.util.List<android.credentials.selection.Entry>, @Nullable android.credentials.selection.Entry);
method @Nullable public android.credentials.selection.Entry getRemoteEntry();
@@ -1317,19 +1305,6 @@
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.DisabledProviderData> CREATOR;
}
- @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class Entry implements android.os.Parcelable {
- ctor public Entry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice);
- ctor public Entry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, @NonNull android.content.Intent);
- method public int describeContents();
- method @Nullable public android.content.Intent getFrameworkExtrasIntent();
- method @NonNull public String getKey();
- method @Nullable public android.app.PendingIntent getPendingIntent();
- method @NonNull public android.app.slice.Slice getSlice();
- method @NonNull public String getSubkey();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.Entry> CREATOR;
- }
-
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class FailureDialogResult extends android.credentials.selection.BaseDialogResult implements android.os.Parcelable {
ctor public FailureDialogResult(@Nullable android.os.IBinder, @Nullable String);
method public static void addToBundle(@NonNull android.credentials.selection.FailureDialogResult, @NonNull android.os.Bundle);
@@ -1357,7 +1332,8 @@
}
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public class IntentFactory {
- method @NonNull public static android.content.Intent createCredentialSelectorIntent(@NonNull android.credentials.selection.RequestInfo, @NonNull java.util.ArrayList<android.credentials.selection.ProviderData>, @NonNull java.util.ArrayList<android.credentials.selection.DisabledProviderData>, @NonNull android.os.ResultReceiver);
+ method @NonNull public static android.content.Intent createCancelUiIntent(@NonNull android.os.IBinder, boolean, @NonNull String);
+ method @NonNull public static android.content.Intent createCredentialSelectorIntent(@NonNull android.content.Context, @NonNull android.credentials.selection.RequestInfo, @NonNull java.util.ArrayList<android.credentials.selection.ProviderData>, @NonNull java.util.ArrayList<android.credentials.selection.DisabledProviderData>, @NonNull android.os.ResultReceiver);
}
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public abstract class ProviderData implements android.os.Parcelable {
@@ -1371,25 +1347,12 @@
}
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestInfo implements android.os.Parcelable {
- method public int describeContents();
- method @NonNull public String getAppPackageName();
- method @Nullable public android.credentials.CreateCredentialRequest getCreateCredentialRequest();
- method @NonNull public java.util.List<java.lang.String> getDefaultProviderIds();
- method @Nullable public android.credentials.GetCredentialRequest getGetCredentialRequest();
- method @NonNull public java.util.List<java.lang.String> getRegistryProviderIds();
- method @NonNull public android.os.IBinder getToken();
- method @NonNull public String getType();
- method public boolean hasPermissionToOverrideDefault();
- method @NonNull public static android.credentials.selection.RequestInfo newCreateRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.CreateCredentialRequest, @NonNull String);
- method @NonNull public static android.credentials.selection.RequestInfo newCreateRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.CreateCredentialRequest, @NonNull String, boolean, @NonNull java.util.List<java.lang.String>);
- method @NonNull public static android.credentials.selection.RequestInfo newGetRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.GetCredentialRequest, @NonNull String, boolean);
- method @NonNull public static android.credentials.selection.RequestInfo newGetRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.GetCredentialRequest, @NonNull String);
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.RequestInfo> CREATOR;
- field @NonNull public static final String EXTRA_REQUEST_INFO = "android.credentials.selection.extra.REQUEST_INFO";
- field @NonNull public static final String TYPE_CREATE = "android.credentials.selection.TYPE_CREATE";
- field @NonNull public static final String TYPE_GET = "android.credentials.selection.TYPE_GET";
- field @NonNull public static final String TYPE_UNDEFINED = "android.credentials.selection.TYPE_UNDEFINED";
+ method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newCreateRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.CreateCredentialRequest, @NonNull String, boolean, @NonNull java.util.List<java.lang.String>);
+ method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newGetRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.GetCredentialRequest, @NonNull String, boolean);
+ }
+
+ @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestToken {
+ ctor @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public RequestToken(@NonNull android.os.IBinder);
}
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class UserSelectionDialogResult extends android.credentials.selection.BaseDialogResult implements android.os.Parcelable {
@@ -3315,7 +3278,6 @@
method @NonNull public android.util.Pair<java.lang.Integer,java.lang.Integer> getHalVersion(int);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag();
method @Deprecated public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDomainSelectionSupported();
method public boolean modifyDevicePolicyOverrideApn(@NonNull android.content.Context, int, @NonNull android.telephony.data.ApnSetting);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2103055..ab9a4ec 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -844,6 +844,7 @@
private IBinder mToken;
private IBinder mAssistToken;
private IBinder mShareableActivityToken;
+ private ComponentCaller mInitialCaller;
@UnsupportedAppUsage
private int mIdent;
@UnsupportedAppUsage
@@ -7031,6 +7032,20 @@
}
/**
+ * 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 @@
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 @@
getAutofillClientController().onActivityAttached(application);
setContentCaptureOptions(application.getContentCaptureOptions());
+
+ if (android.security.Flags.contentUriPermissionApis()) {
+ mInitialCaller = new ComponentCaller(getActivityToken(), initialCallerInfoAccessToken);
+ }
}
/** @hide */
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 111895e..1edf4bd 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1974,8 +1974,8 @@
binder = new Binder(descriptor);
}
- private LaunchCookie(Parcel in) {
- this.binder = in.readStrongBinder();
+ private LaunchCookie(IBinder binder) {
+ this.binder = binder;
}
/** @hide */
@@ -1996,7 +1996,11 @@
/** @hide */
public static LaunchCookie readFromParcel(@NonNull Parcel in) {
- return new LaunchCookie(in);
+ IBinder binder = in.readStrongBinder();
+ if (binder == null) {
+ return null;
+ }
+ return new LaunchCookie(binder);
}
/** @hide */
@@ -2017,7 +2021,7 @@
@Override
public LaunchCookie createFromParcel(Parcel source) {
- return new LaunchCookie(source);
+ return LaunchCookie.readFromParcel(source);
}
@Override
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4c54b03..2c00c99 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -580,6 +580,7 @@
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 @@
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 @@
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 @@
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 0000000..583408e
--- /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 68512b8..454d605 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1423,8 +1423,8 @@
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 5e55268..6357a20 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -298,6 +298,33 @@
return result;
}
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("ClientTransaction{");
+ if (mTransactionItems != null) {
+ // #addTransactionItem
+ sb.append("\n transactionItems=[");
+ final int size = mTransactionItems.size();
+ for (int i = 0; i < size; i++) {
+ sb.append("\n ").append(mTransactionItems.get(i));
+ }
+ sb.append("\n ]");
+ } else {
+ // #addCallback
+ sb.append("\n callbacks=[");
+ final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0;
+ for (int i = 0; i < size; i++) {
+ sb.append("\n ").append(mActivityCallbacks.get(i));
+ }
+ sb.append("\n ]");
+ // #setLifecycleStateRequest
+ sb.append("\n stateRequest=").append(mLifecycleStateRequest);
+ }
+ sb.append("\n}");
+ return sb.toString();
+ }
+
/** Dump transaction items callback items and final lifecycle state request. */
void dump(@NonNull String prefix, @NonNull PrintWriter pw,
@NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 4d53701..95f5ad0 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -80,6 +80,7 @@
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 @@
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 @@
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 @@
sceneTransitionInfo, isForward,
profilerInfo != null ? new ProfilerInfo(profilerInfo) : null,
assistToken, activityClientController, shareableActivityToken,
- launchedFromBubble, taskFragmentToken);
+ launchedFromBubble, taskFragmentToken, initialCallerInfoAccessToken);
return instance;
}
@@ -170,7 +171,7 @@
@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 @@
dest.writeStrongBinder(mShareableActivityToken);
dest.writeBoolean(mLaunchedFromBubble);
dest.writeStrongBinder(mTaskFragmentToken);
+ dest.writeStrongBinder(mInitialCallerInfoAccessToken);
}
/** Read from Parcel. */
@@ -220,6 +222,7 @@
IActivityClientController.Stub.asInterface(in.readStrongBinder()),
in.readStrongBinder(),
in.readBoolean(),
+ in.readStrongBinder(),
in.readStrongBinder());
}
@@ -259,7 +262,9 @@
&& 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 @@
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 @@
@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 @@
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 ba94077..406e00a 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -97,6 +97,10 @@
executeCallbacks(transaction);
executeLifecycleState(transaction);
}
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to execute the transaction: "
+ + transactionToString(transaction, mTransactionHandler));
+ throw e;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/core/java/android/content/pm/ProcessInfo.java b/core/java/android/content/pm/ProcessInfo.java
index 632c0f5..f84b46d 100644
--- a/core/java/android/content/pm/ProcessInfo.java
+++ b/core/java/android/content/pm/ProcessInfo.java
@@ -18,10 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.pm.ApplicationInfo;
import android.os.Parcel;
import android.os.Parcelable;
-import android.text.TextUtils;
import android.util.ArraySet;
import com.android.internal.util.DataClass;
@@ -64,6 +62,12 @@
*/
public @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized;
+ /**
+ * Enable use of embedded dex in the APK, rather than extracted or locally compiled variants.
+ * If false (default), the parent app's configuration determines behavior.
+ */
+ public boolean useEmbeddedDex;
+
@Deprecated
public ProcessInfo(@NonNull ProcessInfo orig) {
this.name = orig.name;
@@ -71,11 +75,12 @@
this.gwpAsanMode = orig.gwpAsanMode;
this.memtagMode = orig.memtagMode;
this.nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized;
+ this.useEmbeddedDex = orig.useEmbeddedDex;
}
- // Code below generated by codegen v1.0.22.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -102,6 +107,9 @@
* disabled, or left unspecified.
* @param nativeHeapZeroInitialized
* Enable automatic zero-initialization of native heap memory allocations.
+ * @param useEmbeddedDex
+ * Enable use of embedded dex in the APK, rather than extracted or locally compiled variants.
+ * If false (default), the parent app's configuration determines behavior.
*/
@DataClass.Generated.Member
public ProcessInfo(
@@ -109,7 +117,8 @@
@Nullable ArraySet<String> deniedPermissions,
@ApplicationInfo.GwpAsanMode int gwpAsanMode,
@ApplicationInfo.MemtagMode int memtagMode,
- @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) {
+ @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized,
+ boolean useEmbeddedDex) {
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
@@ -123,6 +132,7 @@
this.nativeHeapZeroInitialized = nativeHeapZeroInitialized;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+ this.useEmbeddedDex = useEmbeddedDex;
// onConstructed(); // You can define this method to get a callback
}
@@ -145,6 +155,7 @@
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
+ if (useEmbeddedDex) flg |= 0x20;
if (deniedPermissions != null) flg |= 0x2;
dest.writeByte(flg);
dest.writeString(name);
@@ -166,6 +177,7 @@
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
+ boolean _useEmbeddedDex = (flg & 0x20) != 0;
String _name = in.readString();
ArraySet<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
int _gwpAsanMode = in.readInt();
@@ -185,6 +197,7 @@
this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+ this.useEmbeddedDex = _useEmbeddedDex;
// onConstructed(); // You can define this method to get a callback
}
@@ -204,10 +217,10 @@
};
@DataClass.Generated(
- time = 1615850184524L,
- codegenVersion = "1.0.22",
+ time = 1706177470784L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/ProcessInfo.java",
- inputSignatures = "public @android.annotation.NonNull java.lang.String name\npublic @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArraySet.class) android.util.ArraySet<java.lang.String> deniedPermissions\npublic @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\npublic @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\npublic @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\nclass ProcessInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
+ inputSignatures = "public @android.annotation.NonNull java.lang.String name\npublic @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArraySet.class) android.util.ArraySet<java.lang.String> deniedPermissions\npublic @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\npublic @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\npublic @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic boolean useEmbeddedDex\nclass ProcessInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 1d0e2db..f54b2ac 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -77,6 +77,8 @@
private static final String ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY =
"crossProfileContentSharingStrategy";
private static final String ATTR_PROFILE_API_VISIBILITY = "profileApiVisibility";
+ private static final String ITEMS_RESTRICTED_ON_HOME_SCREEN =
+ "itemsRestrictedOnHomeScreen";
/** Index values of each property (to indicate whether they are present in this object). */
@IntDef(prefix = "INDEX_", value = {
INDEX_SHOW_IN_LAUNCHER,
@@ -96,7 +98,8 @@
INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY,
INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING,
- INDEX_PROFILE_API_VISIBILITY
+ INDEX_PROFILE_API_VISIBILITY,
+ INDEX_ITEMS_RESTRICTED_ON_HOME_SCREEN
})
@Retention(RetentionPolicy.SOURCE)
private @interface PropertyIndex {
@@ -119,6 +122,7 @@
private static final int INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY = 15;
private static final int INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING = 16;
private static final int INDEX_PROFILE_API_VISIBILITY = 17;
+ private static final int INDEX_ITEMS_RESTRICTED_ON_HOME_SCREEN = 18;
/** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
private long mPropertiesPresent = 0;
@@ -532,6 +536,7 @@
setDeleteAppWithParent(orig.getDeleteAppWithParent());
setAlwaysVisible(orig.getAlwaysVisible());
setAllowStoppingUserWithDelayedLocking(orig.getAllowStoppingUserWithDelayedLocking());
+ setItemsRestrictedOnHomeScreen(orig.areItemsRestrictedOnHomeScreen());
}
if (hasManagePermission) {
// Add items that require MANAGE_USERS or stronger.
@@ -1014,6 +1019,38 @@
}
private @ProfileApiVisibility int mProfileApiVisibility;
+ /**
+ * Returns whether a user (usually a profile) is allowed to have items such as Apps Pending
+ * Installation, Widgets, Custom App Shortcuts, etc. on Launcher home screen.
+ *
+ * <p> For a typical user/profile, this property will be false, allowing framework APIs to
+ * provide information about such items to Launcher(s). When set true, framework APIs will
+ * restrict the same.
+ *
+ * <p> This property only restricts information about items that are accessed solely via the
+ * Launcher home screen. Information about items such as App Icons, Deep Links, which can also
+ * be accessed via other launcher components, such as All Apps Drawer is not restricted by this
+ * property.
+ *
+ * @hide
+ */
+ public boolean areItemsRestrictedOnHomeScreen() {
+ if (isPresent(INDEX_ITEMS_RESTRICTED_ON_HOME_SCREEN)) {
+ return mItemsRestrictedOnHomeScreen;
+ }
+ if (mDefaultProperties != null) {
+ return mDefaultProperties.mItemsRestrictedOnHomeScreen;
+ }
+ throw new SecurityException(
+ "You don't have permission to query mItemsRestrictedOnHomeScreen");
+ }
+ /** @hide */
+ public void setItemsRestrictedOnHomeScreen(boolean val) {
+ this.mItemsRestrictedOnHomeScreen = val;
+ setPresent(INDEX_ITEMS_RESTRICTED_ON_HOME_SCREEN);
+ }
+ private boolean mItemsRestrictedOnHomeScreen;
+
@Override
public String toString() {
String profileApiVisibility =
@@ -1042,7 +1079,8 @@
+ ", mDeleteAppWithParent=" + getDeleteAppWithParent()
+ ", mAlwaysVisible=" + getAlwaysVisible()
+ ", mCrossProfileContentSharingStrategy=" + getCrossProfileContentSharingStrategy()
- + profileApiVisibility
+ + ", mProfileApiVisibility=" + profileApiVisibility
+ + ", mItemsRestrictedOnHomeScreen=" + areItemsRestrictedOnHomeScreen()
+ "}";
}
@@ -1079,6 +1117,7 @@
if (android.multiuser.Flags.supportHidingProfiles()) {
pw.println(prefix + " mProfileApiVisibility=" + getProfileApiVisibility());
}
+ pw.println(prefix + " mItemsRestrictedOnHomeScreen=" + areItemsRestrictedOnHomeScreen());
}
/**
@@ -1168,6 +1207,9 @@
setProfileApiVisibility(parser.getAttributeInt(i));
}
break;
+ case ITEMS_RESTRICTED_ON_HOME_SCREEN:
+ setItemsRestrictedOnHomeScreen(parser.getAttributeBoolean(i));
+ break;
default:
Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
}
@@ -1256,6 +1298,10 @@
mProfileApiVisibility);
}
}
+ if (isPresent(INDEX_ITEMS_RESTRICTED_ON_HOME_SCREEN)) {
+ serializer.attributeBoolean(null, ITEMS_RESTRICTED_ON_HOME_SCREEN,
+ mItemsRestrictedOnHomeScreen);
+ }
}
// For use only with an object that has already had any permission-lacking fields stripped out.
@@ -1280,6 +1326,7 @@
dest.writeBoolean(mAlwaysVisible);
dest.writeInt(mCrossProfileContentSharingStrategy);
dest.writeInt(mProfileApiVisibility);
+ dest.writeBoolean(mItemsRestrictedOnHomeScreen);
}
/**
@@ -1308,6 +1355,7 @@
mAlwaysVisible = source.readBoolean();
mCrossProfileContentSharingStrategy = source.readInt();
mProfileApiVisibility = source.readInt();
+ mItemsRestrictedOnHomeScreen = source.readBoolean();
}
@Override
@@ -1358,6 +1406,7 @@
private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy =
CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION;
private @ProfileApiVisibility int mProfileApiVisibility = 0;
+ private boolean mItemsRestrictedOnHomeScreen = false;
/**
* @hide
@@ -1523,6 +1572,15 @@
return this;
}
+ /** Sets the value for {@link #mItemsRestrictedOnHomeScreen}
+ * @hide
+ */
+ public Builder setItemsRestrictedOnHomeScreen(
+ boolean itemsRestrictedOnHomeScreen) {
+ mItemsRestrictedOnHomeScreen = itemsRestrictedOnHomeScreen;
+ return this;
+ }
+
/** Builds a UserProperties object with *all* values populated.
* @hide
*/
@@ -1548,7 +1606,8 @@
mDeleteAppWithParent,
mAlwaysVisible,
mCrossProfileContentSharingStrategy,
- mProfileApiVisibility);
+ mProfileApiVisibility,
+ mItemsRestrictedOnHomeScreen);
}
} // end Builder
@@ -1570,7 +1629,8 @@
boolean deleteAppWithParent,
boolean alwaysVisible,
@CrossProfileContentSharingStrategy int crossProfileContentSharingStrategy,
- @ProfileApiVisibility int profileApiVisibility) {
+ @ProfileApiVisibility int profileApiVisibility,
+ boolean itemsRestrictedOnHomeScreen) {
mDefaultProperties = null;
setShowInLauncher(showInLauncher);
setStartWithParent(startWithParent);
@@ -1593,5 +1653,6 @@
if (android.multiuser.Flags.supportHidingProfiles()) {
setProfileApiVisibility(profileApiVisibility);
}
+ setItemsRestrictedOnHomeScreen(itemsRestrictedOnHomeScreen);
}
}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index d7e64b6..7ded747 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -44,6 +44,20 @@
}
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 @@
description: "Handle listing of private space apps in settings pages with interleaved content"
bug: "323212460"
}
+
+flag {
+ name: "enable_hiding_profiles"
+ namespace: "profile_experiences"
+ description: "Allow the use of a profileApiAvailability user property to exclude HIDDEN profiles in API results"
+ bug: "316362775"
+}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 4626679..69f9a7d 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -39,6 +39,7 @@
import android.util.Pair;
import android.util.Slog;
+import com.android.internal.pm.pkg.component.flags.Flags;
import com.android.internal.util.ArrayUtils;
import libcore.io.IoUtils;
@@ -89,6 +90,8 @@
private static final String TAG_SDK_LIBRARY = "sdk-library";
private static final int SDK_VERSION = Build.VERSION.SDK_INT;
private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
+ private static final String TAG_PROCESSES = "processes";
+ private static final String TAG_PROCESS = "process";
/**
* Parse only lightweight details about the package at the given location.
@@ -518,6 +521,28 @@
case TAG_SDK_LIBRARY:
isSdkLibrary = true;
break;
+ case TAG_PROCESSES:
+ final int processesDepth = parser.getDepth();
+ int processesType;
+ while ((processesType = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (processesType != XmlPullParser.END_TAG
+ || parser.getDepth() > processesDepth)) {
+ if (processesType == XmlPullParser.END_TAG
+ || processesType == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (parser.getDepth() != processesDepth + 1) {
+ // Search only under <processes>.
+ continue;
+ }
+
+ if (parser.getName().equals(TAG_PROCESS)
+ && Flags.enablePerProcessUseEmbeddedDexAttr()) {
+ useEmbeddedDex |= parser.getAttributeBooleanValue(
+ ANDROID_RES_NAMESPACE, "useEmbeddedDex", false);
+ }
+ }
}
}
} else if (TAG_OVERLAY.equals(parser.getName())) {
diff --git a/core/java/android/credentials/selection/AuthenticationEntry.java b/core/java/android/credentials/selection/AuthenticationEntry.java
index 54589e1..dd6ca9e 100644
--- a/core/java/android/credentials/selection/AuthenticationEntry.java
+++ b/core/java/android/credentials/selection/AuthenticationEntry.java
@@ -23,9 +23,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
-import android.annotation.TestApi;
+import android.annotation.SystemApi;
import android.app.slice.Slice;
+import android.content.Context;
import android.content.Intent;
+import android.credentials.GetCredentialRequest;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
import android.os.Parcel;
import android.os.Parcelable;
@@ -33,17 +37,20 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
/**
* An authentication entry.
*
- * Applicable only for credential retrieval flow, authentication entries are a special type of
- * entries that require the user to unlock the given provider before its credential options can
- * be fully rendered.
+ * Applicable only for
+ * {@link android.credentials.CredentialManager#getCredential(Context, GetCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} flow, authentication entries are a special type
+ * of entries that require the user to unlock the given provider before its credential options
+ * can be fully rendered.
*
* @hide
*/
-@TestApi
+@SystemApi
@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
public final class AuthenticationEntry implements Parcelable {
@NonNull
@@ -67,17 +74,31 @@
public @interface Status {
}
- /** This entry is still locked, as initially supplied by the provider. */
+ /**
+ * This entry is still locked, as initially supplied by the provider.
+ *
+ * This entry should be rendered in a way to signal that it is still locked, and when chosen
+ * will lead to an unlock challenge (e.g. draw a trailing lock icon on this entry).
+ */
public static final int STATUS_LOCKED = 0;
/**
* This entry was unlocked but didn't contain any credential. Meanwhile, "less recent" means
* there is another such entry that was unlocked more recently.
+ *
+ * This entry should be rendered in a way to signal that it was unlocked but turns out to
+ * contain no credential that can be used, and as a result, it should be unclickable.
*/
public static final int STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT = 1;
/**
* This is the most recent entry that was unlocked but didn't contain any credential.
*
* There will be at most one authentication entry with this status.
+ *
+ * This entry should be rendered in a way to signal that it was unlocked but turns out to
+ * contain no credential that can be used, and as a result, it should be unclickable.
+ *
+ * If this was the last clickable option prior to unlocking, then the UI should display an
+ * information that all options are exhausted then gracefully finish itself.
*/
public static final int STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT = 2;
@@ -94,29 +115,36 @@
}
/**
- * Constructor to be used for an entry that does not require further activities
- * to be invoked when selected.
+ * Constructor to be used for an entry that requires a pending intent to be invoked
+ * when clicked.
+ *
+ * @param key the identifier of this entry that's unique within the context of the given
+ * CredentialManager request. This is used when constructing the
+ * {@link android.credentials.selection.UserSelectionResult#UserSelectionResult(
+ * String providerId, String entryKey, String entrySubkey,
+ * ProviderPendingIntentResponse providerPendingIntentResponse)}
+ *
+ * @param subkey the sub-identifier of this entry that's unique within the context of the
+ * {@code key}. This is used when constructing the
+ * {@link android.credentials.selection.UserSelectionResult#UserSelectionResult(
+ * String providerId, String entryKey, String entrySubkey,
+ * ProviderPendingIntentResponse providerPendingIntentResponse)}
+ * @param intent the intent containing extra data that has to be filled in when launching this
+ * entry's provider PendingIntent
+ * @param slice the Slice to be displayed
+ * @param status the entry status, depending on which the entry should be rendered differently
*/
- // TODO(b/322065508): remove this constructor.
public AuthenticationEntry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
- @Status int status) {
+ @Status int status, @NonNull Intent intent) {
mKey = key;
mSubkey = subkey;
mSlice = slice;
mStatus = status;
- }
-
- /** Constructor to be used for an entry that requires a pending intent to be invoked
- * when clicked.
- */
- public AuthenticationEntry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
- @Status int status, @NonNull Intent intent) {
- this(key, subkey, slice, status);
mFrameworkExtrasIntent = intent;
}
/**
- * Returns the identifier of this entry that's unique within the context of the
+ * Returns the identifier of this entry that's unique within the context of the given
* CredentialManager request.
*/
@NonNull
@@ -138,7 +166,7 @@
return mSlice;
}
- /** Returns the entry status, depending on which the entry will be rendered differently. */
+ /** Returns the entry status, depending on which the entry should be rendered differently. */
@NonNull
@Status
public int getStatus() {
@@ -146,8 +174,10 @@
}
/**
- * Returns the framework intent to be filled in when launching this entry's provider
- * PendingIntent.
+ * Returns the intent containing extra data that has to be filled in when launching this
+ * entry's provider PendingIntent.
+ *
+ * If null, the provider PendingIntent can be launched without any fill in intent.
*/
@Nullable
@SuppressLint("IntentBuilderName") // Not building a new intent.
diff --git a/core/java/android/credentials/selection/CancelSelectionRequest.java b/core/java/android/credentials/selection/CancelSelectionRequest.java
new file mode 100644
index 0000000..2662d76
--- /dev/null
+++ b/core/java/android/credentials/selection/CancelSelectionRequest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 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 android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * A request to cancel the ongoing selection UI matching the identifier token in this request.
+ *
+ * Upon receiving this request, the UI should gracefully finish itself if the given request token
+ * {@link CancelSelectionRequest#getToken()} matches that of the selection UI is currently rendered
+ * for. Also, the UI should display some informational cancellation message (e.g. "Request is
+ * cancelled by the app") before closing when the
+ * {@link CancelSelectionRequest#shouldShowCancellationExplanation()} is true.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class CancelSelectionRequest implements Parcelable {
+
+ /**
+ * The intent extra key for the {@code CancelUiRequest} object when launching the UX
+ * activities.
+ *
+ * @hide
+ */
+ @NonNull
+ public static final String EXTRA_CANCEL_UI_REQUEST =
+ "android.credentials.selection.extra.CANCEL_UI_REQUEST";
+
+ @NonNull
+ private final IBinder mToken;
+
+ private final boolean mShouldShowCancellationExplanation;
+
+ @NonNull
+ private final String mAppPackageName;
+
+ /**
+ * Returns the request token matching the user request that should be cancelled.
+ *
+ * The request token for the current UI can be found from the UI launch intent, mapping to
+ * {@link RequestInfo#getToken()}.
+ *
+ * @hide
+ */
+ @NonNull
+ public IBinder getToken() {
+ return mToken;
+ }
+
+ /** Returns the request token matching the app request that should be cancelled. */
+ @NonNull
+ public RequestToken getRequestToken() {
+ return new RequestToken(mToken);
+ }
+
+ /**
+ * Returns the app package name invoking this request, that can be used to derive display
+ * metadata (e.g. "Cancelled by `App Name`").
+ */
+ @NonNull
+ public String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /**
+ * Returns whether the UI should display some informational cancellation message (e.g.
+ * "Request is cancelled by the app") before closing. If false, the UI should be silently
+ * cancelled.
+ */
+ public boolean shouldShowCancellationExplanation() {
+ return mShouldShowCancellationExplanation;
+ }
+
+ /**
+ * Constructs a {@link CancelSelectionRequest}.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+ public CancelSelectionRequest(@NonNull IBinder token, boolean shouldShowCancellationExplanation,
+ @NonNull String appPackageName) {
+ mToken = token;
+ mShouldShowCancellationExplanation = shouldShowCancellationExplanation;
+ mAppPackageName = appPackageName;
+ }
+
+ private CancelSelectionRequest(@NonNull Parcel in) {
+ mToken = in.readStrongBinder();
+ AnnotationValidations.validate(NonNull.class, null, mToken);
+ mShouldShowCancellationExplanation = in.readBoolean();
+ mAppPackageName = in.readString8();
+ AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeStrongBinder(mToken);
+ dest.writeBoolean(mShouldShowCancellationExplanation);
+ dest.writeString8(mAppPackageName);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<CancelSelectionRequest> CREATOR = new Creator<>() {
+ @Override
+ public CancelSelectionRequest createFromParcel(@NonNull Parcel in) {
+ return new CancelSelectionRequest(in);
+ }
+
+ @Override
+ public CancelSelectionRequest[] newArray(int size) {
+ return new CancelSelectionRequest[size];
+ }
+ };
+}
diff --git a/core/java/android/credentials/selection/CancelUiRequest.java b/core/java/android/credentials/selection/CancelUiRequest.java
deleted file mode 100644
index fca0e2a..0000000
--- a/core/java/android/credentials/selection/CancelUiRequest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright 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 android.credentials.selection;
-
-import android.annotation.NonNull;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-/**
- * A request to cancel the ongoing UI matching the identifier token in this request.
- *
- * @hide
- */
-public final class CancelUiRequest implements Parcelable {
-
- /**
- * The intent extra key for the {@code CancelUiRequest} object when launching the UX
- * activities.
- *
- * @hide
- */
- @NonNull
- public static final String EXTRA_CANCEL_UI_REQUEST =
- "android.credentials.selection.extra.CANCEL_UI_REQUEST";
-
- @NonNull
- private final IBinder mToken;
-
- private final boolean mShouldShowCancellationUi;
-
- @NonNull
- private final String mAppPackageName;
-
- /** Returns the request token matching the user request that should be cancelled. */
- @NonNull
- public IBinder getToken() {
- return mToken;
- }
-
- /**
- * Returns the app package name invoking this request, that can be used to derive display
- * metadata (e.g. "Cancelled by `App Name`").
- */
- @NonNull
- public String getAppPackageName() {
- return mAppPackageName;
- }
-
- /**
- * Returns whether the UI should render a cancellation UI upon the request. If false, the UI
- * will be silently cancelled.
- */
- public boolean shouldShowCancellationUi() {
- return mShouldShowCancellationUi;
- }
-
- /** Constructs a {@link CancelUiRequest}. */
- public CancelUiRequest(@NonNull IBinder token, boolean shouldShowCancellationUi,
- @NonNull String appPackageName) {
- mToken = token;
- mShouldShowCancellationUi = shouldShowCancellationUi;
- mAppPackageName = appPackageName;
- }
-
- private CancelUiRequest(@NonNull Parcel in) {
- mToken = in.readStrongBinder();
- AnnotationValidations.validate(NonNull.class, null, mToken);
- mShouldShowCancellationUi = in.readBoolean();
- mAppPackageName = in.readString8();
- AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeStrongBinder(mToken);
- dest.writeBoolean(mShouldShowCancellationUi);
- dest.writeString8(mAppPackageName);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @NonNull
- public static final Creator<CancelUiRequest> CREATOR = new Creator<>() {
- @Override
- public CancelUiRequest createFromParcel(@NonNull Parcel in) {
- return new CancelUiRequest(in);
- }
-
- @Override
- public CancelUiRequest[] newArray(int size) {
- return new CancelUiRequest[size];
- }
- };
-}
diff --git a/core/java/android/credentials/selection/CreateCredentialProviderData.java b/core/java/android/credentials/selection/CreateCredentialProviderData.java
index fc80ea8..ba9b00a 100644
--- a/core/java/android/credentials/selection/CreateCredentialProviderData.java
+++ b/core/java/android/credentials/selection/CreateCredentialProviderData.java
@@ -118,9 +118,12 @@
@TestApi
@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
public static final class Builder {
- @NonNull private String mProviderFlattenedComponentName;
- @NonNull private List<Entry> mSaveEntries = new ArrayList<>();
- @Nullable private Entry mRemoteEntry = null;
+ @NonNull
+ private String mProviderFlattenedComponentName;
+ @NonNull
+ private List<Entry> mSaveEntries = new ArrayList<>();
+ @Nullable
+ private Entry mRemoteEntry = null;
/** Constructor with required properties. */
public Builder(@NonNull String providerFlattenedComponentName) {
diff --git a/core/java/android/credentials/selection/CreateCredentialProviderInfo.java b/core/java/android/credentials/selection/CreateCredentialProviderInfo.java
index 78b9fd44..6d02f0d 100644
--- a/core/java/android/credentials/selection/CreateCredentialProviderInfo.java
+++ b/core/java/android/credentials/selection/CreateCredentialProviderInfo.java
@@ -16,21 +16,40 @@
package android.credentials.selection;
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.credentials.CreateCredentialRequest;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
/**
- * Information pertaining to a specific provider during the given create-credential flow.
+ * Per-provider metadata and entries for the
+ * {@link android.credentials.CredentialManager#createCredential(Context, CreateCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} flow.
*
* This includes provider metadata and its credential creation options for display purposes.
*
+ * The selection UI should render all options (from
+ * {@link CreateCredentialProviderInfo#getSaveEntries()} and
+ * {@link CreateCredentialProviderInfo#getRemoteEntry()}) offered by this provider while clearly
+ * associating them with the given provider using the provider icon, label, etc. derived from
+ * {@link CreateCredentialProviderInfo#getProviderName()}.
+ *
* @hide
*/
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
public final class CreateCredentialProviderInfo {
@NonNull
@@ -65,7 +84,8 @@
* Returns the remote credential saving option, if any.
*
* Notice that only one system configured provider can set this option, and when set, it means
- * that the system service has already validated the provider's eligibility.
+ * that the system service has already validated the provider's eligibility. A null value means
+ * no remote entry should be displayed for this provider.
*/
@Nullable
public Entry getRemoteEntry() {
@@ -77,6 +97,8 @@
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
public static final class Builder {
@NonNull
private String mProviderName;
@@ -85,7 +107,12 @@
@Nullable
private Entry mRemoteEntry = null;
- /** Constructor with required properties. */
+ /**
+ * Constructs a {@link CreateCredentialProviderInfo.Builder}.
+ *
+ * @param providerName the provider (component or package) name
+ * @throws IllegalArgumentException if {@code providerName} is null or empty
+ */
public Builder(@NonNull String providerName) {
mProviderName = Preconditions.checkStringNotEmpty(providerName);
}
@@ -97,7 +124,13 @@
return this;
}
- /** Sets the remote entry of the provider. */
+ /**
+ * Sets the remote entry to be displayed to the user.
+ *
+ * The system service should only set this entry to non-null if it has validated that
+ * the given provider does have the permission to set this value. Null means there is
+ * no valid remote entry for display.
+ */
@NonNull
public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
mRemoteEntry = remoteEntry;
diff --git a/core/java/android/credentials/selection/DisabledProviderData.java b/core/java/android/credentials/selection/DisabledProviderData.java
index b6f6ad4..4238612 100644
--- a/core/java/android/credentials/selection/DisabledProviderData.java
+++ b/core/java/android/credentials/selection/DisabledProviderData.java
@@ -63,14 +63,14 @@
}
public static final @NonNull Creator<DisabledProviderData> CREATOR = new Creator<>() {
- @Override
- public DisabledProviderData createFromParcel(@NonNull Parcel in) {
- return new DisabledProviderData(in);
- }
+ @Override
+ public DisabledProviderData createFromParcel(@NonNull Parcel in) {
+ return new DisabledProviderData(in);
+ }
- @Override
- public DisabledProviderData[] newArray(int size) {
- return new DisabledProviderData[size];
- }
+ @Override
+ public DisabledProviderData[] newArray(int size) {
+ return new DisabledProviderData[size];
+ }
};
}
diff --git a/core/java/android/credentials/selection/DisabledProviderInfo.java b/core/java/android/credentials/selection/DisabledProviderInfo.java
index 7d7dbc2..7764d2e 100644
--- a/core/java/android/credentials/selection/DisabledProviderInfo.java
+++ b/core/java/android/credentials/selection/DisabledProviderInfo.java
@@ -16,17 +16,36 @@
package android.credentials.selection;
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.credentials.CreateCredentialRequest;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
import com.android.internal.util.Preconditions;
+import java.util.concurrent.Executor;
+
/**
* Information pertaining to a specific provider that is disabled from the user settings.
*
- * Currently, disabled provider data is only propagated in the create-credential flow.
+ * Currently, disabled provider data is only propagated in the
+ * {@link android.credentials.CredentialManager#createCredential(Context, CreateCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} flow.
+ *
+ * This should be used to display an option, e.g. "+ Enable `disabled_provider_1`,
+ * `disabled_provider_2`" to navigate the user to Settings
+ * ({@link android.provider.Settings#ACTION_CREDENTIAL_PROVIDER}) to enable these
+ * disabled providers.
*
* @hide
*/
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
public final class DisabledProviderInfo {
@NonNull
@@ -37,8 +56,7 @@
*
* @throws IllegalArgumentException if {@code providerName} is empty
*/
- public DisabledProviderInfo(
- @NonNull String providerName) {
+ public DisabledProviderInfo(@NonNull String providerName) {
mProviderName = Preconditions.checkStringNotEmpty(providerName);
}
diff --git a/core/java/android/credentials/selection/Entry.java b/core/java/android/credentials/selection/Entry.java
index bcf4ee3..a131f3f 100644
--- a/core/java/android/credentials/selection/Entry.java
+++ b/core/java/android/credentials/selection/Entry.java
@@ -22,7 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
-import android.annotation.TestApi;
+import android.annotation.SystemApi;
import android.app.PendingIntent;
import android.app.slice.Slice;
import android.content.Intent;
@@ -36,7 +36,7 @@
*
* @hide
*/
-@TestApi
+@SystemApi
@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
public final class Entry implements Parcelable {
@NonNull
@@ -67,30 +67,34 @@
}
/**
- * Constructor to be used for an entry that does not require further activities
- * to be invoked when selected.
- */
- // TODO(b/322065508): deprecate this constructor.
- public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice) {
- mKey = key;
- mSubkey = subkey;
- mSlice = slice;
- }
-
- /**
* Constructor to be used for an entry that requires a pending intent to be invoked
* when clicked.
+ *
+ * @param key the identifier of this entry that's unique within the context of the given
+ * CredentialManager request. This is used when constructing the
+ * {@link android.credentials.selection.UserSelectionResult#UserSelectionResult(
+ * String providerId, String entryKey, String entrySubkey,
+ * ProviderPendingIntentResponse providerPendingIntentResponse)}
+ * @param subkey the sub-identifier of this entry that's unique within the context of the
+ * {@code key}. This is used when constructing the
+ * {@link android.credentials.selection.UserSelectionResult#UserSelectionResult(
+ * String providerId, String entryKey, String entrySubkey,
+ * ProviderPendingIntentResponse providerPendingIntentResponse)}
+ * @param intent the intent containing extra data that has to be filled in when launching this
+ * entry's provider PendingIntent
+ * @param slice the Slice to be displayed
*/
public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
@NonNull Intent intent) {
- this(key, subkey, slice);
+ mKey = key;
+ mSubkey = subkey;
+ mSlice = slice;
mFrameworkExtrasIntent = intent;
}
/**
* Returns the identifier of this entry that's unique within the context of the
- * CredentialManager
- * request.
+ * CredentialManager request.
*
* Generally used when sending the user selection result back to the system service.
*/
@@ -116,17 +120,10 @@
}
/**
- * Returns the provider PendingIntent to launch once this entry is selected.
- */
- // TODO(b/322065508): deprecate this bit.
- @Nullable
- public PendingIntent getPendingIntent() {
- return mPendingIntent;
- }
-
- /**
- * Returns the framework fill in intent to add to the provider PendingIntent to launch, once
- * this entry is selected.
+ * Returns the intent containing extra data that has to be filled in when launching this
+ * entry's provider PendingIntent.
+ *
+ * If null, the provider PendingIntent can be launched without any fill in intent.
*/
@Nullable
@SuppressLint("IntentBuilderName") // Not building a new intent.
diff --git a/core/java/android/credentials/selection/GetCredentialProviderInfo.java b/core/java/android/credentials/selection/GetCredentialProviderInfo.java
index db0fb84..1e2f27c 100644
--- a/core/java/android/credentials/selection/GetCredentialProviderInfo.java
+++ b/core/java/android/credentials/selection/GetCredentialProviderInfo.java
@@ -16,21 +16,45 @@
package android.credentials.selection;
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.credentials.GetCredentialRequest;
+import android.credentials.PrepareGetCredentialResponse;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
/**
- * Information pertaining to a specific provider during the given create-credential flow.
+ * Information pertaining to a specific provider during the given
+ * {@link android.credentials.CredentialManager#getCredential(Context, GetCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} or
+ * {@link android.credentials.CredentialManager#getCredential(Context,
+ * PrepareGetCredentialResponse.PendingGetCredentialHandle, CancellationSignal, Executor,
+ * OutcomeReceiver)} flow.
*
* This includes provider metadata and its credential creation options for display purposes.
*
+ * The selection UI should render all options (from
+ * {@link GetCredentialProviderInfo#getRemoteEntry()},
+ * {@link GetCredentialProviderInfo#getCredentialEntries()}, and
+ * {@link GetCredentialProviderInfo#getActionChips()}) offered by this provider while clearly
+ * associated them with the given provider using the provider icon, label, etc. derived from
+ * {@link GetCredentialProviderInfo#getProviderName()}.
+ *
* @hide
*/
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
public final class GetCredentialProviderInfo {
@NonNull
@@ -95,8 +119,9 @@
/**
* Returns the remote credential retrieval option, if any.
*
- * Notice that only one system configured provider can set this option, and when set, it means
- * that the system service has already validated the provider's eligibility.
+ * Notice that only one system configured provider can set this option, and when set to
+ * non-null, it means that the system service has already validated the provider's eligibility.
+ * A null value means no remote entry should be displayed for this provider.
*/
@Nullable
public Entry getRemoteEntry() {
@@ -108,6 +133,8 @@
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
public static final class Builder {
@NonNull
private String mProviderName;
@@ -123,6 +150,7 @@
/**
* Constructs a {@link GetCredentialProviderInfo.Builder}.
*
+ * @param providerName the provider (component or package) name
* @throws IllegalArgumentException if {@code providerName} is null or empty
*/
public Builder(@NonNull String providerName) {
@@ -151,7 +179,13 @@
return this;
}
- /** Sets the remote entry to be displayed to the user. */
+ /**
+ * Sets the remote entry to be displayed to the user.
+ *
+ * The system service should only set this entry to non-null if it has validated that
+ * the given provider does have the permission to set this value. Null means there is
+ * no valid remote entry for display.
+ */
@NonNull
public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
mRemoteEntry = remoteEntry;
diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java
index e8d5d37..1837976 100644
--- a/core/java/android/credentials/selection/IntentFactory.java
+++ b/core/java/android/credentials/selection/IntentFactory.java
@@ -17,6 +17,7 @@
package android.credentials.selection;
import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+import static android.credentials.flags.Flags.configurableSelectorUiEnabled;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
@@ -24,11 +25,16 @@
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.IBinder;
import android.os.Parcel;
import android.os.ResultReceiver;
+import android.text.TextUtils;
+import android.util.Slog;
import java.util.ArrayList;
@@ -42,12 +48,14 @@
public class IntentFactory {
/**
- * Generate a new launch intent to the Credential Selector UI.
+ * Generate a new launch intent to the Credential Selector UI for auto-filling.
*
* @hide
*/
@NonNull
+ // TODO(b/323552850) - clean up method overloads
public static Intent createCredentialSelectorIntent(
+ @NonNull Context context,
@NonNull RequestInfo requestInfo,
@SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
@Nullable
@@ -60,10 +68,10 @@
Intent intent;
if (enabledProviderDataList != null) {
- intent = createCredentialSelectorIntent(requestInfo, enabledProviderDataList,
+ intent = createCredentialSelectorIntent(context, requestInfo, enabledProviderDataList,
disabledProviderDataList, resultReceiver);
} else {
- intent = createCredentialSelectorIntent(requestInfo,
+ intent = createCredentialSelectorIntent(context, requestInfo,
disabledProviderDataList, resultReceiver);
}
intent.putExtra(Constants.EXTRA_REQ_FOR_ALL_OPTIONS, isRequestForAllOptions);
@@ -77,7 +85,8 @@
* @hide
*/
@NonNull
- public static Intent createCredentialSelectorIntent(
+ private static Intent createCredentialSelectorIntent(
+ @NonNull Context context,
@NonNull RequestInfo requestInfo,
@SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
@NonNull
@@ -90,6 +99,10 @@
.getString(
com.android.internal.R.string
.config_credentialManagerDialogComponent));
+ ComponentName oemOverrideComponentName = getOemOverrideComponentName(context);
+ if (oemOverrideComponentName != null) {
+ componentName = oemOverrideComponentName;
+ }
intent.setComponent(componentName);
intent.putParcelableArrayListExtra(
ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
@@ -100,9 +113,73 @@
return intent;
}
- /** Generate a new launch intent to the Credential Selector UI. */
+ /**
+ * Returns null if there is not an enabled and valid oem override component. It means the
+ * default platform UI component name should be used instead.
+ */
+ @Nullable
+ private static ComponentName getOemOverrideComponentName(@NonNull Context context) {
+ ComponentName result = null;
+ if (configurableSelectorUiEnabled()) {
+ if (Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.config_enableOemCredentialManagerDialogComponent)) {
+ String oemComponentString =
+ Resources.getSystem()
+ .getString(
+ com.android.internal.R.string
+ .config_oemCredentialManagerDialogComponent);
+ if (!TextUtils.isEmpty(oemComponentString)) {
+ ComponentName oemComponentName = ComponentName.unflattenFromString(
+ oemComponentString);
+ if (oemComponentName != null) {
+ try {
+ ActivityInfo info = context.getPackageManager().getActivityInfo(
+ oemComponentName,
+ PackageManager.ComponentInfoFlags.of(
+ PackageManager.MATCH_SYSTEM_ONLY));
+ if (info.enabled && info.exported) {
+ Slog.i(TAG,
+ "Found enabled oem CredMan UI component."
+ + oemComponentString);
+ result = oemComponentName;
+ } else {
+ Slog.i(TAG,
+ "Found enabled oem CredMan UI component but it was not "
+ + "enabled.");
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.i(TAG, "Unable to find oem CredMan UI component: "
+ + oemComponentString + ".");
+ }
+ } else {
+ Slog.i(TAG, "Invalid OEM ComponentName format.");
+ }
+ } else {
+ Slog.i(TAG, "Invalid empty OEM component name.");
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Generate a new launch intent to the Credential Selector UI.
+ *
+ * @param context the CredentialManager system service (only expected caller)
+ * context that may be used to query existence of the key UI
+ * application
+ * @param disabledProviderDataList the list of disabled provider data that when non-empty the
+ * UI should accordingly generate an entry suggesting the user
+ * to navigate to settings and enable them
+ * @param enabledProviderDataList the list of enabled provider that contain options for this
+ * request; the UI should render each option to the user for
+ * selection
+ * @param requestInfo the display information about the given app request
+ * @param resultReceiver used by the UI to send the UI selection result back
+ */
@NonNull
public static Intent createCredentialSelectorIntent(
+ @NonNull Context context,
@NonNull RequestInfo requestInfo,
@SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
@NonNull
@@ -111,7 +188,7 @@
@NonNull
ArrayList<DisabledProviderData> disabledProviderDataList,
@NonNull ResultReceiver resultReceiver) {
- Intent intent = createCredentialSelectorIntent(requestInfo,
+ Intent intent = createCredentialSelectorIntent(context, requestInfo,
disabledProviderDataList, resultReceiver);
intent.putParcelableArrayListExtra(
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
@@ -120,8 +197,6 @@
/**
* Creates an Intent that cancels any UI matching the given request token id.
- *
- * @hide
*/
@NonNull
public static Intent createCancelUiIntent(@NonNull IBinder requestToken,
@@ -134,8 +209,8 @@
com.android.internal.R.string
.config_credentialManagerDialogComponent));
intent.setComponent(componentName);
- intent.putExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
- new CancelUiRequest(requestToken, shouldShowCancellationUi, appPackageName));
+ intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
+ new CancelSelectionRequest(requestToken, shouldShowCancellationUi, appPackageName));
return intent;
}
@@ -155,5 +230,8 @@
return ipcFriendly;
}
- private IntentFactory() {}
+ private IntentFactory() {
+ }
+
+ private static final String TAG = "CredManIntentHelper";
}
diff --git a/core/java/android/credentials/selection/IntentHelper.java b/core/java/android/credentials/selection/IntentHelper.java
index 6bcd05a..2c3a320 100644
--- a/core/java/android/credentials/selection/IntentHelper.java
+++ b/core/java/android/credentials/selection/IntentHelper.java
@@ -16,12 +16,16 @@
package android.credentials.selection;
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.content.Intent;
import android.os.ResultReceiver;
+import java.util.Collections;
import java.util.List;
/**
@@ -29,15 +33,17 @@
*
* @hide
*/
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
public final class IntentHelper {
/**
- * Attempts to extract a {@link CancelUiRequest} from the given intent; returns null
+ * Attempts to extract a {@link CancelSelectionRequest} from the given intent; returns null
* if not found.
*/
@Nullable
- public static CancelUiRequest extractCancelUiRequest(@NonNull Intent intent) {
- return intent.getParcelableExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
- CancelUiRequest.class);
+ public static CancelSelectionRequest extractCancelUiRequest(@NonNull Intent intent) {
+ return intent.getParcelableExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
+ CancelSelectionRequest.class);
}
/**
@@ -52,37 +58,44 @@
/**
* Attempts to extract the list of {@link GetCredentialProviderInfo} from the given intent;
- * returns null if not found.
+ * returns an empty list if not found.
*/
- @Nullable
- @SuppressLint("NullableCollection") // To be consistent with the nullable Intent extra APIs
- // and the other APIs in this class.
- public static List<GetCredentialProviderInfo> extractGetCredentialProviderDataList(
+ public static @NonNull List<GetCredentialProviderInfo> extractGetCredentialProviderInfoList(
@NonNull Intent intent) {
List<GetCredentialProviderData> providerList = intent.getParcelableArrayListExtra(
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
GetCredentialProviderData.class);
- return providerList == null ? null : providerList.stream().map(
+ return providerList == null ? Collections.emptyList() : providerList.stream().map(
GetCredentialProviderData::toGetCredentialProviderInfo).toList();
}
/**
* Attempts to extract the list of {@link CreateCredentialProviderInfo} from the given intent;
- * returns null if not found.
+ * returns an empty list if not found.
*/
- @Nullable
- @SuppressLint("NullableCollection") // To be consistent with the nullable Intent extra APIs
- // and the other APIs in this class.
- public static List<CreateCredentialProviderInfo> extractCreateCredentialProviderDataList(
- @NonNull Intent intent) {
+ public static @NonNull List<CreateCredentialProviderInfo>
+ extractCreateCredentialProviderInfoList(@NonNull Intent intent) {
List<CreateCredentialProviderData> providerList = intent.getParcelableArrayListExtra(
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
CreateCredentialProviderData.class);
- return providerList == null ? null : providerList.stream().map(
+ return providerList == null ? Collections.emptyList() : providerList.stream().map(
CreateCredentialProviderData::toCreateCredentialProviderInfo).toList();
}
/**
+ * Attempts to extract the list of {@link DisabledProviderInfo} from the given intent;
+ * returns an empty list if not found.
+ */
+ public static @NonNull List<DisabledProviderInfo> extractDisabledProviderInfoList(
+ @NonNull Intent intent) {
+ List<DisabledProviderData> providerList = intent.getParcelableArrayListExtra(
+ ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST,
+ DisabledProviderData.class);
+ return providerList == null ? Collections.emptyList() : providerList.stream().map(
+ DisabledProviderData::toDisabledProviderInfo).toList();
+ }
+
+ /**
* Attempts to extract a {@link android.os.ResultReceiver} from the given intent, which should
* be used to send back UI results; returns null if not found.
*/
diff --git a/core/java/android/credentials/selection/RequestInfo.java b/core/java/android/credentials/selection/RequestInfo.java
index 7d6ea7e..2fd322a 100644
--- a/core/java/android/credentials/selection/RequestInfo.java
+++ b/core/java/android/credentials/selection/RequestInfo.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
@@ -41,13 +42,15 @@
*
* @hide
*/
-@TestApi
+@SystemApi
@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
public final class RequestInfo implements Parcelable {
/**
* The intent extra key for the {@code RequestInfo} object when launching the UX
* activities.
+ *
+ * @hide
*/
@NonNull
public static final String EXTRA_REQUEST_INFO =
@@ -79,7 +82,7 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @StringDef(value = {TYPE_GET, TYPE_CREATE})
+ @StringDef(value = {TYPE_GET, TYPE_CREATE, TYPE_UNDEFINED})
public @interface RequestType {
}
@@ -107,18 +110,13 @@
private final boolean mHasPermissionToOverrideDefault;
- /** Creates new {@code RequestInfo} for a create-credential flow. */
- @NonNull
- public static RequestInfo newCreateRequestInfo(
- @NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
- @NonNull String appPackageName) {
- return new RequestInfo(
- token, TYPE_CREATE, appPackageName, createCredentialRequest, null,
- /*hasPermissionToOverrideDefault=*/ false,
- /*defaultProviderIds=*/ new ArrayList<>());
- }
-
- /** Creates new {@code RequestInfo} for a create-credential flow. */
+ /**
+ * Creates new {@code RequestInfo} for a create-credential flow.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
@NonNull
public static RequestInfo newCreateRequestInfo(
@NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
@@ -129,7 +127,13 @@
hasPermissionToOverrideDefault, defaultProviderIds);
}
- /** Creates new {@code RequestInfo} for a get-credential flow. */
+ /**
+ * Creates new {@code RequestInfo} for a get-credential flow.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
@NonNull
public static RequestInfo newGetRequestInfo(
@NonNull IBinder token, @NonNull GetCredentialRequest getCredentialRequest,
@@ -140,24 +144,17 @@
/*defaultProviderIds=*/ new ArrayList<>());
}
- /** Creates new {@code RequestInfo} for a get-credential flow. */
- @NonNull
- public static RequestInfo newGetRequestInfo(
- @NonNull IBinder token, @NonNull GetCredentialRequest getCredentialRequest,
- @NonNull String appPackageName) {
- return new RequestInfo(
- token, TYPE_GET, appPackageName, null, getCredentialRequest,
- /*hasPermissionToOverrideDefault=*/ false,
- /*defaultProviderIds=*/ new ArrayList<>());
- }
-
/** Returns whether the calling package has the permission. */
public boolean hasPermissionToOverrideDefault() {
return mHasPermissionToOverrideDefault;
}
- /** Returns the request token matching the user request. */
+ /**
+ * Returns the request token matching the user request.
+ *
+ * @hide
+ */
@NonNull
public IBinder getToken() {
return mToken;
@@ -185,6 +182,12 @@
return mCreateCredentialRequest;
}
+ /** Returns the request token matching the app request that should be cancelled. */
+ @NonNull
+ public RequestToken getRequestToken() {
+ return new RequestToken(mToken);
+ }
+
/**
* Returns default provider identifiers (component or package name) configured from the user
* settings.
diff --git a/core/java/android/credentials/selection/RequestToken.java b/core/java/android/credentials/selection/RequestToken.java
new file mode 100644
index 0000000..27b83f8
--- /dev/null
+++ b/core/java/android/credentials/selection/RequestToken.java
@@ -0,0 +1,65 @@
+/*
+ * 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.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.IBinder;
+
+/**
+ * Unique identifier for a getCredential / createCredential API session.
+ *
+ * To compare if two requests pertain to the same session, compare their RequestTokens using
+ * the {@link RequestToken#equals(Object)} method.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class RequestToken {
+
+ @NonNull
+ private final IBinder mToken;
+
+ /** @hide */
+ @TestApi
+ @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+ public RequestToken(@NonNull IBinder token) {
+ mToken = token;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || !(obj instanceof RequestToken)) {
+ return false;
+ }
+ final RequestToken other = (RequestToken) obj;
+ return mToken.equals(other.mToken);
+ }
+
+ @Override
+ public int hashCode() {
+ return mToken.hashCode();
+ }
+}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 35ae3c9..5dfeac7 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -693,7 +693,7 @@
* Begins a transaction in DEFERRED mode, with the android-specific constraint that the
* transaction is read-only. The database may not be modified inside a read-only transaction.
* <p>
- * Read-only transactions may run concurrently with other read-only transactions, and if they
+ * Read-only transactions may run concurrently with other read-only transactions, and if the
* database is in WAL mode, they may also run concurrently with IMMEDIATE or EXCLUSIVE
* transactions.
* <p>
diff --git a/core/java/android/hardware/input/PhysicalKeyLayout.java b/core/java/android/hardware/input/PhysicalKeyLayout.java
index 241c452..3454c39 100644
--- a/core/java/android/hardware/input/PhysicalKeyLayout.java
+++ b/core/java/android/hardware/input/PhysicalKeyLayout.java
@@ -23,8 +23,6 @@
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
-import java.util.Locale;
-
/**
* A complimentary class to {@link KeyboardLayoutPreviewDrawable} describing the physical key layout
* of a Physical keyboard and provides information regarding the scan codes produced by the physical
@@ -339,9 +337,9 @@
}
int utf8Char = (kcm.get(keyCode, modifierState) & KeyCharacterMap.COMBINING_ACCENT_MASK);
if (Character.isValidCodePoint(utf8Char)) {
- return String.valueOf(Character.toChars(utf8Char)).toUpperCase(Locale.getDefault());
+ return String.valueOf(Character.toChars(utf8Char));
} else {
- return String.valueOf(kcm.getDisplayLabel(keyCode)).toUpperCase(Locale.getDefault());
+ return String.valueOf(kcm.getDisplayLabel(keyCode));
}
}
@@ -400,11 +398,11 @@
private final String mAltGrText;
public KeyGlyph(KeyCharacterMap kcm, int keyCode) {
- mBaseText = getKeyText(kcm, keyCode, 0);
+ mBaseText = getKeyText(kcm, keyCode, KeyEvent.META_CAPS_LOCK_ON);
mShiftText = getKeyText(kcm, keyCode,
KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON);
mAltGrText = getKeyText(kcm, keyCode,
- KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON);
+ KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_CAPS_LOCK_ON);
}
public String getBaseText() {
diff --git a/core/java/android/hardware/radio/OWNERS b/core/java/android/hardware/radio/OWNERS
index 302fdd7..51a85e4 100644
--- a/core/java/android/hardware/radio/OWNERS
+++ b/core/java/android/hardware/radio/OWNERS
@@ -1,4 +1,3 @@
xuweilin@google.com
oscarazu@google.com
ericjeong@google.com
-keunyoung@google.com
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 71698e4..281ee50 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1256,7 +1256,9 @@
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 c0d1fb9..800ba6d 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -150,4 +150,5 @@
void setBootUser(int userId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS})")
int getBootUser();
+ int[] getProfileIdsExcludingHidden(int userId, boolean enabledOnly);
}
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index e6bfcd7..224b10d 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -313,25 +313,33 @@
* 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 ad0f940..0da19df 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5357,6 +5357,25 @@
}
/**
+ * @return A list of ids of profiles associated with the specified user excluding those with
+ * {@link UserProperties#getProfileApiVisibility()} set to hidden. The returned list includes
+ * the user itself.
+ * @hide
+ * @see #getProfileIds(int, boolean)
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS}, conditional = true)
+ public int[] getProfileIdsExcludingHidden(@UserIdInt int userId, boolean enabled) {
+ try {
+ return mService.getProfileIdsExcludingHidden(userId, enabled);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the device credential owner id of the profile from
* which this method is called, or userId if called from a user that
* is not a profile.
diff --git a/core/java/android/os/WorkDuration.java b/core/java/android/os/WorkDuration.java
index 2ebcd83..5a54e90 100644
--- a/core/java/android/os/WorkDuration.java
+++ b/core/java/android/os/WorkDuration.java
@@ -83,7 +83,7 @@
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 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 @@
* 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 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 76fda06..ef2d5eb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -18161,6 +18161,16 @@
"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 82559da..744f446 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.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 @@
}
@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 @@
@Override
protected void onStop(StopCallbackArguments args) {
mOnStopStaticCallback.run();
- mHandlerMappings.remove(instanceIndex);
}
};
}
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index f819c9b..db665a9 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -27,9 +27,7 @@
import static android.view.DisplayCutoutProto.SIDE_OVERRIDES;
import static android.view.DisplayCutoutProto.WATERFALL_INSETS;
import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
@@ -168,6 +166,9 @@
// The side index is always under the natural rotation of the device.
private int[] mSideOverrides;
+ static final int[] INVALID_OVERRIDES = new int[]{INVALID_SIDE_OVERRIDE, INVALID_SIDE_OVERRIDE,
+ INVALID_SIDE_OVERRIDE, INVALID_SIDE_OVERRIDE};
+
/** @hide */
@IntDef(prefix = { "BOUNDS_POSITION_" }, value = {
BOUNDS_POSITION_LEFT,
@@ -1157,35 +1158,25 @@
final int resourceId = index >= 0 && index < array.length()
? array.getResourceId(index, ID_NULL)
: ID_NULL;
- final String[] rawOverrides = resourceId != ID_NULL
- ? array.getResources().getStringArray(resourceId)
- : res.getStringArray(R.array.config_mainBuiltInDisplayCutoutSideOverride);
+ final int[] rawOverrides = resourceId != ID_NULL
+ ? array.getResources().getIntArray(resourceId)
+ : res.getIntArray(R.array.config_mainBuiltInDisplayCutoutSideOverride);
array.recycle();
- final int[] override = new int[]{INVALID_SIDE_OVERRIDE, INVALID_SIDE_OVERRIDE,
- INVALID_SIDE_OVERRIDE, INVALID_SIDE_OVERRIDE};
- for (String rawOverride : rawOverrides) {
- int rotation;
- String[] split = rawOverride.split(" *, *");
- switch (split[0]) {
- case "0" -> rotation = ROTATION_0;
- case "90" -> rotation = ROTATION_90;
- case "180" -> rotation = ROTATION_180;
- case "270" -> rotation = ROTATION_270;
- default -> throw new IllegalArgumentException("Invalid side override definition: "
- + rawOverride);
- }
- int side;
- switch (split[1]) {
- case SIDE_STRING_LEFT -> side = BOUNDS_POSITION_LEFT;
- case SIDE_STRING_TOP -> side = BOUNDS_POSITION_TOP;
- case SIDE_STRING_RIGHT -> side = BOUNDS_POSITION_RIGHT;
- case SIDE_STRING_BOTTOM -> side = BOUNDS_POSITION_BOTTOM;
- default -> throw new IllegalArgumentException("Invalid side override definition: "
- + rawOverride);
- }
- override[rotation] = side;
+ if (rawOverrides.length == 0) {
+ return INVALID_OVERRIDES;
+ } else if (rawOverrides.length != 4) {
+ throw new IllegalArgumentException(
+ "Invalid side override definition, exact 4 overrides required: "
+ + Arrays.toString(rawOverrides));
}
- return override;
+ for (int rotation = ROTATION_0; rotation <= ROTATION_270; rotation++) {
+ if (rawOverrides[rotation] < BOUNDS_POSITION_LEFT
+ || rawOverrides[rotation] >= BOUNDS_POSITION_LENGTH) {
+ throw new IllegalArgumentException("Invalid side override definition: "
+ + Arrays.toString(rawOverrides));
+ }
+ }
+ return rawOverrides;
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 38cf490..ac2a66e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1472,6 +1472,34 @@
"android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
/**
+ * Activity-level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * that declares whether this (embedded) activity allows the system to share its state with the
+ * host app when it is embedded in a different process in
+ * {@link android.R.attr#allowUntrustedActivityEmbedding untrusted mode}.
+ *
+ * <p>If this property is "true", the host app may receive event callbacks for the activity
+ * state change, including the reparent event and the component name of the activity, which are
+ * required to restore the embedding state after the embedded activity exits picture-in-picture
+ * mode. This property does not share any of the activity content with the host. Note that, for
+ * {@link android.R.attr#knownActivityEmbeddingCerts trusted embedding}, the reparent event and
+ * the component name are always shared with the host regardless of the value of this property.
+ *
+ * <p>The default value is {@code false}.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <activity>
+ * <property
+ * android:name="android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING"
+ * android:value="true|false"/>
+ * </activity>
+ * </pre>
+ */
+ @FlaggedApi(Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING)
+ String PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING =
+ "android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING";
+
+ /**
* Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
* that an app can specify to inform the system that the app is activity embedding split feature
* enabled.
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 146b576..0deaca1 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -67,7 +67,6 @@
import android.view.accessibility.AccessibilityEvent.EventType;
import com.android.internal.R;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IntPair;
@@ -157,6 +156,22 @@
public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
"com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
+ /**
+ * Used as an int value for accessibility chooser activity to represent the accessibility button
+ * shortcut type.
+ *
+ * @hide
+ */
+ public static final int ACCESSIBILITY_BUTTON = 0;
+
+ /**
+ * Used as an int value for accessibility chooser activity to represent hardware key shortcut,
+ * such as volume key button.
+ *
+ * @hide
+ */
+ public static final int ACCESSIBILITY_SHORTCUT_KEY = 1;
+
/** @hide */
public static final int FLASH_REASON_CALL = 1;
@@ -170,6 +185,32 @@
public static final int FLASH_REASON_PREVIEW = 4;
/**
+ * Annotations for the shortcut type.
+ * <p>Note: Keep in sync with {@link #SHORTCUT_TYPES}.</p>
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ // LINT.IfChange(shortcut_type_intdef)
+ ACCESSIBILITY_BUTTON,
+ ACCESSIBILITY_SHORTCUT_KEY
+ // LINT.ThenChange(:shortcut_type_array)
+ })
+ public @interface ShortcutType {}
+
+ /**
+ * Used for iterating through {@link ShortcutType}.
+ * <p>Note: Keep in sync with {@link ShortcutType}.</p>
+ * @hide
+ */
+ public static final int[] SHORTCUT_TYPES = {
+ // LINT.IfChange(shortcut_type_array)
+ ACCESSIBILITY_BUTTON,
+ ACCESSIBILITY_SHORTCUT_KEY,
+ // LINT.ThenChange(:shortcut_type_intdef)
+ };
+
+ /**
* Annotations for content flag of UI.
* @hide
*/
@@ -1745,8 +1786,7 @@
@TestApi
@RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
@NonNull
- public List<String> getAccessibilityShortcutTargets(
- @ShortcutConstants.UserShortcutType int shortcutType) {
+ public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
final IAccessibilityManager service;
synchronized (mLock) {
service = getServiceLocked();
diff --git a/core/java/android/view/accessibility/MagnificationAnimationCallback.java b/core/java/android/view/accessibility/MagnificationAnimationCallback.java
index 72518db..1755497 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 @@
* 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/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 2433bd8..738bb1f 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5044,10 +5044,7 @@
* @param viewId The id of the {@link AdapterView}
* @param intent The intent of the service which will be
* providing data to the RemoteViewsAdapter
- * @deprecated use
- * {@link #setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)} instead
*/
- @Deprecated
public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
if (remoteAdapterConversion()) {
addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 90d5140..9a4106d9 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -1259,6 +1259,7 @@
int lastBaselineToBottomHeight = -1;
float lineHeight = -1f;
int lineHeightUnit = -1;
+ boolean hasUseBoundForWidthValue = false;
readTextAppearance(context, a, attributes, true /* styleArray */);
@@ -1613,6 +1614,9 @@
lineHeight = a.getDimensionPixelSize(attr, -1);
}
break;
+ case com.android.internal.R.styleable.TextView_useBoundsForWidth:
+ mUseBoundsForWidth = a.getBoolean(attr, false);
+ hasUseBoundForWidthValue = true;
}
}
@@ -1639,10 +1643,12 @@
mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE;
}
- if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) {
- mUseBoundsForWidth = ClientFlags.useBoundsForWidth();
- } else {
- mUseBoundsForWidth = false;
+ if (!hasUseBoundForWidthValue) {
+ if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) {
+ mUseBoundsForWidth = ClientFlags.useBoundsForWidth();
+ } else {
+ mUseBoundsForWidth = false;
+ }
}
// TODO(b/179693024): Use a ChangeId instead.
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index cc875ad..c76c7a4 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -21,6 +21,7 @@
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
@@ -93,7 +94,8 @@
| FLAG_WATCH_OUTSIDE_TOUCH
| FLAG_SPLIT_TOUCH
| FLAG_SCALED
- | FLAG_SECURE;
+ | FLAG_SECURE
+ | FLAG_DIM_BEHIND;
private static final RectF sTmpSnapshotSize = new RectF();
private static final RectF sTmpDstFrame = new RectF();
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index e32c8e5..739cf0e 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -17,6 +17,7 @@
package android.window;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.view.Surface.ROTATION_0;
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@@ -31,6 +32,7 @@
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.view.Display;
+import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.InsetsState;
import android.view.WindowInsets;
@@ -157,10 +159,15 @@
new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
currentDisplayInfo.getNaturalHeight()), isScreenRound,
ACTIVITY_TYPE_UNDEFINED);
- // Set the hardware-provided insets.
+ // Set the hardware-provided insets. Always with the ROTATION_0 result.
+ DisplayCutout cutout = currentDisplayInfo.displayCutout;
+ if (cutout != null && currentDisplayInfo.rotation != ROTATION_0) {
+ cutout = cutout.getRotated(
+ currentDisplayInfo.logicalWidth, currentDisplayInfo.logicalHeight,
+ currentDisplayInfo.rotation, ROTATION_0);
+ }
windowInsets = new WindowInsets.Builder(windowInsets).setRoundedCorners(
- currentDisplayInfo.roundedCorners)
- .setDisplayCutout(currentDisplayInfo.displayCutout).build();
+ currentDisplayInfo.roundedCorners).setDisplayCutout(cutout).build();
// Multiply default density scale because WindowMetrics provide the density value with
// the scaling factor for the Density Independent Pixel unit, which is the same unit
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 65075ae..baefe7b 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -226,6 +226,9 @@
setTopOnBackInvokedCallback(null);
}
+ // We should also stop running animations since all callbacks have been removed.
+ // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
+ Handler.getMain().post(mProgressAnimator::reset);
mAllCallbacks.clear();
mOnBackInvokedCallbacks.clear();
}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 2e20cce..bc63881 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -58,6 +58,14 @@
flag {
namespace: "windowing_sdk"
+ name: "untrusted_embedding_state_sharing"
+ description: "Feature flag to enable state sharing in untrusted embedding when apps opt in."
+ bug: "293647332"
+ is_fixed_read_only: true
+}
+
+flag {
+ namespace: "windowing_sdk"
name: "embedded_activity_back_nav_flag"
description: "Refines embedded activity back navigation behavior"
bug: "293642394"
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index b4395a7..de0f070 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -18,6 +18,7 @@
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
@@ -328,8 +329,7 @@
}
private AlertDialog createShortcutWarningDialog(int userId) {
- List<AccessibilityTarget> targets = getTargets(mContext,
- ShortcutConstants.UserShortcutType.HARDWARE);
+ List<AccessibilityTarget> targets = getTargets(mContext, ACCESSIBILITY_SHORTCUT_KEY);
if (targets.size() == 0) {
return null;
}
@@ -541,7 +541,7 @@
private ComponentName getShortcutTargetComponentName() {
final List<String> shortcutTargets = mFrameworkObjectProvider
.getAccessibilityManagerInstance(mContext)
- .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE);
+ .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY);
if (shortcutTargets.size() != 1) {
return null;
}
diff --git a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
index 353e182..7ec8838 100644
--- a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
+++ b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
@@ -44,27 +44,19 @@
* choose accessibility shortcut as preferred shortcut.
* {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly
* tapping screen 3 times as preferred shortcut.
- * {@code TWO_FINGERS_TRIPLE_TAP} for displaying specifying magnification to be toggled via
- * quickly tapping screen 3 times with two fingers as preferred shortcut.
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef(
- flag = true,
- value = {
- UserShortcutType.DEFAULT,
- UserShortcutType.SOFTWARE,
- UserShortcutType.HARDWARE,
- UserShortcutType.TRIPLETAP,
- UserShortcutType.TWO_FINGERS_TRIPLE_TAP,
- })
+ @IntDef({
+ UserShortcutType.DEFAULT,
+ UserShortcutType.SOFTWARE,
+ UserShortcutType.HARDWARE,
+ UserShortcutType.TRIPLETAP,
+ })
public @interface UserShortcutType {
int DEFAULT = 0;
- // LINT.IfChange(shortcut_type_intdef)
- int SOFTWARE = 1;
- int HARDWARE = 1 << 1;
- int TRIPLETAP = 1 << 2;
- int TWO_FINGERS_TRIPLE_TAP = 1 << 3;
- // LINT.ThenChange(:shortcut_type_array)
+ int SOFTWARE = 1; // 1 << 0
+ int HARDWARE = 2; // 1 << 1
+ int TRIPLETAP = 4; // 1 << 2
}
/**
@@ -72,12 +64,9 @@
* non-default IntDef types.
*/
public static final int[] USER_SHORTCUT_TYPES = {
- // LINT.IfChange(shortcut_type_array)
UserShortcutType.SOFTWARE,
UserShortcutType.HARDWARE,
- UserShortcutType.TRIPLETAP,
- UserShortcutType.TWO_FINGERS_TRIPLE_TAP,
- // LINT.ThenChange(:shortcut_type_intdef)
+ UserShortcutType.TRIPLETAP
};
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
index 33048dc..063154d 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
@@ -17,13 +17,14 @@
package com.android.internal.accessibility.dialog;
import static com.android.internal.accessibility.util.ShortcutUtils.convertToKey;
+import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
import android.accessibilityservice.AccessibilityShortcutInfo;
import android.annotation.NonNull;
import android.content.Context;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
@@ -32,8 +33,7 @@
*/
class AccessibilityActivityTarget extends AccessibilityTarget {
- AccessibilityActivityTarget(Context context,
- @ShortcutConstants.UserShortcutType int shortcutType,
+ AccessibilityActivityTarget(Context context, @ShortcutType int shortcutType,
@NonNull AccessibilityShortcutInfo shortcutInfo) {
super(context,
shortcutType,
@@ -44,7 +44,7 @@
shortcutInfo.getActivityInfo().applicationInfo.uid,
shortcutInfo.getActivityInfo().loadLabel(context.getPackageManager()),
shortcutInfo.getActivityInfo().loadIcon(context.getPackageManager()),
- convertToKey(shortcutType));
+ convertToKey(convertToUserType(shortcutType)));
}
@Override
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
index e084ebd..7eb09e5 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
@@ -17,6 +17,7 @@
package com.android.internal.accessibility.dialog;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -35,7 +36,6 @@
import android.widget.TextView;
import com.android.internal.R;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.widget.ResolverDrawerLayout;
import java.util.ArrayList;
@@ -85,7 +85,7 @@
prompt.setVisibility(View.VISIBLE);
}
- mTargets.addAll(getTargets(this, ShortcutConstants.UserShortcutType.SOFTWARE));
+ mTargets.addAll(getTargets(this, ACCESSIBILITY_BUTTON));
final GridView gridview = findViewById(R.id.accessibility_button_chooser_grid);
gridview.setAdapter(new ButtonTargetAdapter(mTargets));
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
index 7406da4..2b6913c 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
@@ -17,13 +17,14 @@
package com.android.internal.accessibility.dialog;
import static com.android.internal.accessibility.util.ShortcutUtils.convertToKey;
+import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.content.Context;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
@@ -35,9 +36,7 @@
private final AccessibilityServiceInfo mAccessibilityServiceInfo;
- AccessibilityServiceTarget(
- Context context,
- @ShortcutConstants.UserShortcutType int shortcutType,
+ AccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
@AccessibilityFragmentType int fragmentType,
@NonNull AccessibilityServiceInfo serviceInfo) {
super(context,
@@ -49,7 +48,7 @@
serviceInfo.getResolveInfo().serviceInfo.applicationInfo.uid,
serviceInfo.getResolveInfo().loadLabel(context.getPackageManager()),
serviceInfo.getResolveInfo().loadIcon(context.getPackageManager()),
- convertToKey(shortcutType));
+ convertToKey(convertToUserType(shortcutType)));
mAccessibilityServiceInfo = serviceInfo;
}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 8e2ec1b..2e80b7e 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -15,6 +15,10 @@
*/
package com.android.internal.accessibility.dialog;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static android.view.accessibility.AccessibilityManager.ShortcutType;
+
import static com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.createEnableDialogContentView;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets;
@@ -38,7 +42,6 @@
import android.widget.AdapterView;
import com.android.internal.R;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
@@ -49,8 +52,8 @@
* activity or allowlisting feature for volume key shortcut.
*/
public class AccessibilityShortcutChooserActivity extends Activity {
- @ShortcutConstants.UserShortcutType
- private final int mShortcutType = ShortcutConstants.UserShortcutType.HARDWARE;
+ @ShortcutType
+ private final int mShortcutType = ACCESSIBILITY_SHORTCUT_KEY;
private static final String KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE =
"accessibility_shortcut_menu_mode";
private final List<AccessibilityTarget> mTargets = new ArrayList<>();
@@ -243,7 +246,7 @@
mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT;
final int selectDialogTitleId = R.string.accessibility_select_shortcut_menu_title;
final int editDialogTitleId =
- mShortcutType == ShortcutConstants.UserShortcutType.SOFTWARE
+ mShortcutType == ACCESSIBILITY_BUTTON
? R.string.accessibility_edit_shortcut_menu_button_title
: R.string.accessibility_edit_shortcut_menu_volume_title;
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index 4ab1ee9..652cb52 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -16,6 +16,10 @@
package com.android.internal.accessibility.dialog;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+
+import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
import static com.android.internal.accessibility.util.ShortcutUtils.optInValueToSettings;
import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
@@ -26,6 +30,7 @@
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -42,7 +47,7 @@
public abstract class AccessibilityTarget implements TargetOperations, OnTargetSelectedListener,
OnTargetCheckedChangeListener {
private Context mContext;
- @ShortcutConstants.UserShortcutType
+ @ShortcutType
private int mShortcutType;
@AccessibilityFragmentType
private int mFragmentType;
@@ -56,8 +61,7 @@
private CharSequence mStateDescription;
@VisibleForTesting
- public AccessibilityTarget(
- Context context, @ShortcutConstants.UserShortcutType int shortcutType,
+ public AccessibilityTarget(Context context, @ShortcutType int shortcutType,
@AccessibilityFragmentType int fragmentType, boolean isShortcutSwitched, String id,
int uid, CharSequence label, Drawable icon, String key) {
mContext = context;
@@ -95,10 +99,10 @@
final AccessibilityManager am =
getContext().getSystemService(AccessibilityManager.class);
switch (getShortcutType()) {
- case ShortcutConstants.UserShortcutType.SOFTWARE:
+ case ACCESSIBILITY_BUTTON:
am.notifyAccessibilityButtonClicked(getContext().getDisplayId(), getId());
return;
- case ShortcutConstants.UserShortcutType.HARDWARE:
+ case ACCESSIBILITY_SHORTCUT_KEY:
am.performAccessibilityShortcut(getId());
return;
default:
@@ -110,9 +114,9 @@
public void onCheckedChanged(boolean isChecked) {
setShortcutEnabled(isChecked);
if (isChecked) {
- optInValueToSettings(getContext(), getShortcutType(), getId());
+ optInValueToSettings(getContext(), convertToUserType(getShortcutType()), getId());
} else {
- optOutValueFromSettings(getContext(), getShortcutType(), getId());
+ optOutValueFromSettings(getContext(), convertToUserType(getShortcutType()), getId());
}
}
@@ -138,7 +142,7 @@
return mContext;
}
- public @ShortcutConstants.UserShortcutType int getShortcutType() {
+ public @ShortcutType int getShortcutType() {
return mShortcutType;
}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index bd63e23..51a5ddf 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -16,6 +16,8 @@
package com.android.internal.accessibility.dialog;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
@@ -39,12 +41,12 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.R;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import java.util.ArrayList;
@@ -68,9 +70,8 @@
* @return The list of {@link AccessibilityTarget}.
* @hide
*/
- public static List<AccessibilityTarget> getTargets(
- Context context,
- @ShortcutConstants.UserShortcutType int shortcutType) {
+ public static List<AccessibilityTarget> getTargets(Context context,
+ @ShortcutType int shortcutType) {
// List all accessibility target
final List<AccessibilityTarget> installedTargets = getInstalledTargets(context,
shortcutType);
@@ -112,7 +113,7 @@
* @return The list of {@link AccessibilityTarget}.
*/
static List<AccessibilityTarget> getInstalledTargets(Context context,
- @ShortcutConstants.UserShortcutType int shortcutType) {
+ @ShortcutType int shortcutType) {
final List<AccessibilityTarget> targets = new ArrayList<>();
targets.addAll(getAccessibilityFilteredTargets(context, shortcutType));
targets.addAll(getAllowListingFeatureTargets(context, shortcutType));
@@ -121,7 +122,7 @@
}
private static List<AccessibilityTarget> getAccessibilityFilteredTargets(Context context,
- @ShortcutConstants.UserShortcutType int shortcutType) {
+ @ShortcutType int shortcutType) {
final List<AccessibilityTarget> serviceTargets =
getAccessibilityServiceTargets(context, shortcutType);
final List<AccessibilityTarget> activityTargets =
@@ -154,7 +155,7 @@
}
private static List<AccessibilityTarget> getAccessibilityServiceTargets(Context context,
- @ShortcutConstants.UserShortcutType int shortcutType) {
+ @ShortcutType int shortcutType) {
final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
Context.ACCESSIBILITY_SERVICE);
final List<AccessibilityServiceInfo> installedServices =
@@ -170,7 +171,7 @@
final boolean hasRequestAccessibilityButtonFlag =
(info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
if ((targetSdk <= Build.VERSION_CODES.Q) && !hasRequestAccessibilityButtonFlag
- && (shortcutType == ShortcutConstants.UserShortcutType.SOFTWARE)) {
+ && (shortcutType == ACCESSIBILITY_BUTTON)) {
continue;
}
@@ -181,7 +182,7 @@
}
private static List<AccessibilityTarget> getAccessibilityActivityTargets(Context context,
- @ShortcutConstants.UserShortcutType int shortcutType) {
+ @ShortcutType int shortcutType) {
final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
Context.ACCESSIBILITY_SERVICE);
final List<AccessibilityShortcutInfo> installedServices =
@@ -200,7 +201,7 @@
}
private static List<AccessibilityTarget> getAllowListingFeatureTargets(Context context,
- @ShortcutConstants.UserShortcutType int shortcutType) {
+ @ShortcutType int shortcutType) {
final List<AccessibilityTarget> targets = new ArrayList<>();
final int uid = context.getApplicationInfo().uid;
@@ -280,10 +281,8 @@
return targets;
}
- private static AccessibilityTarget createAccessibilityServiceTarget(
- Context context,
- @ShortcutConstants.UserShortcutType int shortcutType,
- @NonNull AccessibilityServiceInfo info) {
+ private static AccessibilityTarget createAccessibilityServiceTarget(Context context,
+ @ShortcutType int shortcutType, @NonNull AccessibilityServiceInfo info) {
switch (getAccessibilityServiceFragmentType(info)) {
case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
return new VolumeShortcutToggleAccessibilityServiceTarget(context, shortcutType,
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
index 641a9f1..1bc8b84 100644
--- a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
@@ -16,6 +16,9 @@
package com.android.internal.accessibility.dialog;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
import static com.android.internal.accessibility.util.ShortcutUtils.isComponentIdExistingInSettings;
@@ -25,6 +28,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.os.UserHandle;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
import android.view.accessibility.Flags;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -41,7 +45,7 @@
public class InvisibleToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
public InvisibleToggleAccessibilityServiceTarget(
- Context context, @UserShortcutType int shortcutType,
+ Context context, @ShortcutType int shortcutType,
@NonNull AccessibilityServiceInfo serviceInfo) {
super(context,
shortcutType,
@@ -68,10 +72,10 @@
private boolean isComponentIdExistingInOtherShortcut() {
switch (getShortcutType()) {
- case UserShortcutType.SOFTWARE:
+ case ACCESSIBILITY_BUTTON:
return isComponentIdExistingInSettings(getContext(), UserShortcutType.HARDWARE,
getId());
- case UserShortcutType.HARDWARE:
+ case ACCESSIBILITY_SHORTCUT_KEY:
return isComponentIdExistingInSettings(getContext(), UserShortcutType.SOFTWARE,
getId());
default:
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
index 2204c0b..c22f17d 100644
--- a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
@@ -18,8 +18,8 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
/**
@@ -28,8 +28,7 @@
*/
class InvisibleToggleAllowListingFeatureTarget extends AccessibilityTarget {
- InvisibleToggleAllowListingFeatureTarget(Context context,
- @ShortcutConstants.UserShortcutType int shortcutType,
+ InvisibleToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
boolean isShortcutSwitched, String id, int uid, CharSequence label, Drawable icon,
String key) {
super(context, shortcutType, AccessibilityFragmentType.INVISIBLE_TOGGLE, isShortcutSwitched,
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
index a6ef73e..a4ffef6 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
@@ -22,9 +22,9 @@
import android.annotation.NonNull;
import android.content.Context;
import android.view.View;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
import com.android.internal.R;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
@@ -45,8 +45,7 @@
float DISABLED = 0.5f;
}
- ToggleAccessibilityServiceTarget(Context context,
- @ShortcutConstants.UserShortcutType int shortcutType,
+ ToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
@NonNull AccessibilityServiceInfo serviceInfo) {
super(context,
shortcutType,
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
index 2a9c555..11e668f 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
@@ -21,9 +21,9 @@
import android.graphics.drawable.Drawable;
import android.provider.Settings;
import android.view.View;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
import com.android.internal.R;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
@@ -34,8 +34,7 @@
*/
class ToggleAllowListingFeatureTarget extends AccessibilityTarget {
- ToggleAllowListingFeatureTarget(Context context,
- @ShortcutConstants.UserShortcutType int shortcutType,
+ ToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
boolean isShortcutSwitched, String id, int uid, CharSequence label, Drawable icon,
String key) {
super(context, shortcutType, AccessibilityFragmentType.TOGGLE, isShortcutSwitched, id,
diff --git a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
index 4926e72..04f5061 100644
--- a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
@@ -16,6 +16,9 @@
package com.android.internal.accessibility.dialog;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
@@ -24,6 +27,7 @@
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
import android.widget.Toast;
import com.android.internal.R;
@@ -35,8 +39,7 @@
*/
class VolumeShortcutToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
- VolumeShortcutToggleAccessibilityServiceTarget(Context context,
- @UserShortcutType int shortcutType,
+ VolumeShortcutToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
@NonNull AccessibilityServiceInfo serviceInfo) {
super(context,
shortcutType,
@@ -47,10 +50,10 @@
@Override
public void onCheckedChanged(boolean isChecked) {
switch (getShortcutType()) {
- case UserShortcutType.SOFTWARE:
+ case ACCESSIBILITY_BUTTON:
onCheckedFromAccessibilityButton(isChecked);
return;
- case UserShortcutType.HARDWARE:
+ case ACCESSIBILITY_SHORTCUT_KEY:
super.onCheckedChanged(isChecked);
return;
default:
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index 1e4bcf2..6b074a6 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -21,6 +21,8 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__DISABLED;
@@ -45,8 +47,9 @@
import android.content.ComponentName;
import android.content.Context;
import android.provider.Settings;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.util.FrameworkStatsLog;
/** Methods for logging accessibility states. */
@@ -68,15 +71,15 @@
/**
* Logs accessibility feature name that is assigned to the given {@code shortcutType}.
- * Calls this when clicking the shortcut {@link ShortcutConstants.UserShortcutType.SOFTWARE} or
- * {@link ShortcutConstants.UserShortcutType.HARDWARE}.
+ * Calls this when clicking the shortcut {@link AccessibilityManager#ACCESSIBILITY_BUTTON} or
+ * {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY}.
*
* @param context context used to retrieve the {@link Settings} provider
* @param componentName component name of the accessibility feature
* @param shortcutType accessibility shortcut type
*/
public static void logAccessibilityShortcutActivated(Context context,
- ComponentName componentName, @ShortcutConstants.UserShortcutType int shortcutType) {
+ ComponentName componentName, @ShortcutType int shortcutType) {
logAccessibilityShortcutActivatedInternal(componentName,
convertToLoggingShortcutType(context, shortcutType), UNKNOWN_STATUS);
}
@@ -84,8 +87,8 @@
/**
* Logs accessibility feature name that is assigned to the given {@code shortcutType} and the
* {@code serviceEnabled} status.
- * Calls this when clicking the shortcut {@link ShortcutConstants.UserShortcutType.SOFTWARE}
- * or {@link ShortcutConstants.UserShortcutType.HARDWARE}.
+ * Calls this when clicking the shortcut {@link AccessibilityManager#ACCESSIBILITY_BUTTON}
+ * or {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY}.
*
* @param context context used to retrieve the {@link Settings} provider
* @param componentName component name of the accessibility feature
@@ -93,8 +96,7 @@
* @param serviceEnabled {@code true} if the service is enabled
*/
public static void logAccessibilityShortcutActivated(Context context,
- ComponentName componentName, @ShortcutConstants.UserShortcutType int shortcutType,
- boolean serviceEnabled) {
+ ComponentName componentName, @ShortcutType int shortcutType, boolean serviceEnabled) {
logAccessibilityShortcutActivatedInternal(componentName,
convertToLoggingShortcutType(context, shortcutType),
convertToLoggingServiceStatus(serviceEnabled));
@@ -233,9 +235,9 @@
}
private static int convertToLoggingShortcutType(Context context,
- @ShortcutConstants.UserShortcutType int shortcutType) {
+ @ShortcutType int shortcutType) {
switch (shortcutType) {
- case ShortcutConstants.UserShortcutType.SOFTWARE:
+ case ACCESSIBILITY_BUTTON:
if (isAccessibilityFloatingMenuEnabled(context)) {
return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
} else if (isAccessibilityGestureEnabled(context)) {
@@ -243,7 +245,7 @@
} else {
return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
}
- case ShortcutConstants.UserShortcutType.HARDWARE:
+ case ACCESSIBILITY_SHORTCUT_KEY:
return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
}
return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 276c5c4..3fd3030 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -16,6 +16,9 @@
package com.android.internal.accessibility.util;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
@@ -30,6 +33,7 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
import java.util.Collections;
import java.util.List;
@@ -140,7 +144,7 @@
* @param componentId The component id that need to be checked.
* @return {@code true} if a component id is contained.
*/
- public static boolean isShortcutContained(Context context, @UserShortcutType int shortcutType,
+ public static boolean isShortcutContained(Context context, @ShortcutType int shortcutType,
@NonNull String componentId) {
final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
Context.ACCESSIBILITY_SERVICE);
@@ -162,8 +166,6 @@
return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
case UserShortcutType.TRIPLETAP:
return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
- case UserShortcutType.TWO_FINGERS_TRIPLE_TAP:
- return Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED;
default:
throw new IllegalArgumentException(
"Unsupported user shortcut type: " + type);
@@ -171,6 +173,24 @@
}
/**
+ * Converts {@link ShortcutType} to {@link UserShortcutType}.
+ *
+ * @param type The shortcut type.
+ * @return Mapping type from {@link UserShortcutType}.
+ */
+ public static @UserShortcutType int convertToUserType(@ShortcutType int type) {
+ switch (type) {
+ case ACCESSIBILITY_BUTTON:
+ return UserShortcutType.SOFTWARE;
+ case ACCESSIBILITY_SHORTCUT_KEY:
+ return UserShortcutType.HARDWARE;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported shortcut type:" + type);
+ }
+ }
+
+ /**
* Updates an accessibility state if the accessibility service is a Always-On a11y service,
* a.k.a. AccessibilityServices that has FLAG_REQUEST_ACCESSIBILITY_BUTTON
* <p>
@@ -235,13 +255,12 @@
public static Set<String> getShortcutTargetsFromSettings(
Context context, @UserShortcutType int shortcutType, int userId) {
final String targetKey = convertToKey(shortcutType);
- if (Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED.equals(targetKey)
- || Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED
- .equals(targetKey)) {
+ if (Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED.equals(targetKey)) {
boolean magnificationEnabled = Settings.Secure.getIntForUser(
context.getContentResolver(), targetKey, /* def= */ 0, userId) == 1;
return magnificationEnabled ? Set.of(MAGNIFICATION_CONTROLLER_NAME)
: Collections.emptySet();
+
} else {
final String targetString = Settings.Secure.getStringForUser(
context.getContentResolver(), targetKey, userId);
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java
index e5247f9..852ed1c 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java
@@ -51,4 +51,6 @@
@ApplicationInfo.NativeHeapZeroInitialized
int getNativeHeapZeroInitialized();
+
+ boolean isUseEmbeddedDex();
}
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
index 212fb86..ff9b11a 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
@@ -54,6 +54,8 @@
@ApplicationInfo.NativeHeapZeroInitialized
private int nativeHeapZeroInitialized = ApplicationInfo.ZEROINIT_DEFAULT;
+ private boolean useEmbeddedDex;
+
public ParsedProcessImpl() {
}
@@ -65,6 +67,7 @@
gwpAsanMode = other.getGwpAsanMode();
memtagMode = other.getMemtagMode();
nativeHeapZeroInitialized = other.getNativeHeapZeroInitialized();
+ useEmbeddedDex = other.isUseEmbeddedDex();
}
public void addStateFrom(@NonNull ParsedProcess other) {
@@ -72,6 +75,7 @@
gwpAsanMode = other.getGwpAsanMode();
memtagMode = other.getMemtagMode();
nativeHeapZeroInitialized = other.getNativeHeapZeroInitialized();
+ useEmbeddedDex = other.isUseEmbeddedDex();
final ArrayMap<String, String> oacn = other.getAppClassNamesByPackage();
for (int i = 0; i < oacn.size(); i++) {
@@ -115,7 +119,8 @@
@NonNull Set<String> deniedPermissions,
@ApplicationInfo.GwpAsanMode int gwpAsanMode,
@ApplicationInfo.MemtagMode int memtagMode,
- @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) {
+ @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized,
+ boolean useEmbeddedDex) {
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
@@ -134,6 +139,7 @@
this.nativeHeapZeroInitialized = nativeHeapZeroInitialized;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+ this.useEmbeddedDex = useEmbeddedDex;
// onConstructed(); // You can define this method to get a callback
}
@@ -172,6 +178,11 @@
}
@DataClass.Generated.Member
+ public boolean isUseEmbeddedDex() {
+ return useEmbeddedDex;
+ }
+
+ @DataClass.Generated.Member
public @NonNull ParsedProcessImpl setName(@NonNull String value) {
name = value;
com.android.internal.util.AnnotationValidations.validate(
@@ -223,6 +234,12 @@
}
@DataClass.Generated.Member
+ public @NonNull ParsedProcessImpl setUseEmbeddedDex( boolean value) {
+ useEmbeddedDex = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
static Parcelling<Set<String>> sParcellingForDeniedPermissions =
Parcelling.Cache.get(
Parcelling.BuiltIn.ForInternedStringSet.class);
@@ -239,6 +256,9 @@
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
+ byte flg = 0;
+ if (useEmbeddedDex) flg |= 0x40;
+ dest.writeByte(flg);
dest.writeString(name);
dest.writeMap(appClassNamesByPackage);
sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags);
@@ -258,6 +278,8 @@
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
+ byte flg = in.readByte();
+ boolean _useEmbeddedDex = (flg & 0x40) != 0;
String _name = in.readString();
ArrayMap<String,String> _appClassNamesByPackage = new ArrayMap();
in.readMap(_appClassNamesByPackage, String.class.getClassLoader());
@@ -284,6 +306,7 @@
this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+ this.useEmbeddedDex = _useEmbeddedDex;
// onConstructed(); // You can define this method to get a callback
}
@@ -303,10 +326,10 @@
};
@DataClass.Generated(
- time = 1701445656489L,
+ time = 1706177189475L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java",
- inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic void addStateFrom(com.android.internal.pm.pkg.component.ParsedProcess)\npublic void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedProcess, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
+ inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\nprivate boolean useEmbeddedDex\npublic void addStateFrom(com.android.internal.pm.pkg.component.ParsedProcess)\npublic void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedProcess, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java
index 3b2056e..dd58815f 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java
@@ -27,6 +27,7 @@
import android.util.ArraySet;
import com.android.internal.R;
+import com.android.internal.pm.pkg.component.flags.Flags;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
import com.android.internal.pm.pkg.parsing.ParsingUtils;
import com.android.internal.util.CollectionUtils;
@@ -111,6 +112,12 @@
proc.setNativeHeapZeroInitialized(
v ? ApplicationInfo.ZEROINIT_ENABLED : ApplicationInfo.ZEROINIT_DISABLED);
}
+ if (Flags.enablePerProcessUseEmbeddedDexAttr()) {
+ proc.setUseEmbeddedDex(
+ sa.getBoolean(R.styleable.AndroidManifestProcess_useEmbeddedDex, false));
+ } else {
+ proc.setUseEmbeddedDex(false);
+ }
} finally {
sa.recycle();
}
diff --git a/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig b/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig
new file mode 100644
index 0000000..ea9abdb
--- /dev/null
+++ b/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.internal.pm.pkg.component.flags"
+
+flag {
+ name: "enable_per_process_use_embedded_dex_attr"
+ namespace: "machine_learning"
+ description: "This flag enables support for parsing per-process useEmbeddedDex attribute."
+ is_fixed_read_only: true
+ bug: "295870718"
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 30e4099..f8f1049 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -55,7 +55,9 @@
* A message of a {@link MessagingLayout}.
*/
@RemoteViews.RemoteView
-public class MessagingGroup extends LinearLayout implements MessagingLinearLayout.MessagingChild {
+public class MessagingGroup extends NotificationOptimizedLinearLayout implements
+ MessagingLinearLayout.MessagingChild {
+
private static final MessagingPool<MessagingGroup> sInstancePool =
new MessagingPool<>(10);
diff --git a/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java b/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java
new file mode 100644
index 0000000..b5e9b8f
--- /dev/null
+++ b/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java
@@ -0,0 +1,578 @@
+/*
+ * 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.internal.widget;
+
+import static android.widget.flags.Flags.notifLinearlayoutOptimized;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Trace;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This LinearLayout customizes the measurement behavior of LinearLayout for Notification layouts.
+ * When there is exactly
+ * one child View with <code>layout_weight</code>. onMeasure methods of this LinearLayout will:
+ * 1. Measure all other children.
+ * 2. Calculate the remaining space for the View with <code>layout_weight</code>
+ * 3. Measure the weighted View using the calculated remaining width or height (based on
+ * Orientation).
+ * This ensures that the weighted View fills the remaining space in LinearLayout with only single
+ * measure.
+ *
+ * **Assumptions:**
+ * - There is *exactly one* child view with non-zero <code>layout_weight</code>.
+ * - Other views should not have weight.
+ * - LinearLayout doesn't have <code>weightSum</code>.
+ * - Horizontal LinearLayout's width should be measured EXACTLY.
+ * - Horizontal LinearLayout shouldn't need baseLineAlignment.
+ * - Vertical LinearLayout shouldn't have MATCH_PARENT children when it is not measured EXACTLY.
+ *
+ * @hide
+ */
+@RemoteViews.RemoteView
+public class NotificationOptimizedLinearLayout extends LinearLayout {
+ private static final boolean DEBUG_LAYOUT = false;
+ private static final boolean TRACE_ONMEASURE = Build.isDebuggable();
+ private static final String TAG = "NotifOptimizedLinearLayout";
+
+ private boolean mShouldUseOptimizedLayout = false;
+
+ public NotificationOptimizedLinearLayout(Context context) {
+ super(context);
+ }
+
+ public NotificationOptimizedLinearLayout(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public NotificationOptimizedLinearLayout(Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public NotificationOptimizedLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final View weightedChildView = getSingleWeightedChild();
+ mShouldUseOptimizedLayout =
+ isUseOptimizedLinearLayoutFlagEnabled() && weightedChildView != null
+ && isLinearLayoutUsable(widthMeasureSpec, heightMeasureSpec);
+
+ if (mShouldUseOptimizedLayout) {
+ onMeasureOptimized(weightedChildView, widthMeasureSpec, heightMeasureSpec);
+ } else {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ private boolean isUseOptimizedLinearLayoutFlagEnabled() {
+ final boolean enabled = notifLinearlayoutOptimized();
+ if (!enabled) {
+ logSkipOptimizedOnMeasure("enableNotifLinearlayoutOptimized flag is off.");
+ }
+ return enabled;
+ }
+
+ /**
+ * Checks if optimizations can be safely applied to this LinearLayout during layout
+ * calculations. Optimizations might be disabled in the following cases:
+ *
+ * **weightSum**: When LinearLayout has weightSum
+ * ** MATCH_PARENT children in non EXACT dimension**
+ * **Horizontal LinearLayout with non-EXACT width**
+ * **Baseline Alignment:** If views need to align their baselines in Horizontal LinearLayout
+ *
+ * @param widthMeasureSpec The width measurement specification.
+ * @param heightMeasureSpec The height measurement specification.
+ * @return `true` if optimization is possible, `false` otherwise.
+ */
+ private boolean isLinearLayoutUsable(int widthMeasureSpec, int heightMeasureSpec) {
+ final boolean hasWeightSum = getWeightSum() > 0.0f;
+ if (hasWeightSum) {
+ logSkipOptimizedOnMeasure("Has weightSum.");
+ return false;
+ }
+
+ if (requiresMatchParentRemeasureForVerticalLinearLayout(widthMeasureSpec)) {
+ logSkipOptimizedOnMeasure(
+ "Vertical LinearLayout requires children width MATCH_PARENT remeasure ");
+ return false;
+ }
+
+ final boolean isHorizontal = getOrientation() == HORIZONTAL;
+ if (isHorizontal && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) {
+ logSkipOptimizedOnMeasure("Horizontal LinearLayout's width should be "
+ + "measured EXACTLY");
+ return false;
+ }
+
+ if (requiresBaselineAlignmentForHorizontalLinearLayout()) {
+ logSkipOptimizedOnMeasure("Need to apply baseline.");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return if the vertical linearlayout requires match_parent children remeasure
+ */
+ private boolean requiresMatchParentRemeasureForVerticalLinearLayout(int widthMeasureSpec) {
+ // HORIZONTAL measuring is handled by LinearLayout. That's why we don't need to check it
+ // here.
+ if (getOrientation() == HORIZONTAL) {
+ return false;
+ }
+
+ // When the width is not EXACT, children with MATCH_PARENT width need to be double measured.
+ // This needs to be handled in LinearLayout because NotificationOptimizedLinearLayout
+ final boolean nonExactWidth =
+ MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY;
+ final List<View> activeChildren = getActiveChildren();
+ for (int i = 0; i < activeChildren.size(); i++) {
+ final View child = activeChildren.get(i);
+ final ViewGroup.LayoutParams lp = child.getLayoutParams();
+ if (nonExactWidth && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return if this layout needs to apply baseLineAlignment.
+ */
+ private boolean requiresBaselineAlignmentForHorizontalLinearLayout() {
+ // baseLineAlignment is not important for Vertical LinearLayout.
+ if (getOrientation() == VERTICAL) {
+ return false;
+ }
+ // Early return, if it is already disabled
+ if (!isBaselineAligned()) {
+ return false;
+ }
+
+ final List<View> activeChildren = getActiveChildren();
+ final int minorGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
+
+ for (int i = 0; i < activeChildren.size(); i++) {
+ final View child = activeChildren.get(i);
+ if (child.getLayoutParams() instanceof LayoutParams) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ int childBaseline = -1;
+
+ if (lp.height != LayoutParams.MATCH_PARENT) {
+ childBaseline = child.getBaseline();
+ }
+ if (childBaseline == -1) {
+ // This child doesn't have a baseline.
+ continue;
+ }
+ int gravity = lp.gravity;
+ if (gravity < 0) {
+ gravity = minorGravity;
+ }
+
+ final int result = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+ if (result == Gravity.TOP || result == Gravity.BOTTOM) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Finds the single child view within this layout that has a non-zero weight assigned to its
+ * LayoutParams.
+ *
+ * @return The weighted child view, or null if multiple weighted children exist or no weighted
+ * children are found.
+ */
+ @Nullable
+ private View getSingleWeightedChild() {
+ final boolean isVertical = getOrientation() == VERTICAL;
+ final List<View> activeChildren = getActiveChildren();
+ View singleWeightedChild = null;
+ for (int i = 0; i < activeChildren.size(); i++) {
+ final View child = activeChildren.get(i);
+ if (child.getLayoutParams() instanceof LayoutParams) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if ((!isVertical && lp.width == ViewGroup.LayoutParams.MATCH_PARENT)
+ || (isVertical && lp.height == ViewGroup.LayoutParams.MATCH_PARENT)) {
+ logSkipOptimizedOnMeasure(
+ "There is a match parent child in the related orientation.");
+ return null;
+ }
+ if (lp.weight != 0) {
+ if (singleWeightedChild == null) {
+ singleWeightedChild = child;
+ } else {
+ logSkipOptimizedOnMeasure("There is more than one weighted child.");
+ return null;
+ }
+ }
+ }
+ }
+ if (singleWeightedChild == null) {
+ logSkipOptimizedOnMeasure("There is no weighted child in this layout.");
+ } else {
+ final LayoutParams lp = (LayoutParams) singleWeightedChild.getLayoutParams();
+ boolean isHeightWrapContentOrZero =
+ lp.height == ViewGroup.LayoutParams.WRAP_CONTENT || lp.height == 0;
+ boolean isWidthWrapContentOrZero =
+ lp.width == ViewGroup.LayoutParams.WRAP_CONTENT || lp.width == 0;
+ if ((isVertical && !isHeightWrapContentOrZero)
+ || (!isVertical && !isWidthWrapContentOrZero)) {
+ logSkipOptimizedOnMeasure(
+ "Single weighted child should be either WRAP_CONTENT or 0"
+ + " in the related orientation");
+ singleWeightedChild = null;
+ }
+ }
+
+ return singleWeightedChild;
+ }
+
+ /**
+ * Optimized measurement for the single weighted child in this LinearLayout.
+ * Measures other children, calculates remaining space, then measures the weighted
+ * child using the remaining width (or height).
+ *
+ * Note: Horizontal LinearLayout doesn't need to apply baseline in optimized case @see
+ * {@link #requiresBaselineAlignmentForHorizontalLinearLayout}.
+ *
+ * @param weightedChildView The weighted child view(with `layout_weight!=0`)
+ * @param widthMeasureSpec The width MeasureSpec to use for measurement
+ * @param heightMeasureSpec The height MeasureSpec to use for measurement.
+ */
+ private void onMeasureOptimized(@NonNull View weightedChildView, int widthMeasureSpec,
+ int heightMeasureSpec) {
+ try {
+ if (TRACE_ONMEASURE) {
+ Trace.beginSection("NotifOptimizedLinearLayout#onMeasure");
+ }
+
+ if (getOrientation() == LinearLayout.HORIZONTAL) {
+ final ViewGroup.LayoutParams lp = weightedChildView.getLayoutParams();
+ final int childWidth = lp.width;
+ final boolean isBaselineAligned = isBaselineAligned();
+ // It should be marked 0 so that it use excessSpace in LinearLayout's onMeasure
+ lp.width = 0;
+
+ // It doesn't need to apply baseline. So disable it.
+ setBaselineAligned(false);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ // restore values.
+ lp.width = childWidth;
+ setBaselineAligned(isBaselineAligned);
+ } else {
+ measureVerticalOptimized(weightedChildView, widthMeasureSpec, heightMeasureSpec);
+ }
+ } finally {
+ if (TRACE_ONMEASURE) {
+ trackShouldUseOptimizedLayout();
+ Trace.endSection();
+ }
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ if (mShouldUseOptimizedLayout) {
+ onLayoutOptimized(changed, l, t, r, b);
+ } else {
+ super.onLayout(changed, l, t, r, b);
+ }
+ }
+
+ private void onLayoutOptimized(boolean changed, int l, int t, int r, int b) {
+ if (getOrientation() == LinearLayout.HORIZONTAL) {
+ super.onLayout(changed, l, t, r, b);
+ } else {
+ layoutVerticalOptimized(l, t, r, b);
+ }
+ }
+
+ /**
+ * Optimized measurement for the single weighted child in this LinearLayout.
+ * Measures other children, calculates remaining space, then measures the weighted
+ * child using the exact remaining height.
+ *
+ * @param weightedChildView The weighted child view(with `layout_weight=1`
+ * @param widthMeasureSpec The width MeasureSpec to use for measurement
+ * @param heightMeasureSpec The height MeasureSpec to use for measurement.
+ */
+ private void measureVerticalOptimized(@NonNull View weightedChildView, int widthMeasureSpec,
+ int heightMeasureSpec) {
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int maxWidth = 0;
+ int usedHeight = 0;
+ final List<View> activeChildren = getActiveChildren();
+ final int activeChildCount = activeChildren.size();
+
+ final boolean isContentFirstItem = !activeChildren.isEmpty() && activeChildren.get(0)
+ == weightedChildView;
+
+ final boolean isContentLastItem = !activeChildren.isEmpty() && activeChildren.get(
+ activeChildCount - 1) == weightedChildView;
+
+ final int horizontalPaddings = getPaddingLeft() + getPaddingRight();
+
+ // 1. Measure other child views.
+ for (int i = 0; i < activeChildCount; i++) {
+ final View child = activeChildren.get(i);
+ if (child == weightedChildView) {
+ continue;
+ }
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+ int requiredVerticalPadding = lp.topMargin + lp.bottomMargin;
+ if (!isContentFirstItem && i == 0) {
+ requiredVerticalPadding += getPaddingTop();
+ }
+ if (!isContentLastItem && i == activeChildCount - 1) {
+ requiredVerticalPadding += getPaddingBottom();
+ }
+
+ child.measure(ViewGroup.getChildMeasureSpec(widthMeasureSpec,
+ horizontalPaddings + lp.leftMargin + lp.rightMargin,
+ child.getLayoutParams().width),
+ ViewGroup.getChildMeasureSpec(heightMeasureSpec, requiredVerticalPadding,
+ lp.height));
+ maxWidth = Math.max(maxWidth,
+ child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
+ usedHeight += child.getMeasuredHeight() + requiredVerticalPadding;
+ }
+
+ // measure content
+ final MarginLayoutParams lp = (MarginLayoutParams) weightedChildView.getLayoutParams();
+
+ int usedSpace = usedHeight + lp.topMargin + lp.bottomMargin;
+ if (isContentFirstItem) {
+ usedSpace += getPaddingTop();
+ }
+ if (isContentLastItem) {
+ usedSpace += getPaddingBottom();
+ }
+
+ final int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
+ final int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
+
+ final int childWidthMeasureSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
+ horizontalPaddings + lp.leftMargin + lp.rightMargin, lp.width);
+
+ // 2. Calculate remaining height for weightedChildView.
+ final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ Math.max(0, availableHeight - usedSpace), MeasureSpec.AT_MOST);
+
+ // 3. Measure weightedChildView with the remaining remaining space.
+ weightedChildView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ maxWidth = Math.max(maxWidth,
+ weightedChildView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
+
+ final int totalUsedHeight = usedSpace + weightedChildView.getMeasuredHeight();
+
+ final int measuredWidth;
+ if (widthMode == MeasureSpec.EXACTLY) {
+ measuredWidth = availableWidth;
+ } else {
+ measuredWidth = maxWidth + getPaddingStart() + getPaddingEnd();
+ }
+
+ final int measuredHeight;
+ if (heightMode == MeasureSpec.EXACTLY) {
+ measuredHeight = availableHeight;
+ } else {
+ measuredHeight = totalUsedHeight;
+ }
+
+ // 4. Set the container size
+ setMeasuredDimension(
+ resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
+ widthMeasureSpec),
+ Math.max(getSuggestedMinimumHeight(), measuredHeight));
+ }
+
+ @NonNull
+ private List<View> getActiveChildren() {
+ final int childCount = getChildCount();
+ final List<View> activeChildren = new ArrayList<>();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child == null || child.getVisibility() == View.GONE) {
+ continue;
+ }
+ activeChildren.add(child);
+ }
+ return activeChildren;
+ }
+
+ //region LinearLayout copy methods
+
+ /**
+ * layoutVerticalOptimized is a version of LinearLayout's layoutVertical method that
+ * excludes
+ * TableRow-related functionalities.
+ *
+ * @see LinearLayout#onLayout(boolean, int, int, int, int)
+ */
+ private void layoutVerticalOptimized(int left, int top, int right,
+ int bottom) {
+ final int paddingLeft = mPaddingLeft;
+ final int mTotalLength = getMeasuredHeight();
+ int childTop;
+ int childLeft;
+
+ // Where right end of child should go
+ final int width = right - left;
+ int childRight = width - mPaddingRight;
+
+ // Space available for child
+ int childSpace = width - paddingLeft - mPaddingRight;
+
+ final int count = getChildCount();
+
+ final int majorGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
+ final int minorGravity = getGravity() & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+
+ switch (majorGravity) {
+ case Gravity.BOTTOM:
+ // mTotalLength contains the padding already
+ childTop = mPaddingTop + bottom - top - mTotalLength;
+ break;
+
+ // mTotalLength contains the padding already
+ case Gravity.CENTER_VERTICAL:
+ childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
+ break;
+
+ case Gravity.TOP:
+ default:
+ childTop = mPaddingTop;
+ break;
+ }
+ final int dividerHeight = getDividerHeight();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child != null && child.getVisibility() != GONE) {
+ final int childWidth = child.getMeasuredWidth();
+ final int childHeight = child.getMeasuredHeight();
+
+ final LinearLayout.LayoutParams lp =
+ (LinearLayout.LayoutParams) child.getLayoutParams();
+
+ int gravity = lp.gravity;
+ if (gravity < 0) {
+ gravity = minorGravity;
+ }
+ final int layoutDirection = getLayoutDirection();
+ final int absoluteGravity = Gravity.getAbsoluteGravity(gravity,
+ layoutDirection);
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.CENTER_HORIZONTAL:
+ childLeft =
+ paddingLeft + ((childSpace - childWidth) / 2) + lp.leftMargin
+ - lp.rightMargin;
+ break;
+
+ case Gravity.RIGHT:
+ childLeft = childRight - childWidth - lp.rightMargin;
+ break;
+
+ case Gravity.LEFT:
+ default:
+ childLeft = paddingLeft + lp.leftMargin;
+ break;
+ }
+
+ if (hasDividerBeforeChildAt(i)) {
+ childTop += dividerHeight;
+ }
+
+ childTop += lp.topMargin;
+ child.layout(childLeft, childTop, childLeft + childWidth,
+ childTop + childHeight);
+ childTop += childHeight + lp.bottomMargin;
+
+ }
+ }
+ }
+
+ /**
+ * Used in laying out views vertically.
+ *
+ * @see #layoutVerticalOptimized
+ * @see LinearLayout#onLayout(boolean, int, int, int, int)
+ */
+ private int getDividerHeight() {
+ final Drawable dividerDrawable = getDividerDrawable();
+ if (dividerDrawable == null) {
+ return 0;
+ } else {
+ return dividerDrawable.getIntrinsicHeight();
+ }
+ }
+ //endregion
+
+ //region Logging&Tracing
+ private void trackShouldUseOptimizedLayout() {
+ if (TRACE_ONMEASURE) {
+ Trace.setCounter("NotifOptimizedLinearLayout#shouldUseOptimizedLayout",
+ mShouldUseOptimizedLayout ? 1 : 0);
+ }
+ }
+
+ private void logSkipOptimizedOnMeasure(String reason) {
+ if (DEBUG_LAYOUT) {
+ final StringBuilder logMessage = new StringBuilder();
+ int layoutId = getId();
+ if (layoutId != NO_ID) {
+ final Resources resources = getResources();
+ if (resources != null) {
+ logMessage.append("[");
+ logMessage.append(resources.getResourceName(layoutId));
+ logMessage.append("] ");
+ }
+ }
+ logMessage.append("Going to skip onMeasureOptimized reason:");
+ logMessage.append(reason);
+
+ Log.d(TAG, logMessage.toString());
+ }
+ }
+ //endregion
+}
diff --git a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
index 7b154a5..bddad94 100644
--- a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
+++ b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
@@ -30,7 +30,7 @@
* MessagingLayouts where groups need to be able to snap it's height to.
*/
@RemoteViews.RemoteView
-public class RemeasuringLinearLayout extends LinearLayout {
+public class RemeasuringLinearLayout extends NotificationOptimizedLinearLayout {
private ArrayList<View> mMatchParentViews = new ArrayList<>();
@@ -79,7 +79,7 @@
int exactHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
for (View child : mMatchParentViews) {
child.measure(getChildMeasureSpec(
- widthMeasureSpec, getPaddingStart() + getPaddingEnd(),
+ widthMeasureSpec, getPaddingStart() + getPaddingEnd(),
child.getLayoutParams().width),
exactHeightSpec);
}
@@ -87,4 +87,5 @@
mMatchParentViews.clear();
setMeasuredDimension(getMeasuredWidth(), height);
}
+
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
new file mode 100644
index 0000000..ce8ca0d
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core;
+
+import java.util.List;
+
+/**
+ * Interface for the companion operations
+ */
+public interface CompanionOperation {
+ void read(WireBuffer buffer, List<Operation> operations);
+
+ // Debugging / Documentation utility functions
+ String name();
+ int id();
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
new file mode 100644
index 0000000..0e4c743
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -0,0 +1,494 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core;
+
+import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
+import com.android.internal.widget.remotecompose.core.operations.Theme;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Represents a platform independent RemoteCompose document,
+ * containing RemoteCompose operations + state
+ */
+public class CoreDocument {
+
+ ArrayList<Operation> mOperations;
+ RemoteComposeState mRemoteComposeState = new RemoteComposeState();
+
+ // Semantic version of the document
+ Version mVersion = new Version(0, 1, 0);
+
+ String mContentDescription; // text description of the document (used for accessibility)
+
+ long mRequiredCapabilities = 0L; // bitmask indicating needed capabilities of the player(unused)
+ int mWidth = 0; // horizontal dimension of the document in pixels
+ int mHeight = 0; // vertical dimension of the document in pixels
+
+ int mContentScroll = RootContentBehavior.NONE;
+ int mContentSizing = RootContentBehavior.NONE;
+ int mContentMode = RootContentBehavior.NONE;
+
+ int mContentAlignment = RootContentBehavior.ALIGNMENT_CENTER;
+
+ RemoteComposeBuffer mBuffer = new RemoteComposeBuffer(mRemoteComposeState);
+
+ public String getContentDescription() {
+ return mContentDescription;
+ }
+
+ public void setContentDescription(String contentDescription) {
+ this.mContentDescription = contentDescription;
+ }
+
+ public long getRequiredCapabilities() {
+ return mRequiredCapabilities;
+ }
+
+ public void setRequiredCapabilities(long requiredCapabilities) {
+ this.mRequiredCapabilities = requiredCapabilities;
+ }
+
+ public int getWidth() {
+ return mWidth;
+ }
+
+ public void setWidth(int width) {
+ this.mWidth = width;
+ }
+
+ public int getHeight() {
+ return mHeight;
+ }
+
+ public void setHeight(int height) {
+ this.mHeight = height;
+ }
+
+ public RemoteComposeBuffer getBuffer() {
+ return mBuffer;
+ }
+
+ public void setBuffer(RemoteComposeBuffer buffer) {
+ this.mBuffer = buffer;
+ }
+
+ public RemoteComposeState getRemoteComposeState() {
+ return mRemoteComposeState;
+ }
+
+ public void setRemoteComposeState(RemoteComposeState remoteComposeState) {
+ this.mRemoteComposeState = remoteComposeState;
+ }
+
+ public int getContentScroll() {
+ return mContentScroll;
+ }
+
+ public int getContentSizing() {
+ return mContentSizing;
+ }
+
+ public int getContentMode() {
+ return mContentMode;
+ }
+
+ /**
+ * Sets the way the player handles the content
+ *
+ * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
+ * @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END)
+ * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
+ * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
+ * the LAYOUT modes are:
+ * - LAYOUT_MATCH_PARENT
+ * - LAYOUT_WRAP_CONTENT
+ * or adding an horizontal mode and a vertical mode:
+ * - LAYOUT_HORIZONTAL_MATCH_PARENT
+ * - LAYOUT_HORIZONTAL_WRAP_CONTENT
+ * - LAYOUT_HORIZONTAL_FIXED
+ * - LAYOUT_VERTICAL_MATCH_PARENT
+ * - LAYOUT_VERTICAL_WRAP_CONTENT
+ * - LAYOUT_VERTICAL_FIXED
+ * The LAYOUT_*_FIXED modes will use the intrinsic document size
+ */
+ public void setRootContentBehavior(int scroll, int alignment, int sizing, int mode) {
+ this.mContentScroll = scroll;
+ this.mContentAlignment = alignment;
+ this.mContentSizing = sizing;
+ this.mContentMode = mode;
+ }
+
+ /**
+ * Given dimensions w x h of where to paint the content, returns the corresponding scale factor
+ * according to the contentSizing information
+ *
+ * @param w horizontal dimension of the rendering area
+ * @param h vertical dimension of the rendering area
+ * @param scaleOutput will contain the computed scale factor
+ */
+ public void computeScale(float w, float h, float[] scaleOutput) {
+ float contentScaleX = 1f;
+ float contentScaleY = 1f;
+ if (mContentSizing == RootContentBehavior.SIZING_SCALE) {
+ // we need to add canvas transforms ops here
+ switch (mContentMode) {
+ case RootContentBehavior.SCALE_INSIDE: {
+ float scaleX = w / mWidth;
+ float scaleY = h / mHeight;
+ float scale = Math.min(1f, Math.min(scaleX, scaleY));
+ contentScaleX = scale;
+ contentScaleY = scale;
+ } break;
+ case RootContentBehavior.SCALE_FIT: {
+ float scaleX = w / mWidth;
+ float scaleY = h / mHeight;
+ float scale = Math.min(scaleX, scaleY);
+ contentScaleX = scale;
+ contentScaleY = scale;
+ } break;
+ case RootContentBehavior.SCALE_FILL_WIDTH: {
+ float scale = w / mWidth;
+ contentScaleX = scale;
+ contentScaleY = scale;
+ } break;
+ case RootContentBehavior.SCALE_FILL_HEIGHT: {
+ float scale = h / mHeight;
+ contentScaleX = scale;
+ contentScaleY = scale;
+ } break;
+ case RootContentBehavior.SCALE_CROP: {
+ float scaleX = w / mWidth;
+ float scaleY = h / mHeight;
+ float scale = Math.max(scaleX, scaleY);
+ contentScaleX = scale;
+ contentScaleY = scale;
+ } break;
+ case RootContentBehavior.SCALE_FILL_BOUNDS: {
+ float scaleX = w / mWidth;
+ float scaleY = h / mHeight;
+ contentScaleX = scaleX;
+ contentScaleY = scaleY;
+ } break;
+ default:
+ // nothing
+ }
+ }
+ scaleOutput[0] = contentScaleX;
+ scaleOutput[1] = contentScaleY;
+ }
+
+ /**
+ * Given dimensions w x h of where to paint the content, returns the corresponding translation
+ * according to the contentAlignment information
+ *
+ * @param w horizontal dimension of the rendering area
+ * @param h vertical dimension of the rendering area
+ * @param contentScaleX the horizontal scale we are going to use for the content
+ * @param contentScaleY the vertical scale we are going to use for the content
+ * @param translateOutput will contain the computed translation
+ */
+ private void computeTranslate(float w, float h, float contentScaleX, float contentScaleY,
+ float[] translateOutput) {
+ int horizontalContentAlignment = mContentAlignment & 0xF0;
+ int verticalContentAlignment = mContentAlignment & 0xF;
+ float translateX = 0f;
+ float translateY = 0f;
+ float contentWidth = mWidth * contentScaleX;
+ float contentHeight = mHeight * contentScaleY;
+
+ switch (horizontalContentAlignment) {
+ case RootContentBehavior.ALIGNMENT_START: {
+ // nothing
+ } break;
+ case RootContentBehavior.ALIGNMENT_HORIZONTAL_CENTER: {
+ translateX = (w - contentWidth) / 2f;
+ } break;
+ case RootContentBehavior.ALIGNMENT_END: {
+ translateX = w - contentWidth;
+ } break;
+ default:
+ // nothing (same as alignment_start)
+ }
+ switch (verticalContentAlignment) {
+ case RootContentBehavior.ALIGNMENT_TOP: {
+ // nothing
+ } break;
+ case RootContentBehavior.ALIGNMENT_VERTICAL_CENTER: {
+ translateY = (h - contentHeight) / 2f;
+ } break;
+ case RootContentBehavior.ALIGNMENT_BOTTOM: {
+ translateY = h - contentHeight;
+ } break;
+ default:
+ // nothing (same as alignment_top)
+ }
+
+ translateOutput[0] = translateX;
+ translateOutput[1] = translateY;
+ }
+
+ public Set<ClickAreaRepresentation> getClickAreas() {
+ return mClickAreas;
+ }
+
+ public interface ClickCallbacks {
+ void click(int id, String metadata);
+ }
+
+ HashSet<ClickCallbacks> mClickListeners = new HashSet<>();
+ HashSet<ClickAreaRepresentation> mClickAreas = new HashSet<>();
+
+ static class Version {
+ public final int major;
+ public final int minor;
+ public final int patchLevel;
+
+ Version(int major, int minor, int patchLevel) {
+ this.major = major;
+ this.minor = minor;
+ this.patchLevel = patchLevel;
+ }
+ }
+
+ public static class ClickAreaRepresentation {
+ int mId;
+ String mContentDescription;
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+ String mMetadata;
+
+ public ClickAreaRepresentation(int id,
+ String contentDescription,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ String metadata) {
+ this.mId = id;
+ this.mContentDescription = contentDescription;
+ this.mLeft = left;
+ this.mTop = top;
+ this.mRight = right;
+ this.mBottom = bottom;
+ this.mMetadata = metadata;
+ }
+
+ public boolean contains(float x, float y) {
+ return x >= mLeft && x < mRight
+ && y >= mTop && y < mBottom;
+ }
+
+ public float getLeft() {
+ return mLeft;
+ }
+
+ public float getTop() {
+ return mTop;
+ }
+
+ public float width() {
+ return Math.max(0, mRight - mLeft);
+ }
+
+ public float height() {
+ return Math.max(0, mBottom - mTop);
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public String getContentDescription() {
+ return mContentDescription;
+ }
+
+ public String getMetadata() {
+ return mMetadata;
+ }
+ }
+
+ /**
+ * Load operations from the given buffer
+ */
+ public void initFromBuffer(RemoteComposeBuffer buffer) {
+ mOperations = new ArrayList<Operation>();
+ buffer.inflateFromBuffer(mOperations);
+ }
+
+ /**
+ * Called when an initialization is needed, allowing the document to eg load
+ * resources / cache them.
+ */
+ public void initializeContext(RemoteContext context) {
+ mRemoteComposeState.reset();
+ mClickAreas.clear();
+
+ context.mDocument = this;
+ context.mRemoteComposeState = mRemoteComposeState;
+
+ // mark context to be in DATA mode, which will skip the painting ops.
+ context.mMode = RemoteContext.ContextMode.DATA;
+ for (Operation op: mOperations) {
+ op.apply(context);
+ }
+ context.mMode = RemoteContext.ContextMode.UNSET;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Document infos
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Returns true if the document can be displayed given this version of the player
+ *
+ * @param majorVersion the max major version supported by the player
+ * @param minorVersion the max minor version supported by the player
+ * @param capabilities a bitmask of capabilities the player supports (unused for now)
+ */
+ public boolean canBeDisplayed(int majorVersion, int minorVersion, long capabilities) {
+ return mVersion.major <= majorVersion && mVersion.minor <= minorVersion;
+ }
+
+ /**
+ * Set the document version, following semantic versioning.
+ *
+ * @param majorVersion major version number, increased upon changes breaking the compatibility
+ * @param minorVersion minor version number, increased when adding new features
+ * @param patch patch level, increased upon bugfixes
+ */
+ void setVersion(int majorVersion, int minorVersion, int patch) {
+ mVersion = new Version(majorVersion, minorVersion, patch);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Click handling
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Add a click area to the document, in root coordinates. We are not doing any specific sorting
+ * through the declared areas on click detections, which means that the first one containing
+ * the click coordinates will be the one reported; the order of addition of those click areas
+ * is therefore meaningful.
+ *
+ * @param id the id of the area, which will be reported on click
+ * @param contentDescription the content description (used for accessibility)
+ * @param left the left coordinate of the click area (in pixels)
+ * @param top the top coordinate of the click area (in pixels)
+ * @param right the right coordinate of the click area (in pixels)
+ * @param bottom the bottom coordinate of the click area (in pixels)
+ * @param metadata arbitrary metadata associated with the are, also reported on click
+ */
+ public void addClickArea(int id, String contentDescription,
+ float left, float top, float right, float bottom, String metadata) {
+ mClickAreas.add(new ClickAreaRepresentation(id,
+ contentDescription, left, top, right, bottom, metadata));
+ }
+
+ /**
+ * Add a click listener. This will get called when a click is detected on the document
+ *
+ * @param callback called when a click area has been hit, passing the click are id and metadata.
+ */
+ public void addClickListener(ClickCallbacks callback) {
+ mClickListeners.add(callback);
+ }
+
+ /**
+ * Passing a click event to the document. This will possibly result in calling the click
+ * listeners.
+ */
+ public void onClick(float x, float y) {
+ for (ClickAreaRepresentation clickArea: mClickAreas) {
+ if (clickArea.contains(x, y)) {
+ warnClickListeners(clickArea);
+ }
+ }
+ }
+
+ /**
+ * Programmatically trigger the click response for the given id
+ *
+ * @param id the click area id
+ */
+ public void performClick(int id) {
+ for (ClickAreaRepresentation clickArea: mClickAreas) {
+ if (clickArea.mId == id) {
+ warnClickListeners(clickArea);
+ }
+ }
+ }
+
+ /**
+ * Warn click listeners when a click area is activated
+ */
+ private void warnClickListeners(ClickAreaRepresentation clickArea) {
+ for (ClickCallbacks listener: mClickListeners) {
+ listener.click(clickArea.mId, clickArea.mMetadata);
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Painting
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ private final float[] mScaleOutput = new float[2];
+ private final float[] mTranslateOutput = new float[2];
+
+ /**
+ * Paint the document
+ *
+ * @param context the provided PaintContext
+ * @param theme the theme we want to use for this document.
+ */
+ public void paint(RemoteContext context, int theme) {
+ context.mMode = RemoteContext.ContextMode.PAINT;
+
+ // current theme starts as UNSPECIFIED, until a Theme setter
+ // operation gets executed and modify it.
+ context.setTheme(Theme.UNSPECIFIED);
+
+ context.mRemoteComposeState = mRemoteComposeState;
+ if (mContentSizing == RootContentBehavior.SIZING_SCALE) {
+ // we need to add canvas transforms ops here
+ computeScale(context.mWidth, context.mHeight, mScaleOutput);
+ computeTranslate(context.mWidth, context.mHeight,
+ mScaleOutput[0], mScaleOutput[1], mTranslateOutput);
+ context.mPaintContext.translate(mTranslateOutput[0], mTranslateOutput[1]);
+ context.mPaintContext.scale(mScaleOutput[0], mScaleOutput[1]);
+ }
+ for (Operation op : mOperations) {
+ // operations will only be executed if no theme is set (ie UNSPECIFIED)
+ // or the theme is equal as the one passed in argument to paint.
+ boolean apply = true;
+ if (theme != Theme.UNSPECIFIED) {
+ apply = op instanceof Theme // always apply a theme setter
+ || context.getTheme() == theme
+ || context.getTheme() == Theme.UNSPECIFIED;
+ }
+ if (apply) {
+ op.apply(context);
+ }
+ }
+ context.mMode = RemoteContext.ContextMode.UNSET;
+ }
+
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operation.java b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
new file mode 100644
index 0000000..7cb9a42
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core;
+
+/**
+ * Base interface for RemoteCompose operations
+ */
+public interface Operation {
+
+ /**
+ * add the operation to the buffer
+ */
+ void write(WireBuffer buffer);
+
+ /**
+ * paint an operation
+ *
+ * @param context the paint context used to paint the operation
+ */
+ void apply(RemoteContext context);
+
+ /**
+ * Debug utility to display an operation + indentation
+ */
+ String deepToString(String indent);
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
new file mode 100644
index 0000000..b8bb1f0
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core;
+
+import com.android.internal.widget.remotecompose.core.operations.BitmapData;
+import com.android.internal.widget.remotecompose.core.operations.ClickArea;
+import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt;
+import com.android.internal.widget.remotecompose.core.operations.Header;
+import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
+import com.android.internal.widget.remotecompose.core.operations.RootContentDescription;
+import com.android.internal.widget.remotecompose.core.operations.TextData;
+import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
+
+/**
+ * List of operations supported in a RemoteCompose document
+ */
+public class Operations {
+
+ ////////////////////////////////////////
+ // Protocol
+ ////////////////////////////////////////
+ public static final int HEADER = 0;
+ public static final int LOAD_BITMAP = 4;
+ public static final int THEME = 63;
+ public static final int CLICK_AREA = 64;
+ public static final int ROOT_CONTENT_BEHAVIOR = 65;
+ public static final int ROOT_CONTENT_DESCRIPTION = 103;
+
+ ////////////////////////////////////////
+ // Draw commands
+ ////////////////////////////////////////
+ public static final int DRAW_BITMAP = 44;
+ public static final int DRAW_BITMAP_INT = 66;
+ public static final int DATA_BITMAP = 101;
+ public static final int DATA_TEXT = 102;
+
+
+ public static IntMap<CompanionOperation> map = new IntMap<>();
+
+ static {
+ map.put(HEADER, Header.COMPANION);
+ map.put(DRAW_BITMAP_INT, DrawBitmapInt.COMPANION);
+ map.put(DATA_BITMAP, BitmapData.COMPANION);
+ map.put(DATA_TEXT, TextData.COMPANION);
+ map.put(THEME, Theme.COMPANION);
+ map.put(CLICK_AREA, ClickArea.COMPANION);
+ map.put(ROOT_CONTENT_BEHAVIOR, RootContentBehavior.COMPANION);
+ map.put(ROOT_CONTENT_DESCRIPTION, RootContentDescription.COMPANION);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
new file mode 100644
index 0000000..6999cde
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core;
+
+/**
+ * Specify an abstract paint context used by RemoteCompose commands to draw
+ */
+public abstract class PaintContext {
+ protected RemoteContext mContext;
+
+ public PaintContext(RemoteContext context) {
+ this.mContext = context;
+ }
+
+ public void setContext(RemoteContext context) {
+ this.mContext = context;
+ }
+
+ public abstract void drawBitmap(int imageId,
+ int srcLeft, int srcTop, int srcRight, int srcBottom,
+ int dstLeft, int dstTop, int dstRight, int dstBottom,
+ int cdId);
+
+ public abstract void scale(float scaleX, float scaleY);
+ public abstract void translate(float translateX, float translateY);
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
new file mode 100644
index 0000000..2f3fe57
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core;
+
+/**
+ * PaintOperation interface, used for operations aimed at painting
+ * (while any operation _can_ paint, this make it a little more explicit)
+ */
+public abstract class PaintOperation implements Operation {
+
+ @Override
+ public void apply(RemoteContext context) {
+ if (context.getMode() == RemoteContext.ContextMode.PAINT
+ && context.getPaintContext() != null) {
+ paint((PaintContext) context.getPaintContext());
+ }
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return indent + toString();
+ }
+
+ public abstract void paint(PaintContext context);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Platform.java b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
new file mode 100644
index 0000000..abda0c0
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core;
+
+/**
+ * Services that are needed to be provided by the platform during encoding.
+ */
+public interface Platform {
+ byte[] imageToByteArray(Object image);
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
new file mode 100644
index 0000000..3ab6c47
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core;
+
+import com.android.internal.widget.remotecompose.core.operations.BitmapData;
+import com.android.internal.widget.remotecompose.core.operations.ClickArea;
+import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt;
+import com.android.internal.widget.remotecompose.core.operations.Header;
+import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
+import com.android.internal.widget.remotecompose.core.operations.RootContentDescription;
+import com.android.internal.widget.remotecompose.core.operations.TextData;
+import com.android.internal.widget.remotecompose.core.operations.Theme;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Provides an abstract buffer to encode/decode RemoteCompose operations
+ */
+public class RemoteComposeBuffer {
+ WireBuffer mBuffer = new WireBuffer();
+ Platform mPlatform = null;
+ RemoteComposeState mRemoteComposeState;
+
+ /**
+ * Provides an abstract buffer to encode/decode RemoteCompose operations
+ *
+ * @param remoteComposeState the state used while encoding on the buffer
+ */
+ public RemoteComposeBuffer(RemoteComposeState remoteComposeState) {
+ this.mRemoteComposeState = remoteComposeState;
+ }
+
+ public void reset() {
+ mBuffer.reset();
+ mRemoteComposeState.reset();
+ }
+
+ public Platform getPlatform() {
+ return mPlatform;
+ }
+
+ public void setPlatform(Platform platform) {
+ this.mPlatform = platform;
+ }
+
+ public WireBuffer getBuffer() {
+ return mBuffer;
+ }
+
+ public void setBuffer(WireBuffer buffer) {
+ this.mBuffer = buffer;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Supported operations on the buffer
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Insert a header
+ *
+ * @param width the width of the document in pixels
+ * @param height the height of the document in pixels
+ * @param contentDescription content description of the document
+ * @param capabilities bitmask indicating needed capabilities (unused for now)
+ */
+ public void header(int width, int height, String contentDescription, long capabilities) {
+ Header.COMPANION.apply(mBuffer, width, height, capabilities);
+ int contentDescriptionId = 0;
+ if (contentDescription != null) {
+ contentDescriptionId = addText(contentDescription);
+ RootContentDescription.COMPANION.apply(mBuffer, contentDescriptionId);
+ }
+ }
+
+ /**
+ * Insert a header
+ *
+ * @param width the width of the document in pixels
+ * @param height the height of the document in pixels
+ * @param contentDescription content description of the document
+ */
+ public void header(int width, int height, String contentDescription) {
+ header(width, height, contentDescription, 0);
+ }
+
+ /**
+ * Insert a bitmap
+ *
+ * @param image an opaque image that we'll add to the buffer
+ * @param imageWidth the width of the image
+ * @param imageHeight the height of the image
+ * @param srcLeft left coordinate of the source area
+ * @param srcTop top coordinate of the source area
+ * @param srcRight right coordinate of the source area
+ * @param srcBottom bottom coordinate of the source area
+ * @param dstLeft left coordinate of the destination area
+ * @param dstTop top coordinate of the destination area
+ * @param dstRight right coordinate of the destination area
+ * @param dstBottom bottom coordinate of the destination area
+ */
+ public void drawBitmap(Object image,
+ int imageWidth, int imageHeight,
+ int srcLeft, int srcTop, int srcRight, int srcBottom,
+ int dstLeft, int dstTop, int dstRight, int dstBottom,
+ String contentDescription) {
+ int imageId = mRemoteComposeState.dataGetId(image);
+ if (imageId == -1) {
+ imageId = mRemoteComposeState.cache(image);
+ byte[] data = mPlatform.imageToByteArray(image);
+ BitmapData.COMPANION.apply(mBuffer, imageId, imageWidth, imageHeight, data);
+ }
+ int contentDescriptionId = 0;
+ if (contentDescription != null) {
+ contentDescriptionId = addText(contentDescription);
+ }
+ DrawBitmapInt.COMPANION.apply(
+ mBuffer, imageId, srcLeft, srcTop, srcRight, srcBottom,
+ dstLeft, dstTop, dstRight, dstBottom, contentDescriptionId
+ );
+ }
+
+ /**
+ * Adds a text string data to the stream and returns its id
+ * Will be used to insert string with bitmaps etc.
+ *
+ * @param text the string to inject in the buffer
+ */
+ int addText(String text) {
+ int id = mRemoteComposeState.dataGetId(text);
+ if (id == -1) {
+ id = mRemoteComposeState.cache(text);
+ TextData.COMPANION.apply(mBuffer, id, text);
+ }
+ return id;
+ }
+
+ /**
+ * Add a click area to the document
+ *
+ * @param id the id of the click area, reported in the click listener callback
+ * @param contentDescription the content description of that click area (accessibility)
+ * @param left left coordinate of the area bounds
+ * @param top top coordinate of the area bounds
+ * @param right right coordinate of the area bounds
+ * @param bottom bottom coordinate of the area bounds
+ * @param metadata associated metadata, user-provided
+ */
+ public void addClickArea(
+ int id,
+ String contentDescription,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ String metadata
+ ) {
+ int contentDescriptionId = 0;
+ if (contentDescription != null) {
+ contentDescriptionId = addText(contentDescription);
+ }
+ int metadataId = 0;
+ if (metadata != null) {
+ metadataId = addText(metadata);
+ }
+ ClickArea.COMPANION.apply(mBuffer, id, contentDescriptionId,
+ left, top, right, bottom, metadataId);
+ }
+
+ /**
+ * Sets the way the player handles the content
+ *
+ * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
+ * @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END)
+ * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
+ * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
+ * the LAYOUT modes are:
+ * - LAYOUT_MATCH_PARENT
+ * - LAYOUT_WRAP_CONTENT
+ * or adding an horizontal mode and a vertical mode:
+ * - LAYOUT_HORIZONTAL_MATCH_PARENT
+ * - LAYOUT_HORIZONTAL_WRAP_CONTENT
+ * - LAYOUT_HORIZONTAL_FIXED
+ * - LAYOUT_VERTICAL_MATCH_PARENT
+ * - LAYOUT_VERTICAL_WRAP_CONTENT
+ * - LAYOUT_VERTICAL_FIXED
+ * The LAYOUT_*_FIXED modes will use the intrinsic document size
+ */
+ public void setRootContentBehavior(int scroll, int alignment, int sizing, int mode) {
+ RootContentBehavior.COMPANION.apply(mBuffer, scroll, alignment, sizing, mode);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ public void inflateFromBuffer(ArrayList<Operation> operations) {
+ mBuffer.setIndex(0);
+ while (mBuffer.available()) {
+ int opId = mBuffer.readByte();
+ CompanionOperation operation = Operations.map.get(opId);
+ if (operation == null) {
+ throw new RuntimeException("Unknown operation encountered");
+ }
+ operation.read(mBuffer, operations);
+ }
+ }
+
+ RemoteComposeBuffer copy() {
+ ArrayList<Operation> operations = new ArrayList<>();
+ inflateFromBuffer(operations);
+ RemoteComposeBuffer buffer = new RemoteComposeBuffer(mRemoteComposeState);
+ return copyFromOperations(operations, buffer);
+ }
+
+ public void setTheme(int theme) {
+ Theme.COMPANION.apply(mBuffer, theme);
+ }
+
+
+ static String version() {
+ return "v1.0";
+ }
+
+ public static RemoteComposeBuffer fromFile(String path,
+ RemoteComposeState remoteComposeState)
+ throws IOException {
+ RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
+ read(new File(path), buffer);
+ return buffer;
+ }
+
+ public RemoteComposeBuffer fromFile(File file,
+ RemoteComposeState remoteComposeState) throws IOException {
+ RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
+ read(file, buffer);
+ return buffer;
+ }
+
+ public static RemoteComposeBuffer fromInputStream(InputStream inputStream,
+ RemoteComposeState remoteComposeState) {
+ RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
+ read(inputStream, buffer);
+ return buffer;
+ }
+
+ RemoteComposeBuffer copyFromOperations(ArrayList<Operation> operations,
+ RemoteComposeBuffer buffer) {
+
+ for (Operation operation : operations) {
+ operation.write(buffer.mBuffer);
+ }
+ return buffer;
+ }
+
+ public void write(RemoteComposeBuffer buffer, File file) {
+ try {
+ FileOutputStream fd = new FileOutputStream(file);
+ fd.write(buffer.mBuffer.getBuffer(), 0, buffer.mBuffer.getSize());
+ fd.flush();
+ fd.close();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ static void read(File file, RemoteComposeBuffer buffer) throws IOException {
+ FileInputStream fd = new FileInputStream(file);
+ read(fd, buffer);
+ }
+
+ public static void read(InputStream fd, RemoteComposeBuffer buffer) {
+ try {
+ byte[] bytes = readAllBytes(fd);
+ buffer.reset();
+ System.arraycopy(bytes, 0, buffer.mBuffer.mBuffer, 0, bytes.length);
+ buffer.mBuffer.mSize = bytes.length;
+ } catch (Exception e) {
+ e.printStackTrace();
+ // todo decide how to handel this stuff
+ }
+ }
+
+ private static byte[] readAllBytes(InputStream is) throws IOException {
+ byte[] buff = new byte[32 * 1024]; // moderate size buff to start
+ int red = 0;
+ while (true) {
+ int ret = is.read(buff, red, buff.length - red);
+ if (ret == -1) {
+ is.close();
+ return Arrays.copyOf(buff, red);
+ }
+ red += ret;
+ if (red == buff.length) {
+ buff = Arrays.copyOf(buff, buff.length * 2);
+ }
+ }
+ }
+
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeOperation.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeOperation.java
new file mode 100644
index 0000000..c7ec3359
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeOperation.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core;
+
+public interface RemoteComposeOperation extends Operation {
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
new file mode 100644
index 0000000..17e8c83
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core;
+
+import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
+
+import java.util.HashMap;
+
+/**
+ * Represents runtime state for a RemoteCompose document
+ */
+public class RemoteComposeState {
+
+ private final IntMap<Object> mIntDataMap = new IntMap<>();
+ private final IntMap<Boolean> mIntWrittenMap = new IntMap<>();
+ private final HashMap<Object, Integer> mDataIntMap = new HashMap();
+
+ private static int sNextId = 42;
+
+ public Object getFromId(int id) {
+ return mIntDataMap.get(id);
+ }
+
+ public boolean containsId(int id) {
+ return mIntDataMap.get(id) != null;
+ }
+
+ /**
+ * Return the id of an item from the cache.
+ */
+ public int dataGetId(Object image) {
+ Integer res = mDataIntMap.get(image);
+ if (res == null) {
+ return -1;
+ }
+ return res;
+ }
+
+ /**
+ * Add an image to the cache. Generates an id for the image and adds it to the cache based on
+ * that id.
+ */
+ public int cache(Object image) {
+ int id = nextId();
+ mDataIntMap.put(image, id);
+ mIntDataMap.put(id, image);
+ return id;
+ }
+
+ /**
+ * Insert an item in the cache
+ */
+ public void cache(int id, Object item) {
+ mDataIntMap.put(item, id);
+ mIntDataMap.put(id, item);
+ }
+
+ /**
+ * Method to determine if a cached value has been written to the documents WireBuffer based on
+ * its id.
+ */
+ public boolean wasNotWritten(int id) {
+ return !mIntWrittenMap.get(id);
+ }
+
+ /**
+ * Method to mark that a value, represented by its id, has been written to the WireBuffer
+ */
+ public void markWritten(int id) {
+ mIntWrittenMap.put(id, true);
+ }
+
+ /**
+ * Clear the record of the values that have been written to the WireBuffer.
+ */
+ void reset() {
+ mIntWrittenMap.clear();
+ }
+
+ public static int nextId() {
+ return sNextId++;
+ }
+ public static void setNextId(int id) {
+ sNextId = id;
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
new file mode 100644
index 0000000..1b7c6fd
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core;
+
+import com.android.internal.widget.remotecompose.core.operations.Theme;
+
+/**
+ * Specify an abstract context used to playback RemoteCompose documents
+ *
+ * This allows us to intercept the different operations in a document and react to them.
+ *
+ * We also contain a PaintContext, so that any operation can draw as needed.
+ */
+public abstract class RemoteContext {
+ protected CoreDocument mDocument;
+ public RemoteComposeState mRemoteComposeState;
+
+ protected PaintContext mPaintContext = null;
+ ContextMode mMode = ContextMode.UNSET;
+
+ boolean mDebug = false;
+ private int mTheme = Theme.UNSPECIFIED;
+
+ public float mWidth = 0f;
+ public float mHeight = 0f;
+
+ /**
+ * The context can be used in a few different mode, allowing operations to skip being executed:
+ * - UNSET : all operations will get executed
+ * - DATA : only operations dealing with DATA (eg loading a bitmap) should execute
+ * - PAINT : only operations painting should execute
+ */
+ public enum ContextMode {
+ UNSET, DATA, PAINT
+ }
+
+ public int getTheme() {
+ return mTheme;
+ }
+
+ public void setTheme(int theme) {
+ this.mTheme = theme;
+ }
+
+ public ContextMode getMode() {
+ return mMode;
+ }
+
+ public void setMode(ContextMode mode) {
+ this.mMode = mode;
+ }
+
+ public PaintContext getPaintContext() {
+ return mPaintContext;
+ }
+
+ public void setPaintContext(PaintContext paintContext) {
+ this.mPaintContext = paintContext;
+ }
+
+ public CoreDocument getDocument() {
+ return mDocument;
+ }
+
+ public boolean isDebug() {
+ return mDebug;
+ }
+
+ public void setDebug(boolean debug) {
+ this.mDebug = debug;
+ }
+
+ public void setDocument(CoreDocument document) {
+ this.mDocument = document;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Operations
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ public void header(int majorVersion, int minorVersion, int patchVersion,
+ int width, int height, long capabilities
+ ) {
+ mDocument.setVersion(majorVersion, minorVersion, patchVersion);
+ mDocument.setWidth(width);
+ mDocument.setHeight(height);
+ mDocument.setRequiredCapabilities(capabilities);
+ }
+
+ /**
+ * Sets the way the player handles the content
+ *
+ * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
+ * @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END)
+ * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
+ * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
+ * the LAYOUT modes are:
+ * - LAYOUT_MATCH_PARENT
+ * - LAYOUT_WRAP_CONTENT
+ * or adding an horizontal mode and a vertical mode:
+ * - LAYOUT_HORIZONTAL_MATCH_PARENT
+ * - LAYOUT_HORIZONTAL_WRAP_CONTENT
+ * - LAYOUT_HORIZONTAL_FIXED
+ * - LAYOUT_VERTICAL_MATCH_PARENT
+ * - LAYOUT_VERTICAL_WRAP_CONTENT
+ * - LAYOUT_VERTICAL_FIXED
+ * The LAYOUT_*_FIXED modes will use the intrinsic document size
+ */
+ public void setRootContentBehavior(int scroll, int alignment, int sizing, int mode) {
+ mDocument.setRootContentBehavior(scroll, alignment, sizing, mode);
+ }
+
+ /**
+ * Set a content description for the document
+ * @param contentDescriptionId the text id pointing at the description
+ */
+ public void setDocumentContentDescription(int contentDescriptionId) {
+ String contentDescription = (String) mRemoteComposeState.getFromId(contentDescriptionId);
+ mDocument.setContentDescription(contentDescription);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Data handling
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ public abstract void loadBitmap(int imageId, int width, int height, byte[] bitmap);
+ public abstract void loadText(int id, String text);
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Click handling
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ public abstract void addClickArea(
+ int id,
+ int contentDescription,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ int metadataId
+ );
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
new file mode 100644
index 0000000..3e701c1
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core;
+
+import java.util.Arrays;
+
+/**
+ * The base communication buffer capable of encoding and decoding various types
+ */
+public class WireBuffer {
+ private static final int BUFFER_SIZE = 1024 * 1024 * 1;
+ int mMaxSize;
+ byte[] mBuffer;
+ int mIndex = 0;
+ int mStartingIndex = 0;
+ int mSize = 0;
+
+ public WireBuffer(int size) {
+ mMaxSize = size;
+ mBuffer = new byte[mMaxSize];
+ }
+
+ public WireBuffer() {
+ this(BUFFER_SIZE);
+ }
+
+ private void resize(int need) {
+ if (mSize + need >= mMaxSize) {
+ mMaxSize = Math.max(mMaxSize * 2, mSize + need);
+ mBuffer = Arrays.copyOf(mBuffer, mMaxSize);
+ }
+ }
+
+ public byte[] getBuffer() {
+ return mBuffer;
+ }
+
+ public int getMax_size() {
+ return mMaxSize;
+ }
+
+ public int getIndex() {
+ return mIndex;
+ }
+
+ public int getSize() {
+ return mSize;
+ }
+
+ public void setIndex(int index) {
+ this.mIndex = index;
+ }
+
+ public void start(int type) {
+ mStartingIndex = mIndex;
+ writeByte(type);
+ }
+
+ public void startWithSize(int type) {
+ mStartingIndex = mIndex;
+ writeByte(type);
+ mIndex += 4; // skip ahead for the future size
+ }
+
+ public void endWithSize() {
+ int size = mIndex - mStartingIndex;
+ int currentIndex = mIndex;
+ mIndex = mStartingIndex + 1; // (type)
+ writeInt(size);
+ mIndex = currentIndex;
+ }
+
+ public void reset() {
+ mIndex = 0;
+ mStartingIndex = 0;
+ mSize = 0;
+ }
+
+ public int size() {
+ return mSize;
+ }
+
+ public boolean available() {
+ return mSize - mIndex > 0;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Read values
+ ///////////////////////////////////////////////////////////////////////////
+
+ public int readOperationType() {
+ return readByte();
+ }
+
+ public boolean readBoolean() {
+ byte value = mBuffer[mIndex];
+ mIndex++;
+ return (value == 1);
+ }
+
+ public int readByte() {
+ byte value = mBuffer[mIndex];
+ mIndex++;
+ return value;
+ }
+
+ public int readShort() {
+ int v1 = (mBuffer[mIndex++] & 0xFF) << 8;
+ int v2 = (mBuffer[mIndex++] & 0xFF) << 0;
+ return v1 + v2;
+ }
+
+ public int readInt() {
+ int v1 = (mBuffer[mIndex++] & 0xFF) << 24;
+ int v2 = (mBuffer[mIndex++] & 0xFF) << 16;
+ int v3 = (mBuffer[mIndex++] & 0xFF) << 8;
+ int v4 = (mBuffer[mIndex++] & 0xFF) << 0;
+ return v1 + v2 + v3 + v4;
+ }
+
+ public long readLong() {
+ long v1 = (mBuffer[mIndex++] & 0xFFL) << 56;
+ long v2 = (mBuffer[mIndex++] & 0xFFL) << 48;
+ long v3 = (mBuffer[mIndex++] & 0xFFL) << 40;
+ long v4 = (mBuffer[mIndex++] & 0xFFL) << 32;
+ long v5 = (mBuffer[mIndex++] & 0xFFL) << 24;
+ long v6 = (mBuffer[mIndex++] & 0xFFL) << 16;
+ long v7 = (mBuffer[mIndex++] & 0xFFL) << 8;
+ long v8 = (mBuffer[mIndex++] & 0xFFL) << 0;
+ return v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8;
+ }
+
+ public float readFloat() {
+ return java.lang.Float.intBitsToFloat(readInt());
+ }
+
+ public double readDouble() {
+ return java.lang.Double.longBitsToDouble(readLong());
+ }
+
+ public byte[] readBuffer() {
+ int count = readInt();
+ byte[] b = Arrays.copyOfRange(mBuffer, mIndex, mIndex + count);
+ mIndex += count;
+ return b;
+ }
+
+ public byte[] readBuffer(int maxSize) {
+ int count = readInt();
+ if (count < 0 || count > maxSize) {
+ throw new RuntimeException("attempt read a buff of invalid size 0 <= "
+ + count + " > " + maxSize);
+ }
+ byte[] b = Arrays.copyOfRange(mBuffer, mIndex, mIndex + count);
+ mIndex += count;
+ return b;
+ }
+
+ public String readUTF8() {
+ byte[] stringBuffer = readBuffer();
+ return new String(stringBuffer);
+ }
+
+ public String readUTF8(int maxSize) {
+ byte[] stringBuffer = readBuffer(maxSize);
+ return new String(stringBuffer);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Write values
+ ///////////////////////////////////////////////////////////////////////////
+
+ public void writeBoolean(boolean value) {
+ resize(1);
+ mBuffer[mIndex++] = (byte) ((value) ? 1 : 0);
+ mSize++;
+ }
+
+ public void writeByte(int value) {
+ resize(1);
+ mBuffer[mIndex++] = (byte) value;
+ mSize++;
+ }
+
+ public void writeShort(int value) {
+ int need = 2;
+ resize(need);
+ mBuffer[mIndex++] = (byte) (value >>> 8 & 0xFF);
+ mBuffer[mIndex++] = (byte) (value & 0xFF);
+ mSize += need;
+ }
+
+ public void writeInt(int value) {
+ int need = 4;
+ resize(need);
+ mBuffer[mIndex++] = (byte) (value >>> 24 & 0xFF);
+ mBuffer[mIndex++] = (byte) (value >>> 16 & 0xFF);
+ mBuffer[mIndex++] = (byte) (value >>> 8 & 0xFF);
+ mBuffer[mIndex++] = (byte) (value & 0xFF);
+ mSize += need;
+ }
+
+ public void writeLong(long value) {
+ int need = 8;
+ resize(need);
+ mBuffer[mIndex++] = (byte) (value >>> 56 & 0xFF);
+ mBuffer[mIndex++] = (byte) (value >>> 48 & 0xFF);
+ mBuffer[mIndex++] = (byte) (value >>> 40 & 0xFF);
+ mBuffer[mIndex++] = (byte) (value >>> 32 & 0xFF);
+ mBuffer[mIndex++] = (byte) (value >>> 24 & 0xFF);
+ mBuffer[mIndex++] = (byte) (value >>> 16 & 0xFF);
+ mBuffer[mIndex++] = (byte) (value >>> 8 & 0xFF);
+ mBuffer[mIndex++] = (byte) (value & 0xFF);
+ mSize += need;
+ }
+
+ public void writeFloat(float value) {
+ writeInt(Float.floatToRawIntBits(value));
+ }
+
+ public void writeDouble(double value) {
+ writeLong(Double.doubleToRawLongBits(value));
+ }
+
+ public void writeBuffer(byte[] b) {
+ resize(b.length + 4);
+ writeInt(b.length);
+ for (int i = 0; i < b.length; i++) {
+ mBuffer[mIndex++] = b[i];
+
+ }
+ mSize += b.length;
+ }
+
+ public void writeUTF8(String content) {
+ byte[] buffer = content.getBytes();
+ writeBuffer(buffer);
+ }
+
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
new file mode 100644
index 0000000..4bfdc59
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Operation to deal with bitmap data
+ * On getting an Image during a draw call the bitmap is compressed and saved
+ * in playback the image is decompressed
+ */
+public class BitmapData implements Operation {
+ int mImageId;
+ int mImageWidth;
+ int mImageHeight;
+ byte[] mBitmap;
+ public static final int MAX_IMAGE_DIMENSION = 6000;
+
+ public static final Companion COMPANION = new Companion();
+
+ public BitmapData(int imageId, int width, int height, byte[] bitmap) {
+ this.mImageId = imageId;
+ this.mImageWidth = width;
+ this.mImageHeight = height;
+ this.mBitmap = bitmap;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mImageId, mImageWidth, mImageHeight, mBitmap);
+ }
+
+ @Override
+ public String toString() {
+ return "BITMAP DATA $imageId";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {}
+
+ @Override
+ public String name() {
+ return "BitmapData";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DATA_BITMAP;
+ }
+
+ public void apply(WireBuffer buffer, int imageId, int width, int height, byte[] bitmap) {
+ buffer.start(Operations.DATA_BITMAP);
+ buffer.writeInt(imageId);
+ buffer.writeInt(width);
+ buffer.writeInt(height);
+ buffer.writeBuffer(bitmap);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int imageId = buffer.readInt();
+ int width = buffer.readInt();
+ int height = buffer.readInt();
+ if (width < 1
+ || height < 1
+ || height > MAX_IMAGE_DIMENSION
+ || width > MAX_IMAGE_DIMENSION) {
+ throw new RuntimeException("Dimension of image is invalid " + width + "x" + height);
+ }
+ byte[] bitmap = buffer.readBuffer();
+ operations.add(new BitmapData(imageId, width, height, bitmap));
+ }
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ context.loadBitmap(mImageId, mImageWidth, mImageHeight, mBitmap);
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return indent + toString();
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
new file mode 100644
index 0000000..a3cd197
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
@@ -0,0 +1,132 @@
+/*
+ * 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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Add a click area to the document
+ */
+public class ClickArea implements RemoteComposeOperation {
+ int mId;
+ int mContentDescription;
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+ int mMetadata;
+
+ public static final Companion COMPANION = new Companion();
+
+ /**
+ * Add a click area to the document
+ *
+ * @param id the id of the click area, which will be reported in the listener
+ * callback on the player
+ * @param contentDescription the content description (used for accessibility, as a textID)
+ * @param left left coordinate of the area bounds
+ * @param top top coordinate of the area bounds
+ * @param right right coordinate of the area bounds
+ * @param bottom bottom coordinate of the area bounds
+ * @param metadata associated metadata, user-provided (as a textID, pointing to a string)
+ */
+ public ClickArea(int id, int contentDescription,
+ float left, float top,
+ float right, float bottom,
+ int metadata) {
+ this.mId = id;
+ this.mContentDescription = contentDescription;
+ this.mLeft = left;
+ this.mTop = top;
+ this.mRight = right;
+ this.mBottom = bottom;
+ this.mMetadata = metadata;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mId, mContentDescription, mLeft, mTop, mRight, mBottom, mMetadata);
+ }
+
+ @Override
+ public String toString() {
+ return "CLICK_AREA <" + mId + " <" + mContentDescription + "> "
+ + "<" + mMetadata + ">+" + mLeft + " "
+ + mTop + " " + mRight + " " + mBottom + "+"
+ + " (" + (mRight - mLeft) + " x " + (mBottom - mTop) + " }";
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ if (context.getMode() != RemoteContext.ContextMode.DATA) {
+ return;
+ }
+ context.addClickArea(mId, mContentDescription, mLeft, mTop, mRight, mBottom, mMetadata);
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return indent + toString();
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {}
+
+ @Override
+ public String name() {
+ return "ClickArea";
+ }
+
+ @Override
+ public int id() {
+ return Operations.CLICK_AREA;
+ }
+
+ public void apply(WireBuffer buffer, int id, int contentDescription,
+ float left, float top, float right, float bottom,
+ int metadata) {
+ buffer.start(Operations.CLICK_AREA);
+ buffer.writeInt(id);
+ buffer.writeInt(contentDescription);
+ buffer.writeFloat(left);
+ buffer.writeFloat(top);
+ buffer.writeFloat(right);
+ buffer.writeFloat(bottom);
+ buffer.writeInt(metadata);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int id = buffer.readInt();
+ int contentDescription = buffer.readInt();
+ float left = buffer.readFloat();
+ float top = buffer.readFloat();
+ float right = buffer.readFloat();
+ float bottom = buffer.readFloat();
+ int metadata = buffer.readInt();
+ ClickArea clickArea = new ClickArea(id, contentDescription,
+ left, top, right, bottom, metadata);
+ operations.add(clickArea);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
new file mode 100644
index 0000000..3fbdf94
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Operation to draw a given cached bitmap
+ */
+public class DrawBitmapInt extends PaintOperation {
+ int mImageId;
+ int mSrcLeft;
+ int mSrcTop;
+ int mSrcRight;
+ int mSrcBottom;
+ int mDstLeft;
+ int mDstTop;
+ int mDstRight;
+ int mDstBottom;
+ int mContentDescId = 0;
+ public static final Companion COMPANION = new Companion();
+
+ public DrawBitmapInt(int imageId,
+ int srcLeft,
+ int srcTop,
+ int srcRight,
+ int srcBottom,
+ int dstLeft,
+ int dstTop,
+ int dstRight,
+ int dstBottom,
+ int cdId) {
+ this.mImageId = imageId;
+ this.mSrcLeft = srcLeft;
+ this.mSrcTop = srcTop;
+ this.mSrcRight = srcRight;
+ this.mSrcBottom = srcBottom;
+ this.mDstLeft = dstLeft;
+ this.mDstTop = dstTop;
+ this.mDstRight = dstRight;
+ this.mDstBottom = dstBottom;
+ this.mContentDescId = cdId;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mImageId, mSrcLeft, mSrcTop, mSrcRight, mSrcBottom,
+ mDstLeft, mDstTop, mDstRight, mDstBottom, mContentDescId);
+ }
+
+ @Override
+ public String toString() {
+ return "DRAW_BITMAP_INT " + mImageId + " on " + mSrcLeft + " " + mSrcTop
+ + " " + mSrcRight + " " + mSrcBottom + " "
+ + "- " + mDstLeft + " " + mDstTop + " " + mDstRight + " " + mDstBottom + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {}
+
+ @Override
+ public String name() {
+ return "DrawBitmapInt";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_BITMAP;
+ }
+
+ public void apply(WireBuffer buffer, int imageId,
+ int srcLeft, int srcTop, int srcRight, int srcBottom,
+ int dstLeft, int dstTop, int dstRight, int dstBottom,
+ int cdId) {
+ buffer.start(Operations.DRAW_BITMAP_INT);
+ buffer.writeInt(imageId);
+ buffer.writeInt(srcLeft);
+ buffer.writeInt(srcTop);
+ buffer.writeInt(srcRight);
+ buffer.writeInt(srcBottom);
+ buffer.writeInt(dstLeft);
+ buffer.writeInt(dstTop);
+ buffer.writeInt(dstRight);
+ buffer.writeInt(dstBottom);
+ buffer.writeInt(cdId);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int imageId = buffer.readInt();
+ int sLeft = buffer.readInt();
+ int srcTop = buffer.readInt();
+ int srcRight = buffer.readInt();
+ int srcBottom = buffer.readInt();
+ int dstLeft = buffer.readInt();
+ int dstTop = buffer.readInt();
+ int dstRight = buffer.readInt();
+ int dstBottom = buffer.readInt();
+ int cdId = buffer.readInt();
+ DrawBitmapInt op = new DrawBitmapInt(imageId, sLeft, srcTop, srcRight, srcBottom,
+ dstLeft, dstTop, dstRight, dstBottom, cdId);
+
+ operations.add(op);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawBitmap(mImageId, mSrcLeft, mSrcTop, mSrcRight, mSrcBottom,
+ mDstLeft, mDstTop, mDstRight, mDstBottom, mContentDescId);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
new file mode 100644
index 0000000..eca43c5
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Describe some basic information for a RemoteCompose document
+ *
+ * It encodes the version of the document (following semantic versioning) as well
+ * as the dimensions of the document in pixels.
+ */
+public class Header implements RemoteComposeOperation {
+ public static final int MAJOR_VERSION = 0;
+ public static final int MINOR_VERSION = 1;
+ public static final int PATCH_VERSION = 0;
+
+ int mMajorVersion;
+ int mMinorVersion;
+ int mPatchVersion;
+
+ int mWidth;
+ int mHeight;
+ long mCapabilities;
+
+ public static final Companion COMPANION = new Companion();
+
+ /**
+ * It encodes the version of the document (following semantic versioning) as well
+ * as the dimensions of the document in pixels.
+ *
+ * @param majorVersion the major version of the RemoteCompose document API
+ * @param minorVersion the minor version of the RemoteCompose document API
+ * @param patchVersion the patch version of the RemoteCompose document API
+ * @param width the width of the RemoteCompose document
+ * @param height the height of the RemoteCompose document
+ * @param capabilities bitmask field storing needed capabilities (unused for now)
+ */
+ public Header(int majorVersion, int minorVersion, int patchVersion,
+ int width, int height, long capabilities) {
+ this.mMajorVersion = majorVersion;
+ this.mMinorVersion = minorVersion;
+ this.mPatchVersion = patchVersion;
+ this.mWidth = width;
+ this.mHeight = height;
+ this.mCapabilities = capabilities;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mWidth, mHeight, mCapabilities);
+ }
+
+ @Override
+ public String toString() {
+ return "HEADER v" + mMajorVersion + "."
+ + mMinorVersion + "." + mPatchVersion + ", "
+ + mWidth + " x " + mHeight + " [" + mCapabilities + "]";
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ context.header(mMajorVersion, mMinorVersion, mPatchVersion, mWidth, mHeight, mCapabilities);
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return toString();
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {}
+
+ @Override
+ public String name() {
+ return "Header";
+ }
+
+ @Override
+ public int id() {
+ return Operations.HEADER;
+ }
+
+ public void apply(WireBuffer buffer, int width, int height, long capabilities) {
+ buffer.start(Operations.HEADER);
+ buffer.writeInt(MAJOR_VERSION); // major version number of the protocol
+ buffer.writeInt(MINOR_VERSION); // minor version number of the protocol
+ buffer.writeInt(PATCH_VERSION); // patch version number of the protocol
+ buffer.writeInt(width);
+ buffer.writeInt(height);
+ buffer.writeLong(capabilities);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int majorVersion = buffer.readInt();
+ int minorVersion = buffer.readInt();
+ int patchVersion = buffer.readInt();
+ int width = buffer.readInt();
+ int height = buffer.readInt();
+ long capabilities = buffer.readLong();
+ Header header = new Header(majorVersion, minorVersion, patchVersion,
+ width, height, capabilities);
+ operations.add(header);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
new file mode 100644
index 0000000..ad4caea
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
@@ -0,0 +1,234 @@
+/*
+ * 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.internal.widget.remotecompose.core.operations;
+
+import android.util.Log;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Describe some basic information for a RemoteCompose document
+ *
+ * It encodes the version of the document (following semantic versioning) as well
+ * as the dimensions of the document in pixels.
+ */
+public class RootContentBehavior implements RemoteComposeOperation {
+
+ int mScroll = NONE;
+ int mSizing = NONE;
+
+ int mAlignment = ALIGNMENT_CENTER;
+
+ int mMode = NONE;
+
+ protected static final String TAG = "RootContentBehavior";
+
+ public static final int NONE = 0;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Scrolling
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ public static final int SCROLL_HORIZONTAL = 1;
+ public static final int SCROLL_VERTICAL = 2;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Sizing
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ public static final int SIZING_LAYOUT = 1;
+ public static final int SIZING_SCALE = 2;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Sizing
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ public static final int ALIGNMENT_TOP = 1;
+ public static final int ALIGNMENT_VERTICAL_CENTER = 2;
+ public static final int ALIGNMENT_BOTTOM = 4;
+ public static final int ALIGNMENT_START = 16;
+ public static final int ALIGNMENT_HORIZONTAL_CENTER = 32;
+ public static final int ALIGNMENT_END = 64;
+ public static final int ALIGNMENT_CENTER = ALIGNMENT_HORIZONTAL_CENTER
+ + ALIGNMENT_VERTICAL_CENTER;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Layout mode
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ public static final int LAYOUT_HORIZONTAL_MATCH_PARENT = 1;
+ public static final int LAYOUT_HORIZONTAL_WRAP_CONTENT = 2;
+ public static final int LAYOUT_HORIZONTAL_FIXED = 4;
+ public static final int LAYOUT_VERTICAL_MATCH_PARENT = 8;
+ public static final int LAYOUT_VERTICAL_WRAP_CONTENT = 16;
+ public static final int LAYOUT_VERTICAL_FIXED = 32;
+ public static final int LAYOUT_MATCH_PARENT =
+ LAYOUT_HORIZONTAL_MATCH_PARENT + LAYOUT_VERTICAL_MATCH_PARENT;
+ public static final int LAYOUT_WRAP_CONTENT =
+ LAYOUT_HORIZONTAL_WRAP_CONTENT + LAYOUT_VERTICAL_WRAP_CONTENT;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Sizing mode
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ public static final int SCALE_INSIDE = 1;
+ public static final int SCALE_FILL_WIDTH = 2;
+ public static final int SCALE_FILL_HEIGHT = 3;
+ public static final int SCALE_FIT = 4;
+ public static final int SCALE_CROP = 5;
+ public static final int SCALE_FILL_BOUNDS = 6;
+
+
+ public static final Companion COMPANION = new Companion();
+
+ /**
+ * Sets the way the player handles the content
+ *
+ * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
+ * @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END)
+ * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
+ * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
+ * the LAYOUT modes are:
+ * - LAYOUT_MATCH_PARENT
+ * - LAYOUT_WRAP_CONTENT
+ * or adding an horizontal mode and a vertical mode:
+ * - LAYOUT_HORIZONTAL_MATCH_PARENT
+ * - LAYOUT_HORIZONTAL_WRAP_CONTENT
+ * - LAYOUT_HORIZONTAL_FIXED
+ * - LAYOUT_VERTICAL_MATCH_PARENT
+ * - LAYOUT_VERTICAL_WRAP_CONTENT
+ * - LAYOUT_VERTICAL_FIXED
+ * The LAYOUT_*_FIXED modes will use the intrinsic document size
+ */
+ public RootContentBehavior(int scroll, int alignment, int sizing, int mode) {
+ switch (scroll) {
+ case NONE:
+ case SCROLL_HORIZONTAL:
+ case SCROLL_VERTICAL:
+ mScroll = scroll;
+ break;
+ default: {
+ Log.e(TAG, "incorrect scroll value " + scroll);
+ }
+ }
+ if (alignment == ALIGNMENT_CENTER) {
+ mAlignment = alignment;
+ } else {
+ int horizontalContentAlignment = alignment & 0xF0;
+ int verticalContentAlignment = alignment & 0xF;
+ boolean validHorizontalAlignment = horizontalContentAlignment == ALIGNMENT_START
+ || horizontalContentAlignment == ALIGNMENT_HORIZONTAL_CENTER
+ || horizontalContentAlignment == ALIGNMENT_END;
+ boolean validVerticalAlignment = verticalContentAlignment == ALIGNMENT_TOP
+ || verticalContentAlignment == ALIGNMENT_VERTICAL_CENTER
+ || verticalContentAlignment == ALIGNMENT_BOTTOM;
+ if (validHorizontalAlignment && validVerticalAlignment) {
+ mAlignment = alignment;
+ } else {
+ Log.e(TAG, "incorrect alignment "
+ + " h: " + horizontalContentAlignment
+ + " v: " + verticalContentAlignment);
+ }
+ }
+ switch (sizing) {
+ case SIZING_LAYOUT: {
+ Log.e(TAG, "sizing_layout is not yet supported");
+ } break;
+ case SIZING_SCALE: {
+ mSizing = sizing;
+ } break;
+ default: {
+ Log.e(TAG, "incorrect sizing value " + sizing);
+ }
+ }
+ if (mSizing == SIZING_LAYOUT) {
+ if (mode != NONE) {
+ Log.e(TAG, "mode for sizing layout is not yet supported");
+ }
+ } else if (mSizing == SIZING_SCALE) {
+ switch (mode) {
+ case SCALE_INSIDE:
+ case SCALE_FIT:
+ case SCALE_FILL_WIDTH:
+ case SCALE_FILL_HEIGHT:
+ case SCALE_CROP:
+ case SCALE_FILL_BOUNDS:
+ mMode = mode;
+ break;
+ default: {
+ Log.e(TAG, "incorrect mode for scale sizing, mode: " + mode);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mScroll, mAlignment, mSizing, mMode);
+ }
+
+ @Override
+ public String toString() {
+ return "ROOT_CONTENT_BEHAVIOR scroll: " + mScroll
+ + " sizing: " + mSizing + " mode: " + mMode;
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ context.setRootContentBehavior(mScroll, mAlignment, mSizing, mMode);
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return toString();
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {}
+
+ @Override
+ public String name() {
+ return "RootContentBehavior";
+ }
+
+ @Override
+ public int id() {
+ return Operations.ROOT_CONTENT_BEHAVIOR;
+ }
+
+ public void apply(WireBuffer buffer, int scroll, int alignment, int sizing, int mode) {
+ buffer.start(Operations.ROOT_CONTENT_BEHAVIOR);
+ buffer.writeInt(scroll);
+ buffer.writeInt(alignment);
+ buffer.writeInt(sizing);
+ buffer.writeInt(mode);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int scroll = buffer.readInt();
+ int alignment = buffer.readInt();
+ int sizing = buffer.readInt();
+ int mode = buffer.readInt();
+ RootContentBehavior rootContentBehavior =
+ new RootContentBehavior(scroll, alignment, sizing, mode);
+ operations.add(rootContentBehavior);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
new file mode 100644
index 0000000..64c7f3e
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Describe a content description for the document
+ */
+public class RootContentDescription implements RemoteComposeOperation {
+ int mContentDescription;
+
+ public static final Companion COMPANION = new Companion();
+
+ /**
+ * Encodes a content description for the document
+ *
+ * @param contentDescription content description for the document
+ */
+ public RootContentDescription(int contentDescription) {
+ this.mContentDescription = contentDescription;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mContentDescription);
+ }
+
+ @Override
+ public String toString() {
+ return "ROOT_CONTENT_DESCRIPTION " + mContentDescription;
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ context.setDocumentContentDescription(mContentDescription);
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return toString();
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {}
+
+ @Override
+ public String name() {
+ return "RootContentDescription";
+ }
+
+ @Override
+ public int id() {
+ return Operations.ROOT_CONTENT_DESCRIPTION;
+ }
+
+ public void apply(WireBuffer buffer, int contentDescription) {
+ buffer.start(Operations.ROOT_CONTENT_DESCRIPTION);
+ buffer.writeInt(contentDescription);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int contentDescription = buffer.readInt();
+ RootContentDescription header = new RootContentDescription(contentDescription);
+ operations.add(header);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
new file mode 100644
index 0000000..5b622ae
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Operation to deal with Text data
+ */
+public class TextData implements Operation {
+ public int mTextId;
+ public String mText;
+ public static final Companion COMPANION = new Companion();
+ public static final int MAX_STRING_SIZE = 4000;
+
+ public TextData(int textId, String text) {
+ this.mTextId = textId;
+ this.mText = text;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mTextId, mText);
+ }
+
+ @Override
+ public String toString() {
+ return "TEXT DATA " + mTextId + "\"" + mText + "\"";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {}
+
+ @Override
+ public String name() {
+ return "TextData";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DATA_TEXT;
+ }
+
+ public void apply(WireBuffer buffer, int textId, String text) {
+ buffer.start(Operations.DATA_TEXT);
+ buffer.writeInt(textId);
+ buffer.writeUTF8(text);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int textId = buffer.readInt();
+
+ String text = buffer.readUTF8(MAX_STRING_SIZE);
+ operations.add(new TextData(textId, text));
+ }
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ context.loadText(mTextId, mText);
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return indent + toString();
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
new file mode 100644
index 0000000..cbe9c12
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
@@ -0,0 +1,97 @@
+/*
+ * 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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Set a current theme, applied to the following operations in the document.
+ * This can be used to "tag" the subsequent operations to a given theme. On playback,
+ * we can then filter operations depending on the chosen theme.
+ *
+ */
+public class Theme implements RemoteComposeOperation {
+ int mTheme;
+ public static final int UNSPECIFIED = -1;
+ public static final int DARK = -2;
+ public static final int LIGHT = -3;
+
+ public static final Companion COMPANION = new Companion();
+
+ /**
+ * we can then filter operations depending on the chosen theme.
+ *
+ * @param theme the theme we are interested in:
+ * - Theme.UNSPECIFIED
+ * - Theme.DARK
+ * - Theme.LIGHT
+ */
+ public Theme(int theme) {
+ this.mTheme = theme;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mTheme);
+ }
+
+ @Override
+ public String toString() {
+ return "SET_THEME " + mTheme;
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ context.setTheme(mTheme);
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return indent + toString();
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {}
+
+ @Override
+ public String name() {
+ return "SetTheme";
+ }
+
+ @Override
+ public int id() {
+ return Operations.THEME;
+ }
+
+ public void apply(WireBuffer buffer, int theme) {
+ buffer.start(Operations.THEME);
+ buffer.writeInt(theme);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int theme = buffer.readInt();
+ operations.add(new Theme(theme));
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
new file mode 100644
index 0000000..8051ef1
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.utilities;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class IntMap<T> {
+
+ private static final int DEFAULT_CAPACITY = 16;
+ private static final float LOAD_FACTOR = 0.75f;
+ private static final int NOT_PRESENT = Integer.MIN_VALUE;
+ private int[] mKeys;
+ private ArrayList<T> mValues;
+ int mSize;
+
+ public IntMap() {
+ mKeys = new int[DEFAULT_CAPACITY];
+ Arrays.fill(mKeys, NOT_PRESENT);
+ mValues = new ArrayList<T>(DEFAULT_CAPACITY);
+ for (int i = 0; i < DEFAULT_CAPACITY; i++) {
+ mValues.add(null);
+ }
+ }
+
+ public void clear() {
+ Arrays.fill(mKeys, NOT_PRESENT);
+ mValues.clear();
+ mSize = 0;
+ }
+
+ public T put(int key, T value) {
+ if (key == NOT_PRESENT) throw new IllegalArgumentException("Key cannot be NOT_PRESENT");
+ if (mSize > mKeys.length * LOAD_FACTOR) {
+ resize();
+ }
+ return insert(key, value);
+ }
+
+
+ public T get(int key) {
+ int index = findKey(key);
+ if (index == -1) {
+ return null;
+ } else
+ return mValues.get(index);
+ }
+
+ public int size() {
+ return mSize;
+ }
+
+ private T insert(int key, T value) {
+ int index = hash(key) % mKeys.length;
+ while (mKeys[index] != NOT_PRESENT && mKeys[index] != key) {
+ index = (index + 1) % mKeys.length;
+ }
+ T oldValue = null;
+ if (mKeys[index] == NOT_PRESENT) {
+ mSize++;
+ } else {
+ oldValue = mValues.get(index);
+ }
+ mKeys[index] = key;
+ mValues.set(index, value);
+ return oldValue;
+ }
+
+ private int findKey(int key) {
+ int index = hash(key) % mKeys.length;
+ while (mKeys[index] != NOT_PRESENT) {
+ if (mKeys[index] == key) {
+ return index;
+ }
+ index = (index + 1) % mKeys.length;
+ }
+ return -1;
+ }
+
+ private int hash(int key) {
+ return key;
+ }
+
+ private void resize() {
+ int[] oldKeys = mKeys;
+ ArrayList<T> oldValues = mValues;
+ mKeys = new int[(oldKeys.length * 2)];
+ for (int i = 0; i < mKeys.length; i++) {
+ mKeys[i] = NOT_PRESENT;
+ }
+ mValues = new ArrayList<T>(oldKeys.length * 2);
+ for (int i = 0; i < oldKeys.length * 2; i++) {
+ mValues.add(null);
+ }
+ mSize = 0;
+ for (int i = 0; i < oldKeys.length; i++) {
+ if (oldKeys[i] != NOT_PRESENT) {
+ put(oldKeys[i], oldValues.get(i));
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
new file mode 100644
index 0000000..bcda27a
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
@@ -0,0 +1,93 @@
+/*
+ * 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.internal.widget.remotecompose.player;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.RemoteComposeBuffer;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+
+import java.io.InputStream;
+
+/**
+ * Public API to create a new RemoteComposeDocument coming from an input stream
+ */
+public class RemoteComposeDocument {
+
+ CoreDocument mDocument = new CoreDocument();
+
+ public RemoteComposeDocument(InputStream inputStream) {
+ RemoteComposeBuffer buffer =
+ RemoteComposeBuffer.fromInputStream(inputStream, mDocument.getRemoteComposeState());
+ mDocument.initFromBuffer(buffer);
+ }
+
+ public CoreDocument getDocument() {
+ return mDocument;
+ }
+
+ public void setDocument(CoreDocument document) {
+ this.mDocument = document;
+ }
+
+ /**
+ * Called when an initialization is needed, allowing the document to eg load
+ * resources / cache them.
+ */
+ public void initializeContext(RemoteContext context) {
+ mDocument.initializeContext(context);
+ }
+
+ /**
+ * Returns the width of the document in pixels
+ */
+ public int getWidth() {
+ return mDocument.getWidth();
+ }
+
+ /**
+ * Returns the height of the document in pixels
+ */
+ public int getHeight() {
+ return mDocument.getHeight();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Painting
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Paint the document
+ *
+ * @param context the provided PaintContext
+ * @param theme the theme we want to use for this document.
+ */
+ public void paint(RemoteContext context, int theme) {
+ mDocument.paint(context, theme);
+ }
+
+ /**
+ * Returns true if the document can be displayed given this version of the player
+ *
+ * @param majorVersion the max major version supported by the player
+ * @param minorVersion the max minor version supported by the player
+ * @param capabilities a bitmask of capabilities the player supports (unused for now)
+ */
+ public boolean canBeDisplayed(int majorVersion, int minorVersion, long capabilities) {
+ return mDocument.canBeDisplayed(majorVersion, minorVersion, capabilities);
+ }
+
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
new file mode 100644
index 0000000..cc1f3dd
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -0,0 +1,177 @@
+/*
+ * 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.internal.widget.remotecompose.player;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.HorizontalScrollView;
+import android.widget.ScrollView;
+
+import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
+import com.android.internal.widget.remotecompose.player.platform.RemoteComposeCanvas;
+
+/**
+ * A view to to display and play RemoteCompose documents
+ */
+public class RemoteComposePlayer extends FrameLayout {
+ private RemoteComposeCanvas mInner;
+
+ private static final int MAX_SUPPORTED_MAJOR_VERSION = 0;
+ private static final int MAX_SUPPORTED_MINOR_VERSION = 1;
+
+ public RemoteComposePlayer(Context context) {
+ super(context);
+ init(context, null, 0);
+ }
+
+ public RemoteComposePlayer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs, 0);
+ }
+
+ public RemoteComposePlayer(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs, defStyleAttr);
+ }
+
+ /**
+ * Turn on debug information
+ * @param debugFlags 1 to set debug on
+ */
+ public void setDebug(int debugFlags) {
+ if (debugFlags == 1) {
+ mInner.setDebug(true);
+ } else {
+ mInner.setDebug(false);
+ }
+ }
+
+ public void setDocument(RemoteComposeDocument value) {
+ if (value != null) {
+ if (value.canBeDisplayed(
+ MAX_SUPPORTED_MAJOR_VERSION,
+ MAX_SUPPORTED_MINOR_VERSION, 0L
+ )
+ ) {
+ mInner.setDocument(value);
+ int contentBehavior = value.getDocument().getContentScroll();
+ applyContentBehavior(contentBehavior);
+ } else {
+ Log.e("RemoteComposePlayer", "Unsupported document ");
+ }
+ } else {
+ mInner.setDocument(null);
+ }
+ }
+
+ /**
+ * Apply the content behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL) to the player,
+ * adding or removing scrollviews as needed.
+ *
+ * @param contentBehavior document content behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
+ */
+ private void applyContentBehavior(int contentBehavior) {
+ switch (contentBehavior) {
+ case RootContentBehavior.SCROLL_HORIZONTAL: {
+ if (!(mInner.getParent() instanceof HorizontalScrollView)) {
+ ((ViewGroup) mInner.getParent()).removeView(mInner);
+ removeAllViews();
+ LayoutParams layoutParamsInner = new LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.MATCH_PARENT);
+ HorizontalScrollView horizontalScrollView =
+ new HorizontalScrollView(getContext());
+ horizontalScrollView.setFillViewport(true);
+ horizontalScrollView.addView(mInner, layoutParamsInner);
+ LayoutParams layoutParams = new LayoutParams(
+ LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
+ addView(horizontalScrollView, layoutParams);
+ }
+ } break;
+ case RootContentBehavior.SCROLL_VERTICAL: {
+ if (!(mInner.getParent() instanceof ScrollView)) {
+ ((ViewGroup) mInner.getParent()).removeView(mInner);
+ removeAllViews();
+ LayoutParams layoutParamsInner = new LayoutParams(
+ LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT);
+ ScrollView scrollView = new ScrollView(getContext());
+ scrollView.setFillViewport(true);
+ scrollView.addView(mInner, layoutParamsInner);
+ LayoutParams layoutParams = new LayoutParams(
+ LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
+ addView(scrollView, layoutParams);
+ }
+ } break;
+ default:
+ if (mInner.getParent() != this) {
+ ((ViewGroup) mInner.getParent()).removeView(mInner);
+ removeAllViews();
+ LayoutParams layoutParams = new LayoutParams(
+ LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
+ addView(mInner, layoutParams);
+ }
+ }
+ }
+
+ private void init(Context context, AttributeSet attrs, int defStyleAttr) {
+ LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
+ mInner = new RemoteComposeCanvas(context, attrs, defStyleAttr);
+ addView(mInner, layoutParams);
+ }
+
+ public interface ClickCallbacks {
+ void click(int id, String metadata);
+ }
+
+ /**
+ * Add a callback for handling click events on the document
+ *
+ * @param callback the callback lambda that will be used when a click is detected
+ * <p>
+ * The parameter of the callback are:
+ * id : the id of the clicked area
+ * metadata: a client provided unstructured string associated with that area
+ */
+ public void addClickListener(ClickCallbacks callback) {
+ mInner.addClickListener((id, metadata) -> callback.click(id, metadata));
+ }
+
+ /**
+ * Set the playback theme for the document. This allows to filter operations in order
+ * to have the document adapt to the given theme. This method is intended to be used
+ * to support night/light themes (system or app level), not custom themes.
+ *
+ * @param theme the theme used for playing the document. Possible values for theme are:
+ * - Theme.UNSPECIFIED -- all instructions in the document will be executed
+ * - Theme.DARK -- only executed NON Light theme instructions
+ * - Theme.LIGHT -- only executed NON Dark theme instructions
+ */
+ public void setTheme(int theme) {
+ if (mInner.getTheme() != theme) {
+ mInner.setTheme(theme);
+ mInner.invalidate();
+ }
+ }
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
new file mode 100644
index 0000000..3799cf6
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.player.platform;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+
+/**
+ * An implementation of PaintContext for the Android Canvas.
+ * This is used to play the RemoteCompose operations on Android.
+ */
+public class AndroidPaintContext extends PaintContext {
+ Paint mPaint = new Paint();
+ Canvas mCanvas;
+
+ public AndroidPaintContext(RemoteContext context, Canvas canvas) {
+ super(context);
+ this.mCanvas = canvas;
+ }
+
+ public Canvas getCanvas() {
+ return mCanvas;
+ }
+
+ public void setCanvas(Canvas canvas) {
+ this.mCanvas = canvas;
+ }
+
+ /**
+ * Draw an image onto the canvas
+ *
+ * @param imageId the id of the image
+ * @param srcLeft left coordinate of the source area
+ * @param srcTop top coordinate of the source area
+ * @param srcRight right coordinate of the source area
+ * @param srcBottom bottom coordinate of the source area
+ * @param dstLeft left coordinate of the destination area
+ * @param dstTop top coordinate of the destination area
+ * @param dstRight right coordinate of the destination area
+ * @param dstBottom bottom coordinate of the destination area
+ */
+
+ @Override
+ public void drawBitmap(int imageId,
+ int srcLeft,
+ int srcTop,
+ int srcRight,
+ int srcBottom,
+ int dstLeft,
+ int dstTop,
+ int dstRight,
+ int dstBottom,
+ int cdId) {
+ AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
+ if (androidContext.mRemoteComposeState.containsId(imageId)) {
+ Bitmap bitmap = (Bitmap) androidContext.mRemoteComposeState.getFromId(imageId);
+ mCanvas.drawBitmap(
+ bitmap,
+ new Rect(srcLeft, srcTop, srcRight, srcBottom),
+ new Rect(dstLeft, dstTop, dstRight, dstBottom), mPaint
+ );
+ }
+ }
+
+ @Override
+ public void scale(float scaleX, float scaleY) {
+ mCanvas.scale(scaleX, scaleY);
+ }
+
+ @Override
+ public void translate(float translateX, float translateY) {
+ mCanvas.translate(translateX, translateY);
+ }
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
new file mode 100644
index 0000000..ce15855
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.player.platform;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+
+/**
+ * An implementation of Context for Android.
+ *
+ * This is used to play the RemoteCompose operations on Android.
+ */
+class AndroidRemoteContext extends RemoteContext {
+
+ public void useCanvas(Canvas canvas) {
+ if (mPaintContext == null) {
+ mPaintContext = new AndroidPaintContext(this, canvas);
+ } else {
+ // need to make sure to update the canvas for the current one
+ ((AndroidPaintContext) mPaintContext).setCanvas(canvas);
+ }
+ mWidth = canvas.getWidth();
+ mHeight = canvas.getHeight();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Data handling
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Decode a byte array into an image and cache it using the given imageId
+ *
+ * @oaram imageId the id of the image
+ * @param width with of image to be loaded
+ * @param height height of image to be loaded
+ * @param bitmap a byte array containing the image information
+ */
+ @Override
+ public void loadBitmap(int imageId, int width, int height, byte[] bitmap) {
+ if (!mRemoteComposeState.containsId(imageId)) {
+ Bitmap image = BitmapFactory.decodeByteArray(bitmap, 0, bitmap.length);
+ mRemoteComposeState.cache(imageId, image);
+ }
+ }
+
+ @Override
+ public void loadText(int id, String text) {
+ if (!mRemoteComposeState.containsId(id)) {
+ mRemoteComposeState.cache(id, text);
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Click handling
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+
+ @Override
+ public void addClickArea(int id,
+ int contentDescriptionId,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ int metadataId) {
+ String contentDescription = (String) mRemoteComposeState.getFromId(contentDescriptionId);
+ String metadata = (String) mRemoteComposeState.getFromId(metadataId);
+ mDocument.addClickArea(id, contentDescription, left, top, right, bottom, metadata);
+ }
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/ClickAreaView.java b/core/java/com/android/internal/widget/remotecompose/player/platform/ClickAreaView.java
new file mode 100644
index 0000000..672dae3
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/ClickAreaView.java
@@ -0,0 +1,66 @@
+/*
+ * 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.internal.widget.remotecompose.player.platform;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.view.View;
+
+
+/**
+ * Implementation for the click handling
+ */
+class ClickAreaView extends View {
+ private int mId;
+ private String mMetadata;
+ Paint mPaint = new Paint();
+
+ private boolean mDebug;
+
+ ClickAreaView(Context context, boolean debug, int id,
+ String contentDescription, String metadata) {
+ super(context);
+ this.mId = id;
+ this.mMetadata = metadata;
+ this.mDebug = debug;
+ setContentDescription(contentDescription);
+ }
+
+
+ public void setDebug(boolean value) {
+ if (mDebug != value) {
+ mDebug = value;
+ invalidate();
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mDebug) {
+ mPaint.setARGB(200, 200, 0, 0);
+ mPaint.setStrokeWidth(3f);
+ canvas.drawLine(0, 0, getWidth(), 0, mPaint);
+ canvas.drawLine(getWidth(), 0, getWidth(), getHeight(), mPaint);
+ canvas.drawLine(getWidth(), getHeight(), 0, getHeight(), mPaint);
+ canvas.drawLine(0, getHeight(), 0, 0, mPaint);
+
+ mPaint.setTextSize(20f);
+ canvas.drawText("id: " + mId + " : " + mMetadata, 4, 22, mPaint);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
new file mode 100644
index 0000000..a3bb73e
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.player.platform;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
+import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.player.RemoteComposeDocument;
+
+import java.util.Set;
+
+/**
+ * Internal view handling the actual painting / interactions
+ */
+public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachStateChangeListener {
+
+ static final boolean USE_VIEW_AREA_CLICK = true; // Use views to represent click areas
+ RemoteComposeDocument mDocument = null;
+ int mTheme = Theme.LIGHT;
+ boolean mInActionDown = false;
+ boolean mDebug = false;
+ Point mActionDownPoint = new Point(0, 0);
+
+ public RemoteComposeCanvas(Context context) {
+ super(context);
+ if (USE_VIEW_AREA_CLICK) {
+ addOnAttachStateChangeListener(this);
+ }
+ }
+
+ public RemoteComposeCanvas(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ if (USE_VIEW_AREA_CLICK) {
+ addOnAttachStateChangeListener(this);
+ }
+ }
+
+ public RemoteComposeCanvas(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setBackgroundColor(Color.WHITE);
+ if (USE_VIEW_AREA_CLICK) {
+ addOnAttachStateChangeListener(this);
+ }
+ }
+
+ public void setDebug(boolean value) {
+ if (mDebug != value) {
+ mDebug = value;
+ if (USE_VIEW_AREA_CLICK) {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child instanceof ClickAreaView) {
+ ((ClickAreaView) child).setDebug(mDebug);
+ }
+ }
+ }
+ invalidate();
+ }
+ }
+
+ public void setDocument(RemoteComposeDocument value) {
+ mDocument = value;
+ mDocument.initializeContext(mARContext);
+ setContentDescription(mDocument.getDocument().getContentDescription());
+ requestLayout();
+ }
+
+ AndroidRemoteContext mARContext = new AndroidRemoteContext();
+
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ if (mDocument == null) {
+ return;
+ }
+ Set<CoreDocument.ClickAreaRepresentation> clickAreas = mDocument
+ .getDocument().getClickAreas();
+ removeAllViews();
+ for (CoreDocument.ClickAreaRepresentation area : clickAreas) {
+ ClickAreaView viewArea = new ClickAreaView(getContext(), mDebug,
+ area.getId(), area.getContentDescription(),
+ area.getMetadata());
+ int w = (int) area.width();
+ int h = (int) area.height();
+ FrameLayout.LayoutParams param = new FrameLayout.LayoutParams(w, h);
+ param.width = w;
+ param.height = h;
+ param.leftMargin = (int) area.getLeft();
+ param.topMargin = (int) area.getTop();
+ viewArea.setOnClickListener(view1
+ -> mDocument.getDocument().performClick(area.getId()));
+ addView(viewArea, param);
+ }
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ removeAllViews();
+ }
+
+
+ public interface ClickCallbacks {
+ void click(int id, String metadata);
+ }
+
+ public void addClickListener(ClickCallbacks callback) {
+ if (mDocument == null) {
+ return;
+ }
+ mDocument.getDocument().addClickListener((id, metadata) -> callback.click(id, metadata));
+ }
+
+ public int getTheme() {
+ return mTheme;
+ }
+
+ public void setTheme(int theme) {
+ this.mTheme = theme;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ if (USE_VIEW_AREA_CLICK) {
+ return super.onTouchEvent(event);
+ }
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ mActionDownPoint.x = (int) event.getX();
+ mActionDownPoint.y = (int) event.getY();
+ mInActionDown = true;
+ return true;
+ }
+ case MotionEvent.ACTION_CANCEL: {
+ mInActionDown = false;
+ return true;
+ }
+ case MotionEvent.ACTION_UP: {
+ mInActionDown = false;
+ performClick();
+ return true;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean performClick() {
+ if (USE_VIEW_AREA_CLICK) {
+ return super.performClick();
+ }
+ mDocument.getDocument().onClick((float) mActionDownPoint.x, (float) mActionDownPoint.y);
+ super.performClick();
+ return true;
+ }
+
+ public int measureDimension(int measureSpec, int intrinsicSize) {
+ int result = intrinsicSize;
+ int mode = MeasureSpec.getMode(measureSpec);
+ int size = MeasureSpec.getSize(measureSpec);
+ switch (mode) {
+ case MeasureSpec.EXACTLY:
+ result = size;
+ break;
+ case MeasureSpec.AT_MOST:
+ result = Integer.min(size, intrinsicSize);
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ result = intrinsicSize;
+ }
+ return result;
+ }
+
+ private static final float[] sScaleOutput = new float[2];
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (mDocument == null) {
+ return;
+ }
+ int w = measureDimension(widthMeasureSpec, mDocument.getWidth());
+ int h = measureDimension(heightMeasureSpec, mDocument.getHeight());
+
+ if (!USE_VIEW_AREA_CLICK) {
+ if (mDocument.getDocument().getContentSizing() == RootContentBehavior.SIZING_SCALE) {
+ mDocument.getDocument().computeScale(w, h, sScaleOutput);
+ w = (int) (mDocument.getWidth() * sScaleOutput[0]);
+ h = (int) (mDocument.getHeight() * sScaleOutput[1]);
+ }
+ }
+ setMeasuredDimension(w, h);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mDocument == null) {
+ return;
+ }
+ mARContext.setDebug(mDebug);
+ mARContext.useCanvas(canvas);
+ mARContext.mWidth = getWidth();
+ mARContext.mHeight = getHeight();
+ mDocument.paint(mARContext, mTheme);
+ }
+
+}
+
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 323f7b6..b5fbb22 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -73,7 +73,7 @@
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 @@
}
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 @@
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 d3f3af7..2861858 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -723,6 +723,7 @@
// 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/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6be1be4..c71a842 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5591,6 +5591,7 @@
of a session based install.
<p>Not for use by third-party applications.
@hide
+ @FlaggedApi("android.content.pm.get_resolved_apk_path")
-->
<permission android:name="android.permission.READ_INSTALLED_SESSION_PATHS"
android:protectionLevel="signature|installer" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2eb28eb..e7b1d09 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4375,9 +4375,14 @@
<!-- Specify one or more <code>polling-loop-filter</code> elements inside a
<code>host-apdu-service</code> to indicate polling loop frames that
your service can handle. -->
+ <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") -->
<declare-styleable name="PollingLoopFilter">
<!-- The polling loop frame. This attribute is mandatory. -->
<attr name="name" />
+ <!-- Whether or not the system should automatically start a transaction when this polling
+ loop filter matches. If not set, default value is false. -->
+ <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") -->
+ <attr name="autoTransact" format="boolean"/>
</declare-styleable>
<!-- Use <code>host-nfcf-service</code> as the root tag of the XML resource that
@@ -5783,7 +5788,17 @@
<enum name="none" value="0" />
<!-- Justification by stretching word spacing. -->
<enum name="inter_word" value = "1" />
+ <!-- Justification by stretching letter spacing. -->
+ <!-- @FlaggedApi("com.android.text.flags.inter_character_justification") -->
+ <enum name="inter_character" value = "2" />
</attr>
+ <!-- Whether to use width of bounding box as a source of automatic line breaking and
+ drawing.
+ If this value is false, the TextView determines the View width, drawing offset and
+ automatic line breaking based on total advances as text widths. By setting true,
+ use glyph bound's as a source of text width. -->
+ <!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") -->
+ <attr name="useBoundsForWidth" format="boolean" />
</declare-styleable>
<declare-styleable name="TextViewAppearance">
<!-- Base text color, typeface, size, and style. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 6884fc0..65c4d9f 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1273,11 +1273,17 @@
null to indicate no split types are offered. -->
<attr name="splitTypes" format="string" />
- <!-- Flag to specify if this app wants to run the dex within its APK but not extracted or
- locally compiled variants. This keeps the dex code protected by the APK signature. Such
- apps will always run in JIT mode (same when they are first installed), and the system will
- never generate ahead-of-time compiled code for them. Depending on the app's workload,
- there may be some run time performance change, noteably the cold start time. -->
+ <!-- Flag to specify if this app (or process) wants to run the dex within its APK but not
+ extracted or locally compiled variants. This keeps the dex code protected by the APK
+ signature. Such apps (or processes) will always run in JIT mode (same when they are first
+ installed). If enabled at the app level, the system will never generate ahead-of-time
+ compiled code for the app. Depending on the app's workload, there may be some run time
+ performance change, noteably the cold start time.
+
+ <p>This attribute can be applied to either
+ {@link android.R.styleable#AndroidManifestProcess process} or
+ {@link android.R.styleable#AndroidManifestApplication application} tags. If enabled at the
+ app level, any process level attribute is effectively ignored. -->
<attr name="useEmbeddedDex" format="boolean" />
<!-- Extra options for an activity's UI. Applies to either the {@code <activity>} or
@@ -2793,6 +2799,7 @@
<attr name="gwpAsanMode" />
<attr name="memtagMode" />
<attr name="nativeHeapZeroInitialized" />
+ <attr name="useEmbeddedDex" />
</declare-styleable>
<!-- The <code>deny-permission</code> tag specifies that a permission is to be denied
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e006b9d..951625e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4055,16 +4055,22 @@
<bool name="config_maskMainBuiltInDisplayCutout">false</bool>
<!-- This string array provide override side of each rotation of the given insets.
- Array of "[rotation],[side]".
- Undefined rotation will apply the default behavior.
+ Array of [side] for rotation 0, 90, 180 and 270 in order.
+ The options of [side] are:
+ - Option 0 - Left.
+ - Option 1 - Top.
+ - Option 2 - Right.
+ - Option 3 - Bottom.
When there are cutouts on multiple edges of the display, the override won't take any
effect. -->
- <string-array name="config_mainBuiltInDisplayCutoutSideOverride" translatable="false">
- <!-- Example:
- <item>90,top</item>
- <item>270,bottom</item>
+ <integer-array name="config_mainBuiltInDisplayCutoutSideOverride">
+ <!-- Example of at top for rotation 0 and 90, and at bottom for rotation 180 and 270:
+ <item>1</item>
+ <item>1</item>
+ <item>3</item>
+ <item>3</item>
-->
- </string-array>
+ </integer-array>
<!-- Ultrasound support for Mic/speaker path -->
<!-- Whether the default microphone audio source supports near-ultrasound frequencies
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 830e99c..81a8908 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -143,6 +143,10 @@
<public name="fragmentAdvancedPattern"/>
<!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
<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/OWNERS b/core/tests/BroadcastRadioTests/OWNERS
index d2bdd64..51a85e4 100644
--- a/core/tests/BroadcastRadioTests/OWNERS
+++ b/core/tests/BroadcastRadioTests/OWNERS
@@ -1,3 +1,3 @@
xuweilin@google.com
oscarazu@google.com
-keunyoung@google.com
+ericjeong@google.com
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 296c451..262f167 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 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.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 @@
@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/LaunchCookieTest.java b/core/tests/coretests/src/android/app/LaunchCookieTest.java
new file mode 100644
index 0000000..9325391
--- /dev/null
+++ b/core/tests/coretests/src/android/app/LaunchCookieTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.app.ActivityOptions.LaunchCookie;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class LaunchCookieTest {
+
+ @Test
+ public void parcelNonNullLaunchCookie() {
+ LaunchCookie launchCookie = new LaunchCookie();
+ Parcel parcel = Parcel.obtain();
+ LaunchCookie.writeToParcel(launchCookie, parcel);
+ parcel.setDataPosition(0);
+ LaunchCookie unparceledLaunchCookie = LaunchCookie.readFromParcel(parcel);
+ assertEquals(launchCookie, unparceledLaunchCookie);
+ }
+
+ @Test
+ public void parcelNullLaunchCookie() {
+ Parcel parcel = Parcel.obtain();
+ LaunchCookie.writeToParcel(/*launchCookie*/null, parcel);
+ LaunchCookie unparceledLaunchCookie = LaunchCookie.readFromParcel(parcel);
+ assertNull(unparceledLaunchCookie);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index a796a0f..c6447be 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 @@
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 @@
.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 3823033..d641659 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 @@
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 @@
}
@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 952cdd9..508c6b2 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 @@
.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 a28bb69..2bd5631 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -219,6 +219,22 @@
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 @@
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 52ff0d4..a709d7b 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -20,7 +20,6 @@
import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeast;
@@ -359,7 +358,7 @@
}
@Test
- public void onDetachFromWindow_cancelsBackAnimation() throws RemoteException {
+ public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
@@ -369,12 +368,13 @@
waitForIdle();
verify(mCallback1).onBackStarted(any(BackEvent.class));
- // This should trigger mCallback1.onBackCancelled() and unset the callback in WM
+ // This should trigger mCallback1.onBackCancelled()
mDispatcher.detachFromWindow();
+ // This should be ignored by mCallback1
+ callbackInfo.getCallback().onBackInvoked();
- OnBackInvokedCallbackInfo callbackInfo1 = assertSetCallbackInfo();
- assertNull(callbackInfo1);
waitForIdle();
+ verify(mCallback1, never()).onBackInvoked();
verify(mCallback1).onBackCancelled();
}
}
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 145aa60d..75b0d4a 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -21,6 +21,7 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
@@ -82,7 +83,6 @@
import com.android.internal.R;
import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkObjectProvider;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.util.test.FakeSettingsProvider;
import org.junit.AfterClass;
@@ -726,14 +726,14 @@
private void configureNoShortcutService() throws Exception {
when(mAccessibilityManagerService
- .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
+ .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
.thenReturn(Collections.emptyList());
Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
}
private void configureValidShortcutService() throws Exception {
when(mAccessibilityManagerService
- .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
+ .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
.thenReturn(Collections.singletonList(SERVICE_NAME_STRING));
Settings.Secure.putString(
mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, SERVICE_NAME_STRING);
@@ -744,7 +744,7 @@
(ComponentName) AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
.keySet().toArray()[0];
when(mAccessibilityManagerService
- .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
+ .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
.thenReturn(Collections.singletonList(featureComponentName.flattenToString()));
Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
featureComponentName.flattenToString());
@@ -806,7 +806,7 @@
private void configureDefaultAccessibilityService() throws Exception {
when(mAccessibilityManagerService
- .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
+ .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
.thenReturn(Collections.singletonList(SERVICE_NAME_STRING));
when(mResources.getString(R.string.config_defaultAccessibilityService)).thenReturn(
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
index 2ea044c..69b6a9b7a 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
@@ -39,7 +39,6 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.accessibility.TestUtils;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
@@ -100,7 +99,7 @@
mSut = new InvisibleToggleAccessibilityServiceTarget(
mContextSpy,
- ShortcutConstants.UserShortcutType.HARDWARE, accessibilityServiceInfo);
+ AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY, accessibilityServiceInfo);
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
new file mode 100644
index 0000000..08333ec
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
@@ -0,0 +1,330 @@
+/*
+ * 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.internal.widget;
+
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.view.Gravity;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.flags.Flags;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests the consistency of {@link NotificationOptimizedLinearLayout}'s onMeasure and onLayout
+ * implementations with the behavior of the standard Android LinearLayout.
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+@EnableFlags(Flags.FLAG_NOTIF_LINEARLAYOUT_OPTIMIZED)
+@Presubmit
+public class NotificationOptimizedLinearLayoutComparisonTest {
+
+ @Rule
+ public final Expect mExpect = Expect.create();
+
+ private static final int[] ORIENTATIONS = {LinearLayout.VERTICAL, LinearLayout.HORIZONTAL};
+ private static final int EXACT_SPEC = MeasureSpec.makeMeasureSpec(500,
+ MeasureSpec.EXACTLY);
+ private static final int AT_MOST_SPEC = MeasureSpec.makeMeasureSpec(500,
+ MeasureSpec.AT_MOST);
+
+ private static final int[] MEASURE_SPECS = {EXACT_SPEC, AT_MOST_SPEC};
+
+ private static final int[] GRAVITIES =
+ {Gravity.NO_GRAVITY, Gravity.TOP, Gravity.LEFT, Gravity.CENTER};
+
+ private static final int[] LAYOUT_PARAMS = {MATCH_PARENT, WRAP_CONTENT, 0, 50};
+ private static final int[] CHILD_WEIGHTS = {0, 1};
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private Context mContext;
+
+ @Before
+ public void before() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ }
+
+ @Test
+ public void test() throws Throwable {
+ for (int orientation : ORIENTATIONS) {
+ for (int widthSpec : MEASURE_SPECS) {
+ for (int heightSpec : MEASURE_SPECS) {
+ for (int firstChildGravity : GRAVITIES) {
+ for (int secondChildGravity : GRAVITIES) {
+ for (int firstChildLayoutWidth : LAYOUT_PARAMS) {
+ for (int firstChildLayoutHeight : LAYOUT_PARAMS) {
+ for (int secondChildLayoutWidth : LAYOUT_PARAMS) {
+ for (int secondChildLayoutHeight : LAYOUT_PARAMS) {
+ for (int firstChildWeight : CHILD_WEIGHTS) {
+ for (int secondChildWeight : CHILD_WEIGHTS) {
+ executeTest(/*testSpec =*/createTestSpec(
+ orientation,
+ widthSpec, heightSpec,
+ firstChildLayoutWidth,
+ firstChildLayoutHeight,
+ secondChildLayoutWidth,
+ secondChildLayoutHeight,
+ firstChildGravity,
+ secondChildGravity,
+ firstChildWeight,
+ secondChildWeight));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void executeTest(TestSpec testSpec) {
+ // GIVEN
+ final List<View> controlChildren =
+ new ArrayList<>();
+ final List<View> testChildren =
+ new ArrayList<>();
+
+ controlChildren.add(
+ buildChildView(
+ testSpec.mFirstChildLayoutWidth,
+ testSpec.mFirstChildLayoutHeight,
+ testSpec.mFirstChildGravity,
+ testSpec.mFirstChildWeight));
+ controlChildren.add(
+ buildChildView(
+ testSpec.mSecondChildLayoutWidth,
+ testSpec.mSecondChildLayoutHeight,
+ testSpec.mSecondChildGravity,
+ testSpec.mSecondChildWeight));
+
+ testChildren.add(
+ buildChildView(
+ testSpec.mFirstChildLayoutWidth,
+ testSpec.mFirstChildLayoutHeight,
+ testSpec.mFirstChildGravity,
+ testSpec.mFirstChildWeight));
+ testChildren.add(
+ buildChildView(
+ testSpec.mSecondChildLayoutWidth,
+ testSpec.mSecondChildLayoutHeight,
+ testSpec.mSecondChildGravity,
+ testSpec.mSecondChildWeight));
+
+ final LinearLayout controlContainer = buildLayout(false,
+ testSpec.mOrientation,
+ controlChildren);
+
+ final LinearLayout testContainer = buildLayout(true,
+ testSpec.mOrientation,
+ testChildren);
+
+ // WHEN
+ controlContainer.measure(testSpec.mWidthSpec, testSpec.mHeightSpec);
+ testContainer.measure(testSpec.mWidthSpec, testSpec.mHeightSpec);
+ controlContainer.layout(0, 0, 1000, 1000);
+ testContainer.layout(0, 0, 1000, 1000);
+ // THEN
+ assertLayoutsEqual("Test Case:" + testSpec, controlContainer, testContainer);
+ }
+
+ private static class TestSpec {
+ private final int mOrientation;
+ private final int mWidthSpec;
+ private final int mHeightSpec;
+ private final int mFirstChildLayoutWidth;
+ private final int mFirstChildLayoutHeight;
+ private final int mSecondChildLayoutWidth;
+ private final int mSecondChildLayoutHeight;
+ private final int mFirstChildGravity;
+ private final int mSecondChildGravity;
+ private final int mFirstChildWeight;
+ private final int mSecondChildWeight;
+
+ TestSpec(
+ int orientation,
+ int widthSpec,
+ int heightSpec,
+ int firstChildLayoutWidth,
+ int firstChildLayoutHeight,
+ int secondChildLayoutWidth,
+ int secondChildLayoutHeight,
+ int firstChildGravity,
+ int secondChildGravity,
+ int firstChildWeight,
+ int secondChildWeight) {
+ mOrientation = orientation;
+ mWidthSpec = widthSpec;
+ mHeightSpec = heightSpec;
+ mFirstChildLayoutWidth = firstChildLayoutWidth;
+ mFirstChildLayoutHeight = firstChildLayoutHeight;
+ mSecondChildLayoutWidth = secondChildLayoutWidth;
+ mSecondChildLayoutHeight = secondChildLayoutHeight;
+ mFirstChildGravity = firstChildGravity;
+ mSecondChildGravity = secondChildGravity;
+ mFirstChildWeight = firstChildWeight;
+ mSecondChildWeight = secondChildWeight;
+ }
+
+ @Override
+ public String toString() {
+ return "TestSpec{"
+ + "mOrientation=" + orientationToString(mOrientation)
+ + ", mWidthSpec=" + MeasureSpec.toString(mWidthSpec)
+ + ", mHeightSpec=" + MeasureSpec.toString(mHeightSpec)
+ + ", mFirstChildLayoutWidth=" + sizeToString(mFirstChildLayoutWidth)
+ + ", mFirstChildLayoutHeight=" + sizeToString(mFirstChildLayoutHeight)
+ + ", mSecondChildLayoutWidth=" + sizeToString(mSecondChildLayoutWidth)
+ + ", mSecondChildLayoutHeight=" + sizeToString(mSecondChildLayoutHeight)
+ + ", mFirstChildGravity=" + mFirstChildGravity
+ + ", mSecondChildGravity=" + mSecondChildGravity
+ + ", mFirstChildWeight=" + mFirstChildWeight
+ + ", mSecondChildWeight=" + mSecondChildWeight
+ + '}';
+ }
+
+ private String orientationToString(int orientation) {
+ if (orientation == LinearLayout.VERTICAL) {
+ return "vertical";
+ } else if (orientation == LinearLayout.HORIZONTAL) {
+ return "horizontal";
+ }
+ throw new IllegalArgumentException();
+ }
+
+ private String sizeToString(int size) {
+ if (size == WRAP_CONTENT) {
+ return "wrap-content";
+ }
+ if (size == MATCH_PARENT) {
+ return "match-parent";
+ }
+ return String.valueOf(size);
+ }
+ }
+
+ private LinearLayout buildLayout(boolean isNotificationOptimized,
+ @LinearLayout.OrientationMode int orientation, List<View> children) {
+ final LinearLayout linearLayout;
+ if (isNotificationOptimized) {
+ linearLayout = new NotificationOptimizedLinearLayout(mContext);
+ } else {
+ linearLayout = new LinearLayout(mContext);
+ }
+ linearLayout.setOrientation(orientation);
+ for (int i = 0; i < children.size(); i++) {
+ linearLayout.addView(children.get(i));
+ }
+ return linearLayout;
+ }
+
+ private void assertLayoutsEqual(String testCase, View controlView, View testView) {
+ mExpect.withMessage("MeasuredWidths are not equal. Test Case:" + testCase)
+ .that(testView.getMeasuredWidth()).isEqualTo(controlView.getMeasuredWidth());
+ mExpect.withMessage("MeasuredHeights are not equal. Test Case:" + testCase)
+ .that(testView.getMeasuredHeight()).isEqualTo(controlView.getMeasuredHeight());
+ mExpect.withMessage("Left Positions are not equal. Test Case:" + testCase)
+ .that(testView.getLeft()).isEqualTo(controlView.getLeft());
+ mExpect.withMessage("Top Positions are not equal. Test Case:" + testCase)
+ .that(testView.getTop()).isEqualTo(controlView.getTop());
+ if (controlView instanceof ViewGroup && testView instanceof ViewGroup) {
+ final ViewGroup controlGroup = (ViewGroup) controlView;
+ final ViewGroup testGroup = (ViewGroup) testView;
+ // Test and Control Views should be identical by hierarchy for the comparison.
+ // That's why mExpect is not used here for assertion.
+ assertEquals(controlGroup.getChildCount(), testGroup.getChildCount());
+
+ for (int i = 0; i < controlGroup.getChildCount(); i++) {
+ View controlChild = controlGroup.getChildAt(i);
+ View testChild = testGroup.getChildAt(i);
+
+ assertLayoutsEqual(testCase, controlChild, testChild);
+ }
+ }
+ }
+
+ private static class TestView extends View {
+ TestView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public int getBaseline() {
+ return 5;
+ }
+ }
+
+
+ private TestSpec createTestSpec(int orientation,
+ int widthSpec, int heightSpec,
+ int firstChildLayoutWidth, int firstChildLayoutHeight, int secondChildLayoutWidth,
+ int secondChildLayoutHeight, int firstChildGravity, int secondChildGravity,
+ int firstChildWeight, int secondChildWeight) {
+
+ return new TestSpec(
+ orientation,
+ widthSpec, heightSpec,
+ firstChildLayoutWidth,
+ firstChildLayoutHeight,
+ secondChildLayoutWidth,
+ secondChildLayoutHeight,
+ firstChildGravity,
+ secondChildGravity,
+ firstChildWeight,
+ secondChildWeight);
+ }
+
+ private View buildChildView(int childLayoutWidth, int childLayoutHeight,
+ int childGravity, int childWeight) {
+ final View childView = new TestView(mContext);
+ // Set desired size using LayoutParams
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(childLayoutWidth,
+ childLayoutHeight, childWeight);
+ params.gravity = childGravity;
+ childView.setLayoutParams(params);
+ return childView;
+ }
+}
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 3e0e36d..39cb616 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 @@
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 50a58da..a2a2914 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.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 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 @@
* 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 72ca296..621c453 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.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 @@
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 @@
}
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 @@
// 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 @@
@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 @@
@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 @@
/** 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 @@
*/
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 @@
}
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 @@
/** 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 @@
// 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);
}
}
@@ -1771,18 +1786,19 @@
@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();
@@ -1976,7 +1992,8 @@
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 dbfa260..6d3f0c3 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 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 void setExpanded(boolean expanded) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setExpanded: " + expanded);
- }
setExpandedInternal(expanded);
dispatchPendingChanges();
}
@@ -347,9 +344,8 @@
* 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 void setSelectedBubble(BubbleViewProvider bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setSelectedBubble: " + bubble);
- }
setSelectedBubbleInternal(bubble);
dispatchPendingChanges();
}
@@ -430,12 +423,13 @@
* 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 @@
* 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 @@
}
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 @@
}
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 @@
}
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 @@
&& 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 @@
}
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 @@
return;
}
Bubble bubbleToRemove = mBubbles.get(indexToRemove);
+ ProtoLog.d(WM_SHELL_BUBBLES, "doRemove=%s", bubbleToRemove.getKey());
bubbleToRemove.stopInflation();
overflowBubble(reason, bubbleToRemove);
@@ -744,17 +725,12 @@
}
// 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 @@
}
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 @@
|| 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 @@
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 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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 f56b171..f1a68e2 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 @@
// 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 efc4d8b..088660e 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.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 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 @@
@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 @@
// 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 @@
@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 @@
@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 @@
super.onDetachedFromWindow();
mImeVisible = false;
mNeedsNewHeight = false;
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey());
- }
}
/**
@@ -805,10 +795,6 @@
* 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 @@
* 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 @@
}
mNeedsNewHeight = false;
}
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "updateHeight: bubble=" + getBubbleKey()
- + " height=" + height
- + " mNeedsNewHeight=" + mNeedsNewHeight);
- }
}
}
@@ -974,10 +952,6 @@
* 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 @@
/** 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 9655470..70cdc82 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.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.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 @@
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 @@
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 @@
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 d62c86c..c03b6f8 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 @@
* 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 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 9facef3..c877d4a 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 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 @@
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 @@
}
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
*/
// 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 @@
*/
// 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 @@
* 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 @@
* Stop monitoring for swipe up gesture
*/
void stopMonitoringSwipeUpGesture() {
- ProtoLog.d(WM_SHELL_BUBBLES, "stopMonitoringSwipeUpGesture");
stopMonitoringSwipeUpGestureInternal();
}
@@ -2202,9 +2176,6 @@
}
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 @@
}
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 @@
private void animateCollapse() {
cancelDelayedExpandCollapseSwitchAnimations();
-
+ ProtoLog.d(WM_SHELL_BUBBLES, "animateCollapse");
if (isManageEduVisible()) {
mManageEduView.hide();
}
@@ -2508,11 +2481,6 @@
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 @@
if (!shouldShowFlyout(bubble)) {
return;
}
-
+ ProtoLog.d(WM_SHELL_BUBBLES, "animateFlyout=%s", bubble.getKey());
mFlyoutDragDeltaX = 0f;
clearFlyoutOnHide();
mAfterFlyoutHidden = () -> {
@@ -3036,6 +3004,9 @@
@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 @@
}
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 @@
}
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 dc27133..530ec5a 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.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 androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.taskview.TaskView;
/**
@@ -79,11 +80,8 @@
@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 @@
// 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 @@
@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 @@
@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 845dca3..e43609f 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.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.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 @@
*/
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 @@
@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 @@
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 @@
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 @@
@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 @@
@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 e86b62d..6325c68 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 @@
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 0c25f27..b3e8bd9 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 @@
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 @@
mActiveTraces::incrementAndGet,
this::onFlush,
mActiveTraces::decrementAndGet);
+ private final Map<String, Integer> mHandlerMapping = new HashMap<>();
public PerfettoTransitionTracer() {
Producer.init(InitArguments.DEFAULTS);
@@ -69,7 +69,7 @@
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 @@
});
}
- 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 @@
}
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 c572944..279f9d6 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -484,8 +484,9 @@
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_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 9f94ef9..28cf250 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -73,6 +73,7 @@
method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcListenerDeviceInfo getWlcListenerDeviceInfo();
method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
method public boolean isEnabled();
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeEnabled();
method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled();
method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index f8b640c7..7680136 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -14,15 +14,23 @@
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported();
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+ method @FlaggedApi("android.nfc.nfc_vendor_cmd") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerNfcVendorNciCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.NfcVendorNciCallback);
method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
+ method @FlaggedApi("android.nfc.nfc_vendor_cmd") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int sendVendorNciMessage(int, @IntRange(from=0, to=15) int, @IntRange(from=0) int, @NonNull byte[]);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderModePollingEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setWlcEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterNfcVendorNciCallback(@NonNull android.nfc.NfcAdapter.NfcVendorNciCallback);
method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener);
field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
+ field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int MESSAGE_TYPE_COMMAND = 1; // 0x1
+ field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_FAILED = 3; // 0x3
+ field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_MESSAGE_CORRUPTED = 2; // 0x2
+ field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_REJECTED = 1; // 0x1
+ field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_SUCCESS = 0; // 0x0
field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
field public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2; // 0xfffffffe
@@ -36,6 +44,11 @@
method public boolean onUnlockAttempted(android.nfc.Tag);
}
+ @FlaggedApi("android.nfc.nfc_vendor_cmd") public static interface NfcAdapter.NfcVendorNciCallback {
+ method @FlaggedApi("android.nfc.nfc_vendor_cmd") public void onVendorNciNotification(@IntRange(from=9, to=15) int, int, @NonNull byte[]);
+ method @FlaggedApi("android.nfc.nfc_vendor_cmd") public void onVendorNciResponse(@IntRange(from=0, to=15) int, int, @NonNull byte[]);
+ }
+
@FlaggedApi("android.nfc.enable_nfc_charging") public static interface NfcAdapter.WlcStateListener {
method public void onWlcStateChanged(@NonNull android.nfc.WlcListenerDeviceInfo);
}
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 63c3414..c444740 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -24,6 +24,7 @@
import android.nfc.IAppCallback;
import android.nfc.INfcAdapterExtras;
import android.nfc.INfcControllerAlwaysOnListener;
+import android.nfc.INfcVendorNciCallback;
import android.nfc.INfcTag;
import android.nfc.INfcCardEmulation;
import android.nfc.INfcFCardEmulation;
@@ -87,6 +88,7 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
boolean enableReaderOption(boolean enable);
boolean isObserveModeSupported();
+ boolean isObserveModeEnabled();
boolean setObserveMode(boolean enabled);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
@@ -100,4 +102,7 @@
void notifyPollingLoop(in Bundle frame);
void notifyHceDeactivated();
+ int sendVendorNciMessage(int mt, int gid, int oid, in byte[] payload);
+ void registerVendorExtensionCallback(in INfcVendorNciCallback callbacks);
+ void unregisterVendorExtensionCallback(in INfcVendorNciCallback callbacks);
}
diff --git a/nfc/java/android/nfc/INfcVendorNciCallback.aidl b/nfc/java/android/nfc/INfcVendorNciCallback.aidl
new file mode 100644
index 0000000..821dc6f
--- /dev/null
+++ b/nfc/java/android/nfc/INfcVendorNciCallback.aidl
@@ -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 android.nfc;
+
+/**
+ * @hide
+ */
+oneway interface INfcVendorNciCallback {
+ void onVendorResponseReceived(int gid, int oid, in byte[] payload);
+ void onVendorNotificationReceived(int gid, int oid, in byte[] payload);
+}
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 5b917a1..782af5f 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -19,6 +19,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -77,6 +78,7 @@
private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener;
private final NfcWlcStateListener mNfcWlcStateListener;
+ private final NfcVendorNciCallbackListener mNfcVendorNciCallbackListener;
/**
* Intent to start an activity when a tag with NDEF payload is discovered.
@@ -866,6 +868,7 @@
mLock = new Object();
mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService());
mNfcWlcStateListener = new NfcWlcStateListener(getService());
+ mNfcVendorNciCallbackListener = new NfcVendorNciCallbackListener(getService());
}
/**
@@ -1205,6 +1208,22 @@
}
/**
+ * Returns whether Observe Mode is currently enabled or not.
+ *
+ * @return true if observe mode is enabled, false otherwise.
+ */
+
+ @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+ public boolean isObserveModeEnabled() {
+ try {
+ return sService.isObserveModeEnabled();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
+ }
+
+ /**
* Controls whether the NFC adapter will allow transactions to proceed or be in observe mode
* and simply observe and notify the APDU service of polling loop frames. See
* {@link #isObserveModeSupported()} for a description of observe mode.
@@ -2962,4 +2981,163 @@
return null;
}
}
+
+ /**
+ * Vendor NCI command success.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ public static final int SEND_VENDOR_NCI_STATUS_SUCCESS = 0;
+ /**
+ * Vendor NCI command rejected.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ public static final int SEND_VENDOR_NCI_STATUS_REJECTED = 1;
+ /**
+ * Vendor NCI command corrupted.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ public static final int SEND_VENDOR_NCI_STATUS_MESSAGE_CORRUPTED = 2;
+ /**
+ * Vendor NCI command failed with unknown reason.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ public static final int SEND_VENDOR_NCI_STATUS_FAILED = 3;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ SEND_VENDOR_NCI_STATUS_SUCCESS,
+ SEND_VENDOR_NCI_STATUS_REJECTED,
+ SEND_VENDOR_NCI_STATUS_MESSAGE_CORRUPTED,
+ SEND_VENDOR_NCI_STATUS_FAILED,
+ })
+ @interface SendVendorNciStatus {}
+
+ /**
+ * Message Type for NCI Command.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ public static final int MESSAGE_TYPE_COMMAND = 1;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ MESSAGE_TYPE_COMMAND,
+ })
+ @interface MessageType {}
+
+ /**
+ * Send Vendor specific Nci Messages with custom message type.
+ *
+ * The format of the NCI messages are defined in the NCI specification. The platform is
+ * responsible for fragmenting the payload if necessary.
+ *
+ * Note that mt (message type) is added at the beginning of method parameters as it is more
+ * distinctive than other parameters and was requested from vendor.
+ *
+ * @param mt message Type of the command
+ * @param gid group ID of the command. This needs to be one of the vendor reserved GIDs from
+ * the NCI specification
+ * @param oid opcode ID of the command. This is left to the OEM / vendor to decide
+ * @param payload containing vendor Nci message payload
+ * @return message send status
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public @SendVendorNciStatus int sendVendorNciMessage(@MessageType int mt,
+ @IntRange(from = 0, to = 15) int gid, @IntRange(from = 0) int oid,
+ @NonNull byte[] payload) {
+ Objects.requireNonNull(payload, "Payload must not be null");
+ try {
+ return sService.sendVendorNciMessage(mt, gid, oid, payload);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Register an {@link NfcVendorNciCallback} to listen for Nfc vendor responses and notifications
+ * <p>The provided callback will be invoked by the given {@link Executor}.
+ *
+ * <p>When first registering a callback, the callbacks's
+ * {@link NfcVendorNciCallback#onVendorNciCallBack(byte[])} is immediately invoked to
+ * notify the vendor notification.
+ *
+ * @param executor an {@link Executor} to execute given callback
+ * @param callback user implementation of the {@link NfcVendorNciCallback}
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void registerNfcVendorNciCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull NfcVendorNciCallback callback) {
+ mNfcVendorNciCallbackListener.register(executor, callback);
+ }
+
+ /**
+ * Unregister the specified {@link NfcVendorNciCallback}
+ *
+ * <p>The same {@link NfcVendorNciCallback} object used when calling
+ * {@link #registerNfcVendorNciCallback(Executor, NfcVendorNciCallback)} must be used.
+ *
+ * <p>Callbacks are automatically unregistered when application process goes away
+ *
+ * @param callback user implementation of the {@link NfcVendorNciCallback}
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void unregisterNfcVendorNciCallback(@NonNull NfcVendorNciCallback callback) {
+ mNfcVendorNciCallbackListener.unregister(callback);
+ }
+
+ /**
+ * Interface for receiving vendor NCI responses and notifications.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ public interface NfcVendorNciCallback {
+ /**
+ * Invoked when a vendor specific NCI response is received.
+ *
+ * @param gid group ID of the command. This needs to be one of the vendor reserved GIDs from
+ * the NCI specification.
+ * @param oid opcode ID of the command. This is left to the OEM / vendor to decide.
+ * @param payload containing vendor Nci message payload.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ void onVendorNciResponse(
+ @IntRange(from = 0, to = 15) int gid, int oid, @NonNull byte[] payload);
+
+ /**
+ * Invoked when a vendor specific NCI notification is received.
+ *
+ * @param gid group ID of the command. This needs to be one of the vendor reserved GIDs from
+ * the NCI specification.
+ * @param oid opcode ID of the command. This is left to the OEM / vendor to decide.
+ * @param payload containing vendor Nci message payload.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+ void onVendorNciNotification(
+ @IntRange(from = 9, to = 15) int gid, int oid, @NonNull byte[] payload);
+ }
}
diff --git a/nfc/java/android/nfc/NfcVendorNciCallbackListener.java b/nfc/java/android/nfc/NfcVendorNciCallbackListener.java
new file mode 100644
index 0000000..742d75f
--- /dev/null
+++ b/nfc/java/android/nfc/NfcVendorNciCallbackListener.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.nfc;
+
+import android.annotation.NonNull;
+import android.nfc.NfcAdapter.NfcVendorNciCallback;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public final class NfcVendorNciCallbackListener extends INfcVendorNciCallback.Stub {
+ private static final String TAG = "Nfc.NfcVendorNciCallbacks";
+ private final INfcAdapter mAdapter;
+ private boolean mIsRegistered = false;
+ private final Map<NfcVendorNciCallback, Executor> mCallbackMap = new HashMap<>();
+
+ public NfcVendorNciCallbackListener(@NonNull INfcAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ public void register(@NonNull Executor executor, @NonNull NfcVendorNciCallback callback) {
+ synchronized (this) {
+ if (mCallbackMap.containsKey(callback)) {
+ return;
+ }
+ mCallbackMap.put(callback, executor);
+ if (!mIsRegistered) {
+ try {
+ mAdapter.registerVendorExtensionCallback(this);
+ mIsRegistered = true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register adapter state callback");
+ mCallbackMap.remove(callback);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ public void unregister(@NonNull NfcVendorNciCallback callback) {
+ synchronized (this) {
+ if (!mCallbackMap.containsKey(callback) || !mIsRegistered) {
+ return;
+ }
+ if (mCallbackMap.size() == 1) {
+ try {
+ mAdapter.unregisterVendorExtensionCallback(this);
+ mIsRegistered = false;
+ mCallbackMap.remove(callback);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to unregister AdapterStateCallback with service");
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ mCallbackMap.remove(callback);
+ }
+ }
+ }
+
+ @Override
+ public void onVendorResponseReceived(int gid, int oid, @NonNull byte[] payload)
+ throws RemoteException {
+ synchronized (this) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ for (NfcVendorNciCallback callback : mCallbackMap.keySet()) {
+ Executor executor = mCallbackMap.get(callback);
+ executor.execute(() -> callback.onVendorNciResponse(gid, oid, payload));
+ }
+ } catch (RuntimeException ex) {
+ throw ex;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void onVendorNotificationReceived(int gid, int oid, @NonNull byte[] payload)
+ throws RemoteException {
+ synchronized (this) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ for (NfcVendorNciCallback callback : mCallbackMap.keySet()) {
+ Executor executor = mCallbackMap.get(callback);
+ executor.execute(() -> callback.onVendorNciNotification(gid, oid, payload));
+ }
+ } catch (RuntimeException ex) {
+ throw ex;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+}
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index 426c5aa..3254a39 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -105,6 +105,8 @@
private final ArrayList<String> mPollingLoopFilters;
+ private final Map<String, Boolean> mAutoTransact;
+
/**
* Whether this service should only be started when the device is unlocked.
*/
@@ -173,6 +175,7 @@
this.mStaticAidGroups = new HashMap<String, AidGroup>();
this.mDynamicAidGroups = new HashMap<String, AidGroup>();
this.mPollingLoopFilters = new ArrayList<String>();
+ this.mAutoTransact = new HashMap<String, Boolean>();
this.mOffHostName = offHost;
this.mStaticOffHostName = staticOffHost;
this.mOnHost = onHost;
@@ -287,6 +290,7 @@
mStaticAidGroups = new HashMap<String, AidGroup>();
mDynamicAidGroups = new HashMap<String, AidGroup>();
mPollingLoopFilters = new ArrayList<String>();
+ mAutoTransact = new HashMap<String, Boolean>();
mOnHost = onHost;
final int depth = parser.getDepth();
@@ -377,6 +381,10 @@
a.getString(com.android.internal.R.styleable.PollingLoopFilter_name)
.toUpperCase(Locale.ROOT);
mPollingLoopFilters.add(plf);
+ boolean autoTransact = a.getBoolean(
+ com.android.internal.R.styleable.PollingLoopFilter_autoTransact,
+ false);
+ mAutoTransact.put(plf, autoTransact);
a.recycle();
}
}
@@ -444,6 +452,17 @@
}
/**
+ * Returns whether this service would like to automatically transact for a given plf.
+ *
+ * @param plf the polling loop filter to query.
+ * @return {@code true} indicating to auto transact, {@code false} indicating to not.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public boolean getShouldAutoTransact(@NonNull String plf) {
+ return mAutoTransact.getOrDefault(plf.toUpperCase(Locale.ROOT), false);
+ }
+
+ /**
* Returns a consolidated list of AIDs with prefixes from the AID groups
* registered by this service. Note that if a service has both
* a static (manifest-based) AID group for a category and a dynamic
@@ -630,6 +649,21 @@
}
/**
+ * Add a Polling Loop Filter. Custom NFC polling frames that match this filter will cause the
+ * device to exit observe mode, just as if
+ * {@link android.nfc.NfcAdapter#setTransactionAllowed(boolean)} had been called with true,
+ * allowing transactions to proceed. The matching frame will also be delivered to
+ * {@link HostApduService#processPollingFrames(List)}.
+ *
+ * @param pollingLoopFilter this polling loop filter to add.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void addPollingLoopFilterToAutoTransact(@NonNull String pollingLoopFilter) {
+ mPollingLoopFilters.add(pollingLoopFilter.toUpperCase(Locale.ROOT));
+ mAutoTransact.put(pollingLoopFilter.toUpperCase(Locale.ROOT), true);
+ }
+
+ /**
* Remove a Polling Loop Filter. Custom NFC polling frames that match this filter will no
* longer be delivered to {@link HostApduService#processPollingFrames(List)}.
* @param pollingLoopFilter this polling loop filter to add.
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 01a4570..0a2619c 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -69,3 +69,10 @@
description: "Flag for NFC set discovery tech API"
bug: "300351519"
}
+
+flag {
+ name: "nfc_vendor_cmd"
+ namespace: "nfc"
+ description: "Enable NFC vendor command support"
+ bug: "289879306"
+}
diff --git a/packages/CrashRecovery/framework/Android.bp b/packages/CrashRecovery/framework/Android.bp
index b2af315..c0d93531 100644
--- a/packages/CrashRecovery/framework/Android.bp
+++ b/packages/CrashRecovery/framework/Android.bp
@@ -1,9 +1,55 @@
-filegroup {
+soong_config_module_type {
+ name: "platform_filegroup",
+ module_type: "filegroup",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "move_crashrecovery_files",
+ ],
+ properties: [
+ "srcs",
+ ],
+}
+
+platform_filegroup {
name: "framework-crashrecovery-sources",
srcs: [
"java/**/*.java",
"java/**/*.aidl",
],
+ soong_config_variables: {
+ // if the flag is enabled, then files would be moved to module
+ move_crashrecovery_files: {
+ srcs: [],
+ },
+ },
path: "java",
visibility: ["//frameworks/base:__subpackages__"],
}
+
+soong_config_module_type {
+ name: "module_filegroup",
+ module_type: "filegroup",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "move_crashrecovery_files",
+ ],
+ properties: [
+ "srcs",
+ ],
+}
+
+module_filegroup {
+ name: "framework-crashrecovery-module-sources",
+ srcs: [],
+ soong_config_variables: {
+ // if the flag is enabled, then files would be moved to module
+ move_crashrecovery_files: {
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.aidl",
+ ],
+ },
+ },
+ path: "java",
+ visibility: ["//packages/modules/CrashRecovery/framework"],
+}
diff --git a/packages/CrashRecovery/services/Android.bp b/packages/CrashRecovery/services/Android.bp
index 63e6c50..ab10b5a 100644
--- a/packages/CrashRecovery/services/Android.bp
+++ b/packages/CrashRecovery/services/Android.bp
@@ -1,13 +1,59 @@
-filegroup {
+soong_config_module_type {
+ name: "platform_filegroup",
+ module_type: "filegroup",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "move_crashrecovery_files",
+ ],
+ properties: [
+ "srcs",
+ ],
+}
+
+platform_filegroup {
name: "services-crashrecovery-sources",
srcs: [
"java/**/*.java",
"java/**/*.aidl",
":statslog-crashrecovery-java-gen",
],
+ soong_config_variables: {
+ // if the flag is enabled, then files would be moved to module
+ move_crashrecovery_files: {
+ srcs: [],
+ },
+ },
visibility: ["//frameworks/base:__subpackages__"],
}
+soong_config_module_type {
+ name: "module_filegroup",
+ module_type: "filegroup",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "move_crashrecovery_files",
+ ],
+ properties: [
+ "srcs",
+ ],
+}
+
+module_filegroup {
+ name: "services-crashrecovery-module-sources",
+ srcs: [],
+ soong_config_variables: {
+ // if the flag is enabled, then files would be moved to module
+ move_crashrecovery_files: {
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.aidl",
+ ":statslog-crashrecovery-java-gen",
+ ],
+ },
+ },
+ visibility: ["//packages/modules/CrashRecovery/service"],
+}
+
genrule {
name: "statslog-crashrecovery-java-gen",
tools: ["stats-log-api-gen"],
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index dffe4e2..5d03ef5 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -390,7 +390,8 @@
return;
}
- CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED, level);
+ CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED,
+ level, levelToString(level));
// Try our best to reset all settings possible, and once finished
// rethrow any exception that we encountered
Exception res = null;
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
index 0fa248d..9a2cf61 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
@@ -36,7 +36,7 @@
fun Intent.parseCancelUiRequest(packageManager: PackageManager): Request? =
this.cancelUiRequest?.let { cancelUiRequest ->
- val showCancel = cancelUiRequest.shouldShowCancellationUi().apply {
+ val showCancel = cancelUiRequest.shouldShowCancellationExplanation().apply {
Log.d(TAG, "Received UI cancel request, shouldShowCancellationUi: $this")
}
if (showCancel) {
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
index 4155b03..9242141 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
@@ -17,7 +17,7 @@
package com.android.credentialmanager.ktx
import android.content.Intent
-import android.credentials.selection.CancelUiRequest
+import android.credentials.selection.CancelSelectionRequest
import android.credentials.selection.Constants
import android.credentials.selection.CreateCredentialProviderData
import android.credentials.selection.GetCredentialProviderData
@@ -25,10 +25,10 @@
import android.credentials.selection.RequestInfo
import android.os.ResultReceiver
-val Intent.cancelUiRequest: CancelUiRequest?
+val Intent.cancelUiRequest: CancelSelectionRequest?
get() = this.extras?.getParcelable(
- CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
- CancelUiRequest::class.java
+ CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
+ CancelSelectionRequest::class.java
)
val Intent.requestInfo: RequestInfo?
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index bd9d2e6..30681f3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -18,7 +18,7 @@
import android.content.Context
import android.content.Intent
-import android.credentials.selection.CancelUiRequest
+import android.credentials.selection.CancelSelectionRequest
import android.credentials.selection.Constants
import android.credentials.selection.CreateCredentialProviderData
import android.credentials.selection.GetCredentialProviderData
@@ -295,10 +295,10 @@
}
/** Return the cancellation request if present. */
- fun getCancelUiRequest(intent: Intent): CancelUiRequest? {
+ fun getCancelUiRequest(intent: Intent): CancelSelectionRequest? {
return intent.extras?.getParcelable(
- CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
- CancelUiRequest::class.java
+ CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
+ CancelSelectionRequest::class.java
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index fa975aa..05aa548 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -131,7 +131,7 @@
// Cancellation was for a different request, don't cancel the current UI.
return Triple(true, false, null)
}
- val shouldShowCancellationUi = cancelUiRequest.shouldShowCancellationUi()
+ val shouldShowCancellationUi = cancelUiRequest.shouldShowCancellationExplanation()
Log.d(
Constants.LOG_TAG, "Received UI cancellation intent. Should show cancellation" +
" ui = $shouldShowCancellationUi")
diff --git a/packages/CredentialManager/wear/res/values/strings.xml b/packages/CredentialManager/wear/res/values/strings.xml
index 109644f..be7e448 100644
--- a/packages/CredentialManager/wear/res/values/strings.xml
+++ b/packages/CredentialManager/wear/res/values/strings.xml
@@ -21,11 +21,16 @@
<!-- Title of a screen prompting if the user would like to use their saved passkey.
[CHAR LIMIT=80] -->
<string name="use_passkey_title">Use passkey?</string>
- <!-- Title of a screen prompting if the user would like to use their saved password.
+ <!-- Title of a screen prompting if the user would like to use their saved passkey.
+[CHAR LIMIT=80] -->
+ <string name="use_sign_in_with_provider_title">Use your sign in for %1$s</string>
+ <!-- Title of a screen prompting if the user would like to sign in with provider
[CHAR LIMIT=80] -->
<string name="use_password_title">Use password?</string>
- <!-- Content description for the cancel button of a screen. [CHAR LIMIT=NONE] -->
- <string name="dialog_cancel_button_cd">Cancel</string>
- <!-- Content description for the OK button of a screen. [CHAR LIMIT=NONE] -->
- <string name="dialog_ok_button_cd">OK</string>
+ <!-- Content description for the dismiss button of a screen. [CHAR LIMIT=NONE] -->
+ <string name="dialog_dismiss_button">Dismiss</string>
+ <!-- Content description for the continue button of a screen. [CHAR LIMIT=NONE] -->
+ <string name="dialog_continue_button">Continue</string>
+ <!-- Content description for the sign in options button of a screen. [CHAR LIMIT=NONE] -->
+ <string name="dialog_sign_in_options_button">Sign-in Options</string>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
new file mode 100644
index 0000000..3297991
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.credentialmanager.ui.components
+
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material.Chip
+import androidx.wear.compose.material.ChipColors
+import androidx.wear.compose.material.ChipDefaults
+import androidx.wear.compose.material.Text
+import com.android.credentialmanager.R
+import com.android.credentialmanager.ui.components.CredentialsScreenChip.TOPPADDING
+
+@Composable
+fun CredentialsScreenChip(
+ label: String,
+ onClick: () -> Unit,
+ secondaryLabel: String? = null,
+ icon: Drawable? = null,
+ modifier: Modifier = Modifier,
+ colors: ChipColors = ChipDefaults.secondaryChipColors(),
+) {
+ val labelParam: (@Composable RowScope.() -> Unit) =
+ {
+ Text(
+ text = label,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = if (secondaryLabel != null) 1 else 2,
+ )
+ }
+
+ val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
+ secondaryLabel?.let {
+ {
+ Text(
+ text = secondaryLabel,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ )
+ }
+ }
+
+ val iconParam: (@Composable BoxScope.() -> Unit)? =
+ icon?.let {
+ {
+ ChipDefaults.IconSize
+ }
+ }
+
+ Chip(
+ label = labelParam,
+ onClick = onClick,
+ modifier = modifier,
+ secondaryLabel = secondaryLabelParam,
+ icon = iconParam,
+ colors = colors,
+ enabled = true,
+ )
+}
+
+@Preview
+@Composable
+fun CredentialsScreenChipPreview() {
+ CredentialsScreenChip(
+ label = "Elisa Beckett",
+ onClick = { },
+ secondaryLabel = "beckett_bakery@gmail.com",
+ icon = null,
+ modifier = Modifier
+ .clipToBounds()
+ .padding(top = 2.dp)
+ )
+}
+
+@Composable
+fun SignInOptionsChip(onClick: () -> Unit) {
+ CredentialsScreenChip(
+ label = stringResource(R.string.dialog_sign_in_options_button),
+ onClick = onClick,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = TOPPADDING)
+ )
+}
+
+@Preview
+@Composable
+fun SignInOptionsChipPreview() {
+ SignInOptionsChip({})
+}
+
+@Composable
+fun ContinueChip(onClick: () -> Unit) {
+ CredentialsScreenChip(
+ label = stringResource(R.string.dialog_continue_button),
+ onClick = onClick,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = TOPPADDING),
+ colors = ChipDefaults.primaryChipColors(),
+ )
+}
+
+@Preview
+@Composable
+fun ContinueChipPreview() {
+ ContinueChip({})
+}
+
+@Composable
+fun DismissChip(onClick: () -> Unit) {
+ CredentialsScreenChip(
+ label = stringResource(R.string.dialog_dismiss_button),
+ onClick = onClick,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = TOPPADDING),
+ )
+}
+
+@Preview
+@Composable
+fun DismissChipPreview() {
+ DismissChip({})
+}
+
+private object CredentialsScreenChip {
+ val TOPPADDING = 8.dp
+}
+
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/DialogButtonsRow.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/DialogButtonsRow.kt
deleted file mode 100644
index 5cb3c15..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/DialogButtonsRow.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://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.credentialmanager.ui.components
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Check
-import androidx.compose.material.icons.filled.Close
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material.ButtonDefaults
-import com.google.android.horologist.compose.material.Button
-import com.google.android.horologist.compose.tools.WearPreview
-import com.android.credentialmanager.R
-import com.google.android.horologist.annotations.ExperimentalHorologistApi
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun DialogButtonsRow(
- onCancelClick: () -> Unit,
- onOKClick: () -> Unit,
- modifier: Modifier = Modifier,
- cancelButtonIcon: ImageVector = Icons.Default.Close,
- okButtonIcon: ImageVector = Icons.Default.Check,
- cancelButtonContentDescription: String = stringResource(R.string.dialog_cancel_button_cd),
- okButtonContentDescription: String = stringResource(R.string.dialog_ok_button_cd),
-) {
- Row(
- modifier = modifier,
- horizontalArrangement = Arrangement.Center,
- ) {
- Button(
- imageVector = cancelButtonIcon,
- contentDescription = cancelButtonContentDescription,
- onClick = onCancelClick,
- colors = ButtonDefaults.secondaryButtonColors(),
- )
- Button(
- imageVector = okButtonIcon,
- contentDescription = okButtonContentDescription,
- onClick = onOKClick,
- modifier = Modifier.padding(start = 20.dp)
- )
- }
-}
-
-@WearPreview
-@Composable
-fun DialogButtonsRowPreview() {
- DialogButtonsRow(onCancelClick = {}, onOKClick = {})
-}
-
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt
index 8532783..e94dd68 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt
@@ -19,21 +19,12 @@
package com.android.credentialmanager.ui.screens.single
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
-import com.android.credentialmanager.R
-import com.android.credentialmanager.ui.components.AccountRow
-import com.android.credentialmanager.ui.components.DialogButtonsRow
-import com.android.credentialmanager.ui.components.SignInHeader
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumn
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
-import com.google.android.horologist.compose.layout.belowTimeTextPreview
-import com.google.android.horologist.compose.tools.WearPreview
@Composable
fun SingleAccountScreen(
@@ -51,33 +42,4 @@
item { accountContent() }
content()
}
-}
-
-@WearPreview
-@Composable
-fun SingleAccountScreenPreview() {
- SingleAccountScreen(
- headerContent = {
- SignInHeader(
- icon = R.drawable.passkey_icon,
- title = stringResource(R.string.use_passkey_title),
- )
- },
- accountContent = {
- AccountRow(
- name = "Elisa Beckett",
- email = "beckett_bakery@gmail.com",
- modifier = Modifier.padding(top = 10.dp)
- )
- },
- columnState = belowTimeTextPreview(),
- ) {
- item {
- DialogButtonsRow(
- onCancelClick = {},
- onOKClick = {},
- modifier = Modifier.padding(top = 10.dp)
- )
- }
- }
-}
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index c9b0230..9558bb0 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -25,20 +25,15 @@
import androidx.compose.ui.unit.dp
import com.android.credentialmanager.R
import com.android.credentialmanager.ui.components.AccountRow
-import com.android.credentialmanager.ui.components.DialogButtonsRow
import com.android.credentialmanager.ui.components.SignInHeader
import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
-import com.google.android.horologist.compose.layout.belowTimeTextPreview
-import com.google.android.horologist.compose.tools.WearPreview
@Composable
fun SinglePasskeyScreen(
name: String,
email: String,
- onCancelClick: () -> Unit,
- onOKClick: () -> Unit,
columnState: ScalingLazyColumnState,
modifier: Modifier = Modifier,
) {
@@ -60,24 +55,6 @@
modifier = modifier.padding(horizontal = 10.dp)
) {
item {
- DialogButtonsRow(
- onCancelClick = onCancelClick,
- onOKClick = onOKClick,
- modifier = Modifier.padding(top = 10.dp)
- )
}
}
}
-
-@WearPreview
-@Composable
-fun SinglePasskeyScreenPreview() {
- SinglePasskeyScreen(
- name = "Elisa Beckett",
- email = "beckett_bakery@gmail.com",
- onCancelClick = {},
- onOKClick = {},
- columnState = belowTimeTextPreview(),
- )
-}
-
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
index 9f971ae..5463612 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
@@ -33,15 +33,12 @@
import com.android.credentialmanager.R
import com.android.credentialmanager.TAG
import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
-import com.android.credentialmanager.ui.components.DialogButtonsRow
import com.android.credentialmanager.ui.components.PasswordRow
import com.android.credentialmanager.ui.components.SignInHeader
import com.android.credentialmanager.ui.model.PasswordUiModel
import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
-import com.google.android.horologist.compose.layout.belowTimeTextPreview
-import com.google.android.horologist.compose.tools.WearPreview
@Composable
fun SinglePasswordScreen(
@@ -63,8 +60,6 @@
is SinglePasswordScreenUiState.Loaded -> {
SinglePasswordScreen(
passwordUiModel = state.passwordUiModel,
- onCancelClick = viewModel::onCancelClick,
- onOKClick = viewModel::onOKClick,
columnState = columnState,
modifier = modifier
)
@@ -102,8 +97,6 @@
@Composable
fun SinglePasswordScreen(
passwordUiModel: PasswordUiModel,
- onCancelClick: () -> Unit,
- onOKClick: () -> Unit,
columnState: ScalingLazyColumnState,
modifier: Modifier = Modifier,
) {
@@ -124,23 +117,6 @@
modifier = modifier.padding(horizontal = 10.dp)
) {
item {
- DialogButtonsRow(
- onCancelClick = onCancelClick,
- onOKClick = onOKClick,
- modifier = Modifier.padding(top = 10.dp)
- )
}
}
}
-
-@WearPreview
-@Composable
-fun SinglePasswordScreenPreview() {
- SinglePasswordScreen(
- passwordUiModel = PasswordUiModel(email = "beckett_bakery@gmail.com"),
- onCancelClick = {},
- onOKClick = {},
- columnState = belowTimeTextPreview(),
- )
-}
-
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 09e0d61..bf69d3b 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 9a06229..f5af510 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/SettingsLib/Spa/spa/src/com/android/settingslib/spa/lifecycle/FlowExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/lifecycle/FlowExt.kt
new file mode 100644
index 0000000..1d3eb51
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/lifecycle/FlowExt.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.lifecycle
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import kotlinx.coroutines.flow.Flow
+
+@Composable
+fun <T> Flow<T>.collectAsCallbackWithLifecycle(): () -> T? {
+ val value by collectAsStateWithLifecycle(initialValue = null)
+ return { value }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/lifecycle/FlowExtTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/lifecycle/FlowExtTest.kt
new file mode 100644
index 0000000..de915ef
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/lifecycle/FlowExtTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.lifecycle
+
+import androidx.compose.material3.Text
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.waitUntilExists
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FlowExtTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun collectAsCallbackWithLifecycle() {
+ val flow = flowOf(TEXT)
+
+ composeTestRule.setContent {
+ val callback = flow.collectAsCallbackWithLifecycle()
+ Text(callback() ?: "")
+ }
+
+ composeTestRule.waitUntilExists(hasText(TEXT))
+ }
+
+ @Test
+ fun collectAsCallbackWithLifecycle_changed() {
+ val flow = MutableStateFlow(TEXT)
+
+ composeTestRule.setContent {
+ val callback = flow.collectAsCallbackWithLifecycle()
+ Text(callback() ?: "")
+ }
+ flow.value = NEW_TEXT
+
+ composeTestRule.waitUntilExists(hasText(NEW_TEXT))
+ }
+
+ private companion object {
+ const val TEXT = "Text"
+ const val NEW_TEXT = "New Text"
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index 9f33fcb..6e9bde4 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -16,7 +16,7 @@
package com.android.settingslib.spaprivileged.model.app
-import android.app.AppOpsManager;
+import android.app.AppOpsManager
import android.app.AppOpsManager.MODE_ALLOWED
import android.app.AppOpsManager.MODE_ERRORED
import android.app.AppOpsManager.Mode
@@ -24,14 +24,13 @@
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.os.UserHandle
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.map
import com.android.settingslib.spaprivileged.framework.common.appOpsManager
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
interface IAppOpsController {
- val mode: LiveData<Int>
- val isAllowed: LiveData<Boolean>
+ val mode: Flow<Int>
+ val isAllowed: Flow<Boolean>
get() = mode.map { it == MODE_ALLOWED }
fun setAllowed(allowed: Boolean)
@@ -48,9 +47,7 @@
) : IAppOpsController {
private val appOpsManager = context.appOpsManager
private val packageManager = context.packageManager
-
- override val mode: LiveData<Int>
- get() = _mode
+ override val mode = appOpsManager.opModeFlow(op, app)
override fun setAllowed(allowed: Boolean) {
val mode = if (allowed) MODE_ALLOWED else modeForNotAllowed
@@ -68,15 +65,7 @@
PackageManager.FLAG_PERMISSION_USER_SET,
UserHandle.getUserHandleForUid(app.uid))
}
- _mode.postValue(mode)
}
- @Mode override fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
-
- private val _mode =
- object : MutableLiveData<Int>() {
- override fun onActive() {
- postValue(getMode())
- }
- }
+ @Mode override fun getMode(): Int = appOpsManager.getOpMode(op, app)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepository.kt
new file mode 100644
index 0000000..0b5604e
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.app.AppOpsManager
+import android.content.pm.ApplicationInfo
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+fun AppOpsManager.getOpMode(op: Int, app: ApplicationInfo) =
+ checkOpNoThrow(op, app.uid, app.packageName)
+
+fun AppOpsManager.opModeFlow(op: Int, app: ApplicationInfo) =
+ opChangedFlow(op, app).map { getOpMode(op, app) }.flowOn(Dispatchers.Default)
+
+private fun AppOpsManager.opChangedFlow(op: Int, app: ApplicationInfo) = callbackFlow {
+ val listener = object : AppOpsManager.OnOpChangedListener {
+ override fun onOpChanged(op: String, packageName: String) {}
+
+ override fun onOpChanged(op: String, packageName: String, userId: Int) {
+ if (userId == app.userId) trySend(Unit)
+ }
+ }
+ startWatchingMode(op, app.packageName, listener)
+ trySend(Unit)
+
+ awaitClose { stopWatchingMode(listener) }
+}.conflate().flowOn(Dispatchers.Default)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index 25c3bc5..5db5eae 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -20,7 +20,7 @@
import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.livedata.observeAsState
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settingslib.spa.framework.util.asyncMapItem
import com.android.settingslib.spa.framework.util.filterItem
import com.android.settingslib.spaprivileged.model.app.AppOpsController
@@ -166,7 +166,7 @@
return { true }
}
- val mode = appOpsController.mode.observeAsState()
+ val mode = appOpsController.mode.collectAsStateWithLifecycle(initialValue = null)
return {
when (mode.value) {
null -> null
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt
new file mode 100644
index 0000000..97c7441
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.os.UserHandle
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.toListWithTimeout
+import com.android.settingslib.spaprivileged.framework.common.appOpsManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class AppOpsRepositoryTest {
+
+ private var listener: AppOpsManager.OnOpChangedListener? = null
+
+ private val mockAppOpsManager = mock<AppOpsManager> {
+ on {
+ checkOpNoThrow(AppOpsManager.OP_MANAGE_MEDIA, UID, PACKAGE_NAME)
+ } doReturn AppOpsManager.MODE_ERRORED
+
+ on {
+ startWatchingMode(eq(AppOpsManager.OP_MANAGE_MEDIA), eq(PACKAGE_NAME), any())
+ } doAnswer { listener = it.arguments[2] as AppOpsManager.OnOpChangedListener }
+ }
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { appOpsManager } doReturn mockAppOpsManager
+ }
+
+ @Test
+ fun getOpMode() {
+ val mode = context.appOpsManager.getOpMode(AppOpsManager.OP_MANAGE_MEDIA, APP)
+
+ assertThat(mode).isEqualTo(AppOpsManager.MODE_ERRORED)
+ }
+
+ @Test
+ fun opModeFlow() = runBlocking {
+ val flow = context.appOpsManager.opModeFlow(AppOpsManager.OP_MANAGE_MEDIA, APP)
+
+ val mode = flow.first()
+
+ assertThat(mode).isEqualTo(AppOpsManager.MODE_ERRORED)
+ }
+
+ @Test
+ fun opModeFlow_changed() = runBlocking {
+ val listDeferred = async {
+ context.appOpsManager.opModeFlow(AppOpsManager.OP_MANAGE_MEDIA, APP).toListWithTimeout()
+ }
+ delay(100)
+
+ mockAppOpsManager.stub {
+ on { checkOpNoThrow(AppOpsManager.OP_MANAGE_MEDIA, UID, PACKAGE_NAME) } doReturn
+ AppOpsManager.MODE_IGNORED
+ }
+ listener?.onOpChanged("", "", UserHandle.getUserId(UID))
+
+ assertThat(listDeferred.await()).contains(AppOpsManager.MODE_IGNORED)
+ }
+
+ private companion object {
+ const val UID = 110000
+ const val PACKAGE_NAME = "package.name"
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ uid = UID
+ }
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index d158a24..bb25cf3 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -21,7 +21,6 @@
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.lifecycle.MutableLiveData
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
@@ -330,7 +329,7 @@
private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController {
var setAllowedCalledWith: Boolean? = null
- override val mode = MutableLiveData(fakeMode)
+ override val mode = flowOf(fakeMode)
override fun setAllowed(allowed: Boolean) {
setAllowedCalledWith = allowed
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index f7f0673..09b1eaf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -9,6 +9,7 @@
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -24,6 +25,7 @@
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.core.graphics.drawable.IconCompat;
@@ -31,9 +33,12 @@
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.settingslib.widget.AdaptiveOutlineDrawable;
+import com.google.common.collect.ImmutableSet;
+
import java.io.IOException;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -47,6 +52,8 @@
public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
+ private static final Set<String> EXCLUSIVE_MANAGERS = ImmutableSet.of(
+ "com.google.android.gms.dck");
private static ErrorListener sErrorListener;
@@ -647,4 +654,66 @@
private static String generateExpressionWithTag(String tag, String value) {
return getTagStart(tag) + value + getTagEnd(tag);
}
+
+ /**
+ * Returns the BluetoothDevice's exclusive manager
+ * ({@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the
+ * given set, otherwise null.
+ */
+ @Nullable
+ private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) {
+ byte[] exclusiveManagerNameBytes = bluetoothDevice.getMetadata(
+ BluetoothDevice.METADATA_EXCLUSIVE_MANAGER);
+ if (exclusiveManagerNameBytes == null) {
+ Log.d(TAG, "Bluetooth device " + bluetoothDevice.getName()
+ + " doesn't have exclusive manager");
+ return null;
+ }
+ String exclusiveManagerName = new String(exclusiveManagerNameBytes);
+ return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName
+ : null;
+ }
+
+ /**
+ * Checks if given package is installed
+ */
+ private static boolean isPackageInstalled(Context context,
+ String packageName) {
+ PackageManager packageManager = context.getPackageManager();
+ try {
+ packageManager.getPackageInfo(packageName, 0);
+ return true;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.d(TAG, "Package " + packageName + " is not installed");
+ }
+ return false;
+ }
+
+ /**
+ * A BluetoothDevice is exclusively managed if
+ * 1) it has field {@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata.
+ * 2) the exclusive manager app name is in the allowlist.
+ * 3) the exclusive manager app is installed.
+ */
+ public static boolean isExclusivelyManagedBluetoothDevice(@NonNull Context context,
+ @NonNull BluetoothDevice bluetoothDevice) {
+ String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice);
+ if (exclusiveManagerName == null) {
+ return false;
+ }
+ if (!isPackageInstalled(context, exclusiveManagerName)) {
+ return false;
+ } else {
+ Log.d(TAG, "Found exclusively managed app " + exclusiveManagerName);
+ return true;
+ }
+ }
+
+ /**
+ * Return the allowlist for exclusive manager names.
+ */
+ @NonNull
+ public static Set<String> getExclusiveManagers() {
+ return EXCLUSIVE_MANAGERS;
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index f7ec80b..475a6d6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -17,6 +17,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -24,6 +26,8 @@
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.net.Uri;
@@ -49,6 +53,8 @@
private BluetoothDevice mBluetoothDevice;
@Mock
private AudioManager mAudioManager;
+ @Mock
+ private PackageManager mPackageManager;
private Context mContext;
private static final String STRING_METADATA = "string_metadata";
@@ -59,6 +65,7 @@
private static final String CONTROL_METADATA =
"<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + STRING_METADATA
+ "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
+ private static final String FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name";
@Before
public void setUp() {
@@ -372,4 +379,55 @@
assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
mAudioManager)).isEqualTo(false);
}
+
+ @Test
+ public void isExclusivelyManagedBluetoothDevice_isNotExclusivelyManaged_returnFalse() {
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+ null);
+
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+ mBluetoothDevice)).isEqualTo(false);
+ }
+
+ @Test
+ public void isExclusivelyManagedBluetoothDevice_isNotInAllowList_returnFalse() {
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+ FAKE_EXCLUSIVE_MANAGER_NAME.getBytes());
+
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+ mBluetoothDevice)).isEqualTo(false);
+ }
+
+ @Test
+ public void isExclusivelyManagedBluetoothDevice_packageNotInstalled_returnFalse()
+ throws Exception {
+ final String exclusiveManagerName =
+ BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
+ FAKE_EXCLUSIVE_MANAGER_NAME);
+
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+ exclusiveManagerName.getBytes());
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getPackageInfo(
+ exclusiveManagerName, 0);
+
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+ mBluetoothDevice)).isEqualTo(false);
+ }
+
+ @Test
+ public void isExclusivelyManagedBluetoothDevice_isExclusivelyManaged_returnTrue()
+ throws Exception {
+ final String exclusiveManagerName =
+ BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
+ FAKE_EXCLUSIVE_MANAGER_NAME);
+
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+ exclusiveManagerName.getBytes());
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0);
+
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+ mBluetoothDevice)).isEqualTo(true);
+ }
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 1e146a5..e4a762a 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 @@
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/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index edbc0b3..ecac5ee 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -14,11 +14,3 @@
bug: "311155098"
is_fixed_read_only: true
}
-
-flag {
- name: "configurable_font_scale_default"
- namespace: "large_screen_experiences_app_compat"
- description: "Whether the font_scale is read from a device dependent configuration file"
- bug: "319808237"
- is_fixed_read_only: true
-}
\ No newline at end of file
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 16de478..b58187d 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -185,6 +185,7 @@
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/Android.bp b/packages/SystemUI/Android.bp
index 900a2f8..d1a3571 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0050676..f877d7a 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/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index 0df9bac..d674b6c 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
index 3fc351c..64dcf6e 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index ba3026e..2ad7192 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -168,6 +168,9 @@
"Note that, even after this callback is called, we're waiting for all windows to finish "
" drawing."
bug: "295873557"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -389,6 +392,13 @@
}
flag {
+ name: "enable_contextual_tips"
+ description: "Enables showing contextual tips."
+ namespace: "systemui"
+ bug: "322891421"
+}
+
+flag {
name: "shaderlib_loading_effect_refactor"
namespace: "systemui"
description: "Extend shader library to provide the common loading effects."
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 872187a..c1125f0 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index d3f66e3..4cbc18c 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
diff --git a/packages/SystemUI/common/Android.bp b/packages/SystemUI/common/Android.bp
index 482776a..6fc13d7 100644
--- a/packages/SystemUI/common/Android.bp
+++ b/packages/SystemUI/common/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp
index 9a4347d..4f7a43e 100644
--- a/packages/SystemUI/compose/core/Android.bp
+++ b/packages/SystemUI/compose/core/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
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 0000000..846abf7e
--- /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/core/tests/Android.bp b/packages/SystemUI/compose/core/tests/Android.bp
index 8e9c586..6e7a142 100644
--- a/packages/SystemUI/compose/core/tests/Android.bp
+++ b/packages/SystemUI/compose/core/tests/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 4cc7332..51d2a03 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -30,12 +30,13 @@
import androidx.lifecycle.LifecycleOwner
import com.android.compose.theme.PlatformTheme
import com.android.compose.ui.platform.DensityAwareComposeView
+import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.composable.BouncerContent
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
-import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
+import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
@@ -129,8 +130,9 @@
return ComposeView(context).apply {
setContent {
PlatformTheme {
- DisplayCutoutProvider(
- displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets)
+ ScreenDecorProvider(
+ displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets),
+ screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
) {
SceneContainer(
viewModel = viewModel,
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 5ab2235..c12084d 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutoutProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
similarity index 70%
rename from packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutoutProvider.kt
rename to packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
index ed393c0..76bd4ec 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutoutProvider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
@@ -21,17 +21,28 @@
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
import kotlinx.coroutines.flow.StateFlow
/** The bounds and [CutoutLocation] of the current display. */
val LocalDisplayCutout = staticCompositionLocalOf { DisplayCutout() }
+/** The corner radius in px of the current display. */
+val LocalScreenCornerRadius = staticCompositionLocalOf { 0.dp }
+
@Composable
-fun DisplayCutoutProvider(
+fun ScreenDecorProvider(
displayCutout: StateFlow<DisplayCutout>,
+ screenCornerRadius: Float,
content: @Composable () -> Unit,
) {
val cutout by displayCutout.collectAsState()
-
- CompositionLocalProvider(LocalDisplayCutout provides cutout) { content() }
+ val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() }
+ CompositionLocalProvider(
+ LocalScreenCornerRadius provides screenCornerRadiusDp,
+ LocalDisplayCutout provides cutout
+ ) {
+ content()
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 0e08a19..3fb8254 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -51,11 +51,13 @@
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
@@ -63,10 +65,15 @@
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
+import com.android.compose.ui.util.lerp
+import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.notifications.ui.composable.Notifications.Form
+import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS
+import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
import com.android.systemui.scene.ui.composable.Gone
import com.android.systemui.scene.ui.composable.Shade
import com.android.systemui.shade.ui.composable.ShadeHeader
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder.SCRIM_CORNER_RADIUS
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import kotlin.math.roundToInt
@@ -77,6 +84,13 @@
val ShelfSpace = ElementKey("ShelfSpace")
}
+ // Expansion fraction thresholds (between 0-1f) at which the corresponding value should be
+ // at its maximum, given they are at their minimum value at expansion = 0f.
+ object TransitionThresholds {
+ const val EXPANSION_FOR_MAX_CORNER_RADIUS = 0.1f
+ const val EXPANSION_FOR_MAX_SCRIM_ALPHA = 0.3f
+ }
+
enum class Form {
HunFromTop,
Stack,
@@ -125,19 +139,19 @@
modifier: Modifier = Modifier,
) {
val density = LocalDensity.current
- val cornerRadius by viewModel.cornerRadiusDp.collectAsState()
+ val screenCornerRadius = LocalScreenCornerRadius.current
val expansionFraction by viewModel.expandFraction.collectAsState(0f)
val navBarHeight =
with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
- val statusBarHeight =
- with(density) { WindowInsets.systemBars.asPaddingValues().calculateTopPadding().toPx() }
- val displayCutoutHeight =
- with(density) { WindowInsets.displayCutout.asPaddingValues().calculateTopPadding().toPx() }
+ val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
+ val displayCutoutHeight = WindowInsets.displayCutout.asPaddingValues().calculateTopPadding()
val screenHeight =
- with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } +
- navBarHeight +
- maxOf(statusBarHeight, displayCutoutHeight)
+ with(density) {
+ (LocalConfiguration.current.screenHeightDp.dp +
+ maxOf(statusBarHeight, displayCutoutHeight))
+ .toPx()
+ } + navBarHeight
val contentHeight = viewModel.intrinsicContentHeight.collectAsState()
@@ -171,26 +185,53 @@
.collect { shouldCollapse -> if (shouldCollapse) scrimOffset.value = 0f }
}
- Box(modifier = modifier.element(Notifications.Elements.NotificationScrim)) {
+ Box(
+ modifier =
+ modifier
+ .element(Notifications.Elements.NotificationScrim)
+ .offset {
+ // if scrim is expanded while transitioning to Gone scene, increase the offset
+ // in step with the transition so that it is 0 when it completes.
+ if (
+ scrimOffset.value < 0 &&
+ layoutState.isTransitioning(from = Shade, to = Gone)
+ ) {
+ IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt())
+ } else {
+ IntOffset(x = 0, y = scrimOffset.value.roundToInt())
+ }
+ }
+ .graphicsLayer {
+ shape =
+ calculateCornerRadius(
+ screenCornerRadius,
+ { expansionFraction },
+ layoutState.isTransitioningBetween(Gone, Shade)
+ )
+ .let {
+ RoundedCornerShape(
+ topStart = it,
+ topEnd = it,
+ )
+ }
+ clip = true
+ }
+ ) {
+ // Creates a cutout in the background scrim in the shape of the notifications scrim.
+ // Only visible when notif scrim alpha < 1, during shade expansion.
Spacer(
modifier =
- Modifier.fillMaxSize()
- .graphicsLayer {
- shape = RoundedCornerShape(cornerRadius.dp)
- clip = true
- }
- .drawBehind { drawRect(Color.Black, blendMode = BlendMode.DstOut) }
+ Modifier.fillMaxSize().drawBehind {
+ drawRect(Color.Black, blendMode = BlendMode.DstOut)
+ }
)
Box(
modifier =
Modifier.fillMaxSize()
- .offset { IntOffset(0, scrimOffset.value.roundToInt()) }
.graphicsLayer {
- shape = RoundedCornerShape(cornerRadius.dp)
- clip = true
alpha =
if (layoutState.isTransitioningBetween(Gone, Shade)) {
- (expansionFraction / 0.3f).coerceAtMost(1f)
+ (expansionFraction / EXPANSION_FOR_MAX_SCRIM_ALPHA).coerceAtMost(1f)
} else 1f
}
.background(MaterialTheme.colorScheme.surface)
@@ -278,10 +319,10 @@
.onSizeChanged { size: IntSize ->
debugLog(viewModel) { "STACK onSizeChanged: size=$size" }
}
- .onPlaced { coordinates: LayoutCoordinates ->
+ .onGloballyPositioned { coordinates: LayoutCoordinates ->
viewModel.onContentTopChanged(coordinates.positionInWindow().y)
debugLog(viewModel) {
- "STACK onPlaced:" +
+ "STACK onGloballyPositioned:" +
" size=${coordinates.size}" +
" position=${coordinates.positionInWindow()}" +
" bounds=${coordinates.boundsInWindow()}"
@@ -310,6 +351,23 @@
}
}
+private fun calculateCornerRadius(
+ screenCornerRadius: Dp,
+ expansionFraction: () -> Float,
+ transitioning: Boolean,
+): Dp {
+ return if (transitioning) {
+ lerp(
+ start = screenCornerRadius.value,
+ stop = SCRIM_CORNER_RADIUS,
+ fraction = (expansionFraction() / EXPANSION_FOR_MAX_CORNER_RADIUS).coerceAtMost(1f),
+ )
+ .dp
+ } else {
+ SCRIM_CORNER_RADIUS.dp
+ }
+}
+
private inline fun debugLog(
viewModel: NotificationsPlaceholderViewModel,
msg: () -> Any,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 5531f9c..969dec3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -36,7 +36,6 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
@@ -47,11 +46,6 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.boundsInWindow
-import androidx.compose.ui.layout.onPlaced
-import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
@@ -62,7 +56,6 @@
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.scene.shared.model.SceneKey
@@ -122,8 +115,6 @@
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
- val cornerRadius by viewModel.notifications.cornerRadiusDp.collectAsState()
-
// TODO(b/280887232): implement the real UI.
Box(modifier = modifier.fillMaxSize()) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
@@ -155,9 +146,9 @@
// a background that extends to the edges.
Spacer(
modifier =
- Modifier.element(Shade.Elements.ScrimBackground)
+ Modifier.element(Shade.Elements.BackgroundScrim)
.fillMaxSize()
- .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
+ .background(MaterialTheme.colorScheme.scrim)
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@@ -242,32 +233,5 @@
}
}
}
- // Scrim with height 0 aligned to bottom of the screen to facilitate shared element
- // transition from Shade scene.
- Box(
- modifier =
- Modifier.element(Notifications.Elements.NotificationScrim)
- .fillMaxWidth()
- .height(0.dp)
- .graphicsLayer {
- shape = RoundedCornerShape(cornerRadius.dp)
- clip = true
- alpha = 1f
- }
- .background(MaterialTheme.colorScheme.surface)
- .align(Alignment.BottomCenter)
- .onPlaced { coordinates: LayoutCoordinates ->
- viewModel.notifications.onContentTopChanged(
- coordinates.positionInWindow().y
- )
- val boundsInWindow = coordinates.boundsInWindow()
- viewModel.notifications.onBoundsChanged(
- left = boundsInWindow.left,
- top = boundsInWindow.top,
- right = boundsInWindow.right,
- bottom = boundsInWindow.bottom,
- )
- }
- )
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 770d654..736ee1f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -16,18 +16,18 @@
package com.android.systemui.scene.ui.composable
-import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -63,6 +63,6 @@
override fun SceneScope.Content(
modifier: Modifier,
) {
- Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim))
+ Spacer(modifier.fillMaxSize())
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 0c2c519..1223ace 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -3,6 +3,7 @@
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.shade.ui.composable.ShadeHeader
@@ -10,5 +11,6 @@
spec = tween(durationMillis = 500)
fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContent) }
- translate(QuickSettings.Elements.Content, Edge.Top, true)
+ translate(QuickSettings.Elements.Content, y = -ShadeHeader.Dimensions.CollapsedHeight * .66f)
+ translate(Notifications.Elements.NotificationScrim, Edge.Top, false)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
index ebc343d..2d5cf5c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
@@ -10,9 +10,8 @@
fun TransitionBuilder.lockscreenToShadeTransition() {
spec = tween(durationMillis = 500)
- translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
fractionRange(end = 0.5f) {
- fade(Shade.Elements.ScrimBackground)
+ fade(Shade.Elements.BackgroundScrim)
translate(
QuickSettings.Elements.CollapsedGrid,
Edge.Top,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 497fe87..677df7e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -38,6 +38,7 @@
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
@@ -74,8 +75,8 @@
object Elements {
val QuickSettings = ElementKey("ShadeQuickSettings")
val MediaCarousel = ElementKey("ShadeMediaCarousel")
- val Scrim = ElementKey("ShadeScrim")
- val ScrimBackground = ElementKey("ShadeScrimBackground")
+ val BackgroundScrim =
+ ElementKey("ShadeBackgroundScrim", scenePicker = LowestZIndexScenePicker)
}
object Dimensions {
@@ -162,7 +163,9 @@
Box(
modifier =
- modifier.element(Shade.Elements.Scrim).background(MaterialTheme.colorScheme.scrim),
+ modifier
+ .element(Shade.Elements.BackgroundScrim)
+ .background(MaterialTheme.colorScheme.scrim),
)
Box {
Layout(
@@ -236,17 +239,7 @@
check(measurables[1].size == 1)
val quickSettingsPlaceable = measurables[0][0].measure(constraints)
-
- val notificationsMeasurable = measurables[1][0]
- val notificationsScrimMaxHeight =
- constraints.maxHeight - ShadeHeader.Dimensions.CollapsedHeight.roundToPx()
- val notificationsPlaceable =
- notificationsMeasurable.measure(
- constraints.copy(
- minHeight = notificationsScrimMaxHeight,
- maxHeight = notificationsScrimMaxHeight
- )
- )
+ val notificationsPlaceable = measurables[1][0].measure(constraints)
maxNotifScrimTop.value = quickSettingsPlaceable.height.toFloat()
diff --git a/packages/SystemUI/compose/features/tests/Android.bp b/packages/SystemUI/compose/features/tests/Android.bp
index c85cd7b..69b18c4 100644
--- a/packages/SystemUI/compose/features/tests/Android.bp
+++ b/packages/SystemUI/compose/features/tests/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp
index 81b5bd4..c399abc 100644
--- a/packages/SystemUI/customization/Android.bp
+++ b/packages/SystemUI/customization/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/log/Android.bp b/packages/SystemUI/log/Android.bp
index 2be22a6..2f1d354 100644
--- a/packages/SystemUI/log/Android.bp
+++ b/packages/SystemUI/log/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/monet/Android.bp b/packages/SystemUI/monet/Android.bp
index 507ea25..98f7ace 100644
--- a/packages/SystemUI/monet/Android.bp
+++ b/packages/SystemUI/monet/Android.bp
@@ -14,6 +14,7 @@
// limitations under the License.
//
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index c82688c..5f932f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -52,7 +52,7 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.SessionTracker
@@ -236,9 +236,7 @@
kosmos = testKosmos()
sceneInteractor = kosmos.sceneInteractor
- keyguardTransitionInteractor =
- KeyguardTransitionInteractorFactory.create(kosmos.testScope.backgroundScope)
- .keyguardTransitionInteractor
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
sceneTransitionStateFlow =
MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
sceneInteractor.setTransitionState(sceneTransitionStateFlow)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 0e257bc..55cfcc2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -16,44 +16,27 @@
package com.android.systemui.biometrics
-import android.os.Handler
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF
import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
-import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
-import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.util.time.SystemClock
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertFalse
@@ -64,9 +47,7 @@
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -77,70 +58,24 @@
@kotlinx.coroutines.ExperimentalCoroutinesApi
class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest :
UdfpsKeyguardViewLegacyControllerBaseTest() {
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
- private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
-
- @Mock private lateinit var bouncerLogger: TableLogBuffer
+ private val keyguardBouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
@Before
override fun setUp() {
allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
MockitoAnnotations.initMocks(this)
- keyguardBouncerRepository =
- KeyguardBouncerRepositoryImpl(
- FakeSystemClock(),
- testScope.backgroundScope,
- bouncerLogger,
- )
- transitionRepository = FakeKeyguardTransitionRepository()
super.setUp()
}
override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewControllerLegacy {
- mPrimaryBouncerInteractor =
- PrimaryBouncerInteractor(
- keyguardBouncerRepository,
- mock(BouncerView::class.java),
- mock(Handler::class.java),
- mKeyguardStateController,
- mock(KeyguardSecurityModel::class.java),
- mock(PrimaryBouncerCallbackInteractor::class.java),
- mock(FalsingCollector::class.java),
- mock(DismissCallbackRegistry::class.java),
- context,
- mKeyguardUpdateMonitor,
- FakeTrustRepository(),
- testScope.backgroundScope,
- mSelectedUserInteractor,
- mock(DeviceEntryFaceAuthInteractor::class.java),
- )
- mAlternateBouncerInteractor =
- AlternateBouncerInteractor(
- mock(StatusBarStateController::class.java),
- mock(KeyguardStateController::class.java),
- keyguardBouncerRepository,
- FakeFingerprintPropertyRepository(),
- mock(BiometricSettingsRepository::class.java),
- mock(SystemClock::class.java),
- mKeyguardUpdateMonitor,
- testScope.backgroundScope,
- )
- mKeyguardTransitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = transitionRepository,
- )
- .keyguardTransitionInteractor
- mUdfpsOverlayInteractor =
- UdfpsOverlayInteractor(
- context,
- mock(AuthController::class.java),
- mock(SelectedUserInteractor::class.java),
- testScope.backgroundScope,
- )
+ mPrimaryBouncerInteractor = kosmos.primaryBouncerInteractor
+ mAlternateBouncerInteractor = kosmos.alternateBouncerInteractor
+ mKeyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
+ mUdfpsOverlayInteractor = kosmos.udfpsOverlayInteractor
return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 6808f5d..b611e0a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -37,16 +37,11 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId.fakeInstanceId
import com.android.internal.logging.UiEventLogger
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
-import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
@@ -59,52 +54,46 @@
import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
-import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.display.data.repository.display
+import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.BiometricType
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.SessionTracker
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.FakeKeyguardStateController
+import com.android.systemui.statusbar.commandQueue
import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.captureMany
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.util.time.SystemClock
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -129,6 +118,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos().apply { this.commandQueue = this.fakeCommandQueue }
private lateinit var underTest: DeviceEntryFaceAuthRepositoryImpl
@Mock private lateinit var faceManager: FaceManager
@@ -136,7 +126,6 @@
@Mock private lateinit var sessionTracker: SessionTracker
@Mock private lateinit var uiEventLogger: UiEventLogger
@Mock private lateinit var dumpManager: DumpManager
- @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Captor
private lateinit var authenticationCallback: ArgumentCaptor<FaceManager.AuthenticationCallback>
@@ -151,11 +140,11 @@
@Captor
private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
- private lateinit var testDispatcher: TestDispatcher
+ private val testDispatcher = kosmos.testDispatcher
- private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
- private lateinit var testScope: TestScope
- private lateinit var fakeUserRepository: FakeUserRepository
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val testScope = kosmos.testScope
+ private val fakeUserRepository = kosmos.fakeUserRepository
private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?>
private lateinit var detectStatus: FlowValue<FaceDetectionStatus?>
private lateinit var authRunning: FlowValue<Boolean?>
@@ -163,88 +152,30 @@
private lateinit var lockedOut: FlowValue<Boolean?>
private lateinit var canFaceAuthRun: FlowValue<Boolean?>
private lateinit var authenticated: FlowValue<Boolean?>
- private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
- private lateinit var deviceEntryFingerprintAuthRepository:
- FakeDeviceEntryFingerprintAuthRepository
- private lateinit var trustRepository: FakeTrustRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var powerRepository: FakePowerRepository
- private lateinit var powerInteractor: PowerInteractor
- private lateinit var keyguardInteractor: KeyguardInteractor
- private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
- private lateinit var displayStateInteractor: DisplayStateInteractor
- private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
- private lateinit var displayRepository: FakeDisplayRepository
- private lateinit var fakeCommandQueue: FakeCommandQueue
+ private val biometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
+ private val deviceEntryFingerprintAuthRepository =
+ kosmos.fakeDeviceEntryFingerprintAuthRepository
+ private val trustRepository = kosmos.fakeTrustRepository
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val powerInteractor = kosmos.powerInteractor
+ private val keyguardInteractor = kosmos.keyguardInteractor
+ private val alternateBouncerInteractor = kosmos.alternateBouncerInteractor
+ private val displayStateInteractor = kosmos.displayStateInteractor
+ private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ private val displayRepository = kosmos.displayRepository
+ private val fakeCommandQueue = kosmos.fakeCommandQueue
+ private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
private lateinit var featureFlags: FakeFeatureFlags
- private lateinit var fakeFacePropertyRepository: FakeFacePropertyRepository
private var wasAuthCancelled = false
private var wasDetectCancelled = false
- private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
-
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- testDispatcher = StandardTestDispatcher()
- testScope = TestScope(testDispatcher)
- fakeUserRepository = FakeUserRepository()
fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser))
- biometricSettingsRepository = FakeBiometricSettingsRepository()
- deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
- trustRepository = FakeTrustRepository()
featureFlags = FakeFeatureFlags()
- powerRepository = FakePowerRepository()
- powerInteractor =
- PowerInteractorFactory.create(
- repository = powerRepository,
- )
- .powerInteractor
-
- val withDeps =
- KeyguardInteractorFactory.create(
- featureFlags = featureFlags,
- powerInteractor = powerInteractor,
- )
- keyguardInteractor = withDeps.keyguardInteractor
- keyguardRepository = withDeps.repository
- bouncerRepository = withDeps.bouncerRepository
-
- keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- keyguardTransitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = keyguardTransitionRepository,
- keyguardInteractor = keyguardInteractor,
- )
- .keyguardTransitionInteractor
-
- fakeCommandQueue = withDeps.commandQueue
-
- alternateBouncerInteractor =
- AlternateBouncerInteractor(
- bouncerRepository = bouncerRepository,
- fingerprintPropertyRepository = FakeFingerprintPropertyRepository(),
- biometricSettingsRepository = biometricSettingsRepository,
- systemClock = mock(SystemClock::class.java),
- keyguardStateController = FakeKeyguardStateController(),
- statusBarStateController = mock(StatusBarStateController::class.java),
- keyguardUpdateMonitor = keyguardUpdateMonitor,
- scope = testScope.backgroundScope,
- )
-
- displayRepository = FakeDisplayRepository()
- displayStateInteractor =
- DisplayStateInteractorImpl(
- applicationScope = testScope.backgroundScope,
- context = context,
- mainExecutor = FakeExecutor(FakeSystemClock()),
- displayStateRepository = FakeDisplayStateRepository(),
- displayRepository = displayRepository,
- )
-
bypassStateChangedListener =
KotlinArgumentCaptor(KeyguardBypassController.OnBypassStateChangedListener::class.java)
whenever(sessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(keyguardSessionId)
@@ -282,7 +213,6 @@
testScope.backgroundScope
)
- fakeFacePropertyRepository = FakeFacePropertyRepository()
return DeviceEntryFaceAuthRepositoryImpl(
mContext,
fmOverride,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index 98f0211..9d34903 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -18,23 +18,27 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.Intent
+import android.view.accessibility.accessibilityManagerWrapper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.logging.uiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -43,7 +47,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -51,27 +54,25 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardLongPressInteractorTest : SysuiTestCase() {
-
- @Mock private lateinit var logger: UiEventLogger
- @Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper
+ private val kosmos =
+ testKosmos().apply {
+ this.accessibilityManagerWrapper = mock<AccessibilityManagerWrapper>()
+ this.uiEventLogger = mock<UiEventLoggerFake>()
+ }
private lateinit var underTest: KeyguardLongPressInteractor
- private lateinit var testScope: TestScope
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ private val logger = kosmos.uiEventLogger
+ private val testScope = kosmos.testScope
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
overrideResource(R.bool.long_press_keyguard_customize_lockscreen_enabled, true)
- whenever(accessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenAnswer {
- it.arguments[0]
- }
-
- testScope = TestScope()
- keyguardRepository = FakeKeyguardRepository()
- keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+ whenever(kosmos.accessibilityManagerWrapper.getRecommendedTimeoutMillis(anyInt(), anyInt()))
+ .thenAnswer { it.arguments[0] }
runBlocking { createUnderTest() }
}
@@ -284,25 +285,22 @@
isRevampedWppFeatureEnabled: Boolean = true,
isOpenWppDirectlyEnabled: Boolean = false,
) {
+ // This needs to be re-created for each test outside of kosmos since the flag values are
+ // read during initialization to set up flows. Maybe there is a better way to handle that.
underTest =
KeyguardLongPressInteractor(
appContext = mContext,
scope = testScope.backgroundScope,
- transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = keyguardTransitionRepository,
- )
- .keyguardTransitionInteractor,
+ transitionInteractor = kosmos.keyguardTransitionInteractor,
repository = keyguardRepository,
logger = logger,
featureFlags =
- FakeFeatureFlags().apply {
+ kosmos.fakeFeatureFlagsClassic.apply {
set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, isLongPressFeatureEnabled)
set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, isOpenWppDirectlyEnabled)
},
broadcastDispatcher = fakeBroadcastDispatcher,
- accessibilityManager = accessibilityManager
+ accessibilityManager = kosmos.accessibilityManagerWrapper
)
setUpState()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index ce43d4e..9b302ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -19,23 +19,23 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.fakeLightRevealScrimRepository
import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealScrim
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.testKosmos
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
@@ -43,29 +43,22 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class LightRevealScrimInteractorTest : SysuiTestCase() {
- private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository()
+ val kosmos =
+ testKosmos().apply {
+ this.fakeLightRevealScrimRepository = Mockito.spy(FakeLightRevealScrimRepository())
+ }
- private val fakeLightRevealScrimRepository by lazy {
- Mockito.spy(FakeLightRevealScrimRepository())
- }
+ private val fakeLightRevealScrimRepository = kosmos.fakeLightRevealScrimRepository
- private val testScope = TestScope()
+ private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val testScope = kosmos.testScope
- private val keyguardTransitionInteractor by lazy {
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = fakeKeyguardTransitionRepository,
- )
- .keyguardTransitionInteractor
- }
-
- private lateinit var underTest: LightRevealScrimInteractor
+ private val underTest = kosmos.lightRevealScrimInteractor
private val reveal1 =
object : LightRevealEffect {
@@ -77,19 +70,6 @@
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {}
}
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- underTest =
- LightRevealScrimInteractor(
- keyguardTransitionInteractor,
- fakeLightRevealScrimRepository,
- testScope.backgroundScope,
- mock(),
- mock()
- )
- }
-
@Test
fun lightRevealEffect_doesNotChangeDuringKeyguardTransition() =
runTest(UnconfinedTestDispatcher()) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
similarity index 62%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index 84b2c4b..53b262b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -21,7 +21,11 @@
import android.graphics.Rect
import android.view.Display
import android.view.DisplayCutout
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.CameraProtectionInfo
+import com.android.systemui.SysUICutoutInformation
+import com.android.systemui.SysUICutoutProvider
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -38,30 +42,30 @@
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
+@RunWith(AndroidJUnit4::class)
@SmallTest
class StatusBarContentInsetsProviderTest : SysuiTestCase() {
- @Mock private lateinit var dc: DisplayCutout
- @Mock private lateinit var contextMock: Context
- @Mock private lateinit var display: Display
- private lateinit var configurationController: ConfigurationController
-
+ private val sysUICutout = mock<SysUICutoutInformation>()
+ private val dc = mock<DisplayCutout>()
+ private val contextMock = mock<Context>()
+ private val display = mock<Display>()
private val configuration = Configuration()
+ private lateinit var configurationController: ConfigurationController
+
@Before
fun setup() {
- MockitoAnnotations.initMocks(this)
- `when`(contextMock.display).thenReturn(display)
+ whenever(sysUICutout.cutout).thenReturn(dc)
+ whenever(contextMock.display).thenReturn(display)
context.ensureTestableResources()
- `when`(contextMock.resources).thenReturn(context.resources)
- `when`(contextMock.resources.configuration).thenReturn(configuration)
- `when`(contextMock.createConfigurationContext(any())).thenAnswer {
+ whenever(contextMock.resources).thenReturn(context.resources)
+ whenever(contextMock.resources.configuration).thenReturn(configuration)
+ whenever(contextMock.createConfigurationContext(any())).thenAnswer {
context.createConfigurationContext(it.arguments[0] as Configuration)
}
configurationController = ConfigurationControllerImpl(contextMock)
@@ -117,7 +121,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -161,7 +165,7 @@
}
@Test
- fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout() {
+ fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout_noCameraProtection() {
// GIVEN a device in portrait mode with width < height and a display cutout in the top-left
val screenBounds = Rect(0, 0, 1080, 2160)
val dcBounds = Rect(0, 0, 100, 100)
@@ -174,7 +178,7 @@
val dotWidth = 10
val statusBarContentHeight = 15
- `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
// THEN rotations which share a short side should use the greater value between rounded
// corner padding and the display cutout's size
@@ -187,7 +191,7 @@
var bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -208,7 +212,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -231,7 +235,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -253,7 +257,335 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+ }
+
+ @Test
+ fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout_withCameraProtection() {
+ // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+ val screenBounds = Rect(0, 0, 1080, 2160)
+ val dcBounds = Rect(0, 0, 100, 100)
+ val protectionBounds = Rect(10, 10, 110, 110)
+ val minLeftPadding = 20
+ val minRightPadding = 20
+ val sbHeightPortrait = 100
+ val sbHeightLandscape = 60
+ val currentRotation = ROTATION_NONE
+ val isRtl = false
+ val dotWidth = 10
+ val statusBarContentHeight = 15
+
+ val protectionInfo = mock<CameraProtectionInfo> {
+ whenever(this.cutoutBounds).thenReturn(protectionBounds)
+ }
+ whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+ // THEN rotations which share a short side should use the greater value between rounded
+ // corner padding, the display cutout's size, and the camera protections' size.
+ var targetRotation = ROTATION_NONE
+ var expectedBounds = Rect(protectionBounds.right,
+ 0,
+ screenBounds.right - minRightPadding,
+ sbHeightPortrait)
+
+ var bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ targetRotation = ROTATION_LANDSCAPE
+ expectedBounds = Rect(protectionBounds.bottom,
+ 0,
+ screenBounds.height() - minRightPadding,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // THEN the side that does NOT share a short side with the display cutout ignores the
+ // display cutout bounds
+ targetRotation = ROTATION_UPSIDE_DOWN
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.width() - minRightPadding,
+ sbHeightPortrait)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // Phone in portrait, seascape (rot_270) bounds
+ targetRotation = ROTATION_SEASCAPE
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.height() - protectionBounds.bottom - dotWidth,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+ }
+
+ @Test
+ fun testCalculateInsetsForRotationWithRotatedResources_topRightCutout_noCameraProtection() {
+ // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+ val screenBounds = Rect(0, 0, 1000, 2000)
+ val dcBounds = Rect(900, 0, 1000, 100)
+ val minLeftPadding = 20
+ val minRightPadding = 20
+ val sbHeightPortrait = 100
+ val sbHeightLandscape = 60
+ val currentRotation = ROTATION_NONE
+ val isRtl = false
+ val dotWidth = 10
+ val statusBarContentHeight = 15
+
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+ // THEN rotations which share a short side should use the greater value between rounded
+ // corner padding and the display cutout's size
+ var targetRotation = ROTATION_NONE
+ var expectedBounds = Rect(minLeftPadding,
+ 0,
+ dcBounds.left - dotWidth,
+ sbHeightPortrait)
+
+ var bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ targetRotation = ROTATION_LANDSCAPE
+ expectedBounds = Rect(dcBounds.height(),
+ 0,
+ screenBounds.height() - minRightPadding,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // THEN the side that does NOT share a short side with the display cutout ignores the
+ // display cutout bounds
+ targetRotation = ROTATION_UPSIDE_DOWN
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.width() - minRightPadding,
+ sbHeightPortrait)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // Phone in portrait, seascape (rot_270) bounds
+ targetRotation = ROTATION_SEASCAPE
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.height() - dcBounds.height() - dotWidth,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+ }
+
+ @Test
+ fun testCalculateInsetsForRotationWithRotatedResources_topRightCutout_withCameraProtection() {
+ // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+ val screenBounds = Rect(0, 0, 1000, 2000)
+ val dcBounds = Rect(900, 0, 1000, 100)
+ val protectionBounds = Rect(890, 10, 990, 110)
+ val minLeftPadding = 20
+ val minRightPadding = 20
+ val sbHeightPortrait = 100
+ val sbHeightLandscape = 60
+ val currentRotation = ROTATION_NONE
+ val isRtl = false
+ val dotWidth = 10
+ val statusBarContentHeight = 15
+
+ val protectionInfo = mock<CameraProtectionInfo> {
+ whenever(this.cutoutBounds).thenReturn(protectionBounds)
+ }
+ whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+ // THEN rotations which share a short side should use the greater value between rounded
+ // corner padding, the display cutout's size, and the camera protections' size.
+ var targetRotation = ROTATION_NONE
+ var expectedBounds = Rect(minLeftPadding,
+ 0,
+ protectionBounds.left - dotWidth,
+ sbHeightPortrait)
+
+ var bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ targetRotation = ROTATION_LANDSCAPE
+ expectedBounds = Rect(protectionBounds.bottom,
+ 0,
+ screenBounds.height() - minRightPadding,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // THEN the side that does NOT share a short side with the display cutout ignores the
+ // display cutout bounds
+ targetRotation = ROTATION_UPSIDE_DOWN
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.width() - minRightPadding,
+ sbHeightPortrait)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // Phone in portrait, seascape (rot_270) bounds
+ targetRotation = ROTATION_SEASCAPE
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.height() - protectionBounds.bottom - dotWidth,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -273,7 +605,7 @@
val bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation = ROTATION_NONE,
targetRotation = ROTATION_NONE,
- displayCutout = dc,
+ sysUICutout = sysUICutout,
maxBounds = Rect(0, 0, 1080, 2160),
statusBarHeight = 100,
minLeft = 0,
@@ -293,7 +625,7 @@
val bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation = ROTATION_NONE,
targetRotation = ROTATION_NONE,
- displayCutout = dc,
+ sysUICutout = sysUICutout,
maxBounds = Rect(0, 0, 1080, 2160),
statusBarHeight = 100,
minLeft = 0,
@@ -321,6 +653,7 @@
val screenBounds = Rect(0, 0, 1080, 2160)
// cutout centered at the top
val dcBounds = Rect(490, 0, 590, 100)
+ val protectionBounds = Rect(480, 10, 600, 90)
val minLeftPadding = 20
val minRightPadding = 20
val sbHeightPortrait = 100
@@ -330,7 +663,11 @@
val dotWidth = 10
val statusBarContentHeight = 15
- `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+ val protectionInfo = mock<CameraProtectionInfo> {
+ whenever(this.cutoutBounds).thenReturn(protectionBounds)
+ }
+ whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
// THEN only the landscape/seascape rotations should avoid the cutout area because of the
// potential letterboxing
@@ -343,7 +680,7 @@
var bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -364,7 +701,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -385,7 +722,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -406,7 +743,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -528,7 +865,7 @@
val dotWidth = 10
val statusBarContentHeight = 15
- `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
// THEN left should be set to the display cutout width, and right should use the minRight
val targetRotation = ROTATION_NONE
@@ -540,7 +877,7 @@
val bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -557,7 +894,7 @@
fun testDisplayChanged_returnsUpdatedInsets() {
// GIVEN: get insets on the first display and switch to the second display
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -576,7 +913,7 @@
// GIVEN: get insets on the first display, switch to the second display,
// get insets and switch back
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
val firstDisplayInsetsFirstCall = provider
@@ -602,7 +939,7 @@
configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
configurationController.onConfigurationChanged(configuration)
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
val listener = object : StatusBarContentInsetsChangedListener {
var triggered = false
@@ -624,7 +961,7 @@
fun onDensityOrFontScaleChanged_listenerNotified() {
configuration.densityDpi = 12
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
val listener = object : StatusBarContentInsetsChangedListener {
var triggered = false
@@ -645,7 +982,7 @@
@Test
fun onThemeChanged_listenerNotified() {
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
val listener = object : StatusBarContentInsetsChangedListener {
var triggered = false
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index 9063a02..682a68f 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/plugin/ExamplePlugin/Android.bp b/packages/SystemUI/plugin/ExamplePlugin/Android.bp
index 66951b5..f6fa6a5 100644
--- a/packages/SystemUI/plugin/ExamplePlugin/Android.bp
+++ b/packages/SystemUI/plugin/ExamplePlugin/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/plugin_core/Android.bp b/packages/SystemUI/plugin_core/Android.bp
index b7e1545..521c019 100644
--- a/packages/SystemUI/plugin_core/Android.bp
+++ b/packages/SystemUI/plugin_core/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
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 0000000..6028769
--- /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 95c7778c..cae9d6b 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 6e541a7..ce09385 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 7a83070..65c69f7 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -542,6 +542,9 @@
<string translatable="false" name="config_protectedCameraId"></string>
<!-- Physical ID for the camera of outer display that needs extra protection -->
<string translatable="false" name="config_protectedPhysicalCameraId"></string>
+ <!-- Unique ID of the outer display that contains the camera that needs protection. -->
+ <string translatable="false" name="config_protectedScreenUniqueId"></string>
+
<!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
<string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>
@@ -550,6 +553,8 @@
<string translatable="false" name="config_protectedInnerCameraId"></string>
<!-- Physical ID for the camera of inner display that needs extra protection -->
<string translatable="false" name="config_protectedInnerPhysicalCameraId"></string>
+ <!-- Unique ID of the inner display that contains the camera that needs protection. -->
+ <string translatable="false" name="config_protectedInnerScreenUniqueId"></string>
<!-- Comma-separated list of packages to exclude from camera protection e.g.
"com.android.systemui,com.android.xyz" -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 33bdca3..51012a4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -318,9 +318,6 @@
notification panel collapses -->
<dimen name="shelf_appear_translation">42dp</dimen>
- <!-- Vertical translation of pulsing notification animations -->
- <dimen name="pulsing_notification_appear_translation">10dp</dimen>
-
<!-- The amount the content shifts upwards when transforming into the shelf -->
<dimen name="shelf_transform_content_shift">32dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8971859..f0cbe7a 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/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 6bf4906..6e611fe 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
index bbab4de..6314bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
@@ -24,4 +24,5 @@
val physicalCameraId: String?,
val cutoutProtectionPath: Path,
val cutoutBounds: Rect,
+ val displayUniqueId: String?,
)
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
index 8fe9389..6cee28b 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
@@ -25,15 +25,21 @@
import javax.inject.Inject
import kotlin.math.roundToInt
-class CameraProtectionLoader @Inject constructor(private val context: Context) {
+interface CameraProtectionLoader {
+ fun loadCameraProtectionInfoList(): List<CameraProtectionInfo>
+}
- fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> {
+class CameraProtectionLoaderImpl @Inject constructor(private val context: Context) :
+ CameraProtectionLoader {
+
+ override fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> {
val list = mutableListOf<CameraProtectionInfo>()
val front =
loadCameraProtectionInfo(
R.string.config_protectedCameraId,
R.string.config_protectedPhysicalCameraId,
- R.string.config_frontBuiltInDisplayCutoutProtection
+ R.string.config_frontBuiltInDisplayCutoutProtection,
+ R.string.config_protectedScreenUniqueId,
)
if (front != null) {
list.add(front)
@@ -42,7 +48,8 @@
loadCameraProtectionInfo(
R.string.config_protectedInnerCameraId,
R.string.config_protectedInnerPhysicalCameraId,
- R.string.config_innerBuiltInDisplayCutoutProtection
+ R.string.config_innerBuiltInDisplayCutoutProtection,
+ R.string.config_protectedInnerScreenUniqueId,
)
if (inner != null) {
list.add(inner)
@@ -53,7 +60,8 @@
private fun loadCameraProtectionInfo(
cameraIdRes: Int,
physicalCameraIdRes: Int,
- pathRes: Int
+ pathRes: Int,
+ displayUniqueIdRes: Int,
): CameraProtectionInfo? {
val logicalCameraId = context.getString(cameraIdRes)
if (logicalCameraId.isNullOrEmpty()) {
@@ -70,11 +78,13 @@
computed.right.roundToInt(),
computed.bottom.roundToInt()
)
+ val displayUniqueId = context.getString(displayUniqueIdRes)
return CameraProtectionInfo(
logicalCameraId,
physicalCameraId,
protectionPath,
- protectionBounds
+ protectionBounds,
+ displayUniqueId
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt
new file mode 100644
index 0000000..58680a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface CameraProtectionModule {
+
+ @Binds fun cameraProtectionLoaderImpl(impl: CameraProtectionLoaderImpl): CameraProtectionLoader
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt b/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt
new file mode 100644
index 0000000..fc0b97e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.view.DisplayCutout
+
+data class SysUICutoutInformation(
+ val cutout: DisplayCutout,
+ val cameraProtection: CameraProtectionInfo?
+)
diff --git a/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
new file mode 100644
index 0000000..aad9341
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.content.Context
+import android.view.DisplayCutout
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class SysUICutoutProvider
+@Inject
+constructor(
+ private val context: Context,
+ private val cameraProtectionLoader: CameraProtectionLoader,
+) {
+
+ private val cameraProtectionList by lazy {
+ cameraProtectionLoader.loadCameraProtectionInfoList()
+ }
+
+ fun cutoutInfoForCurrentDisplay(): SysUICutoutInformation? {
+ val display = context.display
+ val displayCutout: DisplayCutout = display.cutout ?: return null
+ val displayUniqueId: String? = display.uniqueId
+ if (displayUniqueId.isNullOrEmpty()) {
+ return SysUICutoutInformation(displayCutout, cameraProtection = null)
+ }
+ val cameraProtection: CameraProtectionInfo? =
+ cameraProtectionList.firstOrNull { it.displayUniqueId == displayUniqueId }
+ return SysUICutoutInformation(displayCutout, cameraProtection)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index 4a06ae9..0538e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -21,6 +21,7 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY;
import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE;
import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
import static com.android.systemui.accessibility.floatingmenu.MenuFadeEffectInfoKt.DEFAULT_FADE_EFFECT_IS_ENABLED;
@@ -46,7 +47,6 @@
import androidx.annotation.NonNull;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Prefs;
@@ -182,7 +182,7 @@
}
void loadMenuTargetFeatures(OnInfoReady<List<AccessibilityTarget>> callback) {
- callback.onReady(getTargets(mContext, ShortcutConstants.UserShortcutType.SOFTWARE));
+ callback.onReady(getTargets(mContext, ACCESSIBILITY_BUTTON));
}
void loadMenuSizeType(OnInfoReady<Integer> callback) {
@@ -223,7 +223,7 @@
private void onTargetFeaturesChanged() {
mSettingsContentsCallback.onTargetFeaturesChanged(
- getTargets(mContext, ShortcutConstants.UserShortcutType.SOFTWARE));
+ getTargets(mContext, ACCESSIBILITY_BUTTON));
}
private Position getStartPosition() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index f2e9531..a883c00 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility.floatingmenu;
import static android.view.WindowInsets.Type.ime;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static androidx.core.view.WindowInsetsCompat.Type;
@@ -63,7 +64,6 @@
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto;
@@ -154,7 +154,7 @@
final List<ComponentName> hardwareKeyShortcutComponents =
mAccessibilityManager.getAccessibilityShortcutTargets(
- ShortcutConstants.UserShortcutType.HARDWARE)
+ ACCESSIBILITY_SHORTCUT_KEY)
.stream()
.map(ComponentName::unflattenFromString)
.toList();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index 92eeace..904d5898 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.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 @@
@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 efcbd47..28fd9a9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -27,6 +27,7 @@
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.BootCompleteCache;
import com.android.systemui.BootCompleteCacheImpl;
+import com.android.systemui.CameraProtectionModule;
import com.android.systemui.accessibility.AccessibilityModule;
import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
import com.android.systemui.appops.dagger.AppOpsModule;
@@ -177,6 +178,7 @@
BouncerInteractorModule.class,
BouncerRepositoryModule.class,
BouncerViewModule.class,
+ CameraProtectionModule.class,
ClipboardOverlayModule.class,
ClockRegistryModule.class,
CommunalModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 53c81e5..e18e463 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -109,7 +109,7 @@
* This animation will take place entirely within the Launcher window. We can safely unlock the
* device, end remote animations, etc. even if this is still running.
*/
-const val LAUNCHER_ICONS_ANIMATION_DURATION_MS = 633L
+const val LAUNCHER_ICONS_ANIMATION_DURATION_MS = 1633L
/**
* How long to wait for the shade to get out of the way before starting the canned unlock animation.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index deb70b7..1da0a0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -26,7 +26,6 @@
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
@@ -36,7 +35,6 @@
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
-import dagger.Lazy
import java.util.UUID
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -64,7 +62,7 @@
private val shadeRepository: ShadeRepository,
private val powerInteractor: PowerInteractor,
private val glanceableHubTransitions: GlanceableHubTransitions,
- inWindowLauncherUnlockAnimationInteractor: Lazy<InWindowLauncherUnlockAnimationInteractor>,
+ private val swipeToDismissInteractor: SwipeToDismissInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.LOCKSCREEN,
@@ -102,49 +100,7 @@
return@map null
}
- true // TODO(b/278086361): Implement continuous swipe to unlock.
- }
- .onStart {
- // Default to null ("don't care, use a reasonable default").
- emit(null)
- }
- .distinctUntilChanged()
-
- /**
- * The surface behind view params to use for the transition from LOCKSCREEN, or null if we don't
- * care and should use a reasonable default.
- */
- val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
- combine(
- transitionInteractor.startedKeyguardTransitionStep,
- transitionInteractor.transitionStepsFromState(KeyguardState.LOCKSCREEN),
- inWindowLauncherUnlockAnimationInteractor
- .get()
- .transitioningToGoneWithInWindowAnimation,
- ) { startedStep, fromLockscreenStep, transitioningToGoneWithInWindowAnimation ->
- if (startedStep.to != KeyguardState.GONE) {
- // Only LOCKSCREEN -> GONE has specific surface params (for the unlock
- // animation).
- return@combine null
- } else if (transitioningToGoneWithInWindowAnimation) {
- // If we're prepared for the in-window unlock, we're going to play an animation
- // in the window. Make it fully visible.
- KeyguardSurfaceBehindModel(
- alpha = 1f,
- )
- } else if (fromLockscreenStep.value > 0.5f) {
- // Start the animation once we're 50% transitioned to GONE.
- KeyguardSurfaceBehindModel(
- animateFromAlpha = 0f,
- alpha = 1f,
- animateFromTranslationY = 500f,
- translationY = 0f
- )
- } else {
- KeyguardSurfaceBehindModel(
- alpha = 0f,
- )
- }
+ true // Make the surface visible during LS -> GONE transitions.
}
.onStart {
// Default to null ("don't care, use a reasonable default").
@@ -325,6 +281,13 @@
private fun listenForLockscreenToGoneDragging() {
if (KeyguardWmStateRefactor.isEnabled) {
+ // When the refactor is enabled, we no longer use isKeyguardGoingAway.
+ scope.launch {
+ swipeToDismissInteractor.dismissFling.collect { _ ->
+ startTransitionTo(KeyguardState.GONE)
+ }
+ }
+
return
}
@@ -332,6 +295,7 @@
keyguardInteractor.isKeyguardGoingAway
.sample(startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
+ KeyguardWmStateRefactor.assertInLegacyMode()
val (isKeyguardGoingAway, lastStartedStep) = pair
if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
startTransitionTo(KeyguardState.GONE)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractor.kt
index e7d74a5..8ec831c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractor.kt
@@ -23,7 +23,6 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.shared.system.smartspace.SmartspaceState
-import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -52,10 +51,7 @@
val transitioningToGoneWithInWindowAnimation: StateFlow<Boolean> =
transitionInteractor
.isInTransitionToState(KeyguardState.GONE)
- .sample(repository.launcherActivityClass, ::Pair)
- .map { (isTransitioningToGone, launcherActivityClass) ->
- isTransitioningToGone && isActivityClassUnderneath(launcherActivityClass)
- }
+ .map { transitioningToGone -> transitioningToGone && isLauncherUnderneath() }
.stateIn(scope, SharingStarted.Eagerly, false)
/**
@@ -91,11 +87,11 @@
}
/**
- * Whether an activity with the given [activityClass] name is currently underneath the
- * lockscreen (it's at the top of the activity task stack).
+ * Whether the Launcher is currently underneath the lockscreen (it's at the top of the activity
+ * task stack).
*/
- private fun isActivityClassUnderneath(activityClass: String?): Boolean {
- return activityClass?.let {
+ fun isLauncherUnderneath(): Boolean {
+ return repository.launcherActivityClass.value?.let {
activityManager.runningTask?.topActivity?.className?.equals(it)
}
?: false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index 922baa3..8784723 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -16,75 +16,75 @@
package com.android.systemui.keyguard.domain.interactor
+import android.content.Context
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor.Companion.isSurfaceVisible
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.util.kotlin.toPx
+import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
+
+/**
+ * Distance over which the surface behind the keyguard is animated in during a Y-translation
+ * animation.
+ */
+const val SURFACE_TRANSLATION_Y_DISTANCE_DP = 250
@SysUISingleton
class KeyguardSurfaceBehindInteractor
@Inject
constructor(
private val repository: KeyguardSurfaceBehindRepository,
- private val fromLockscreenInteractor: FromLockscreenTransitionInteractor,
- private val fromPrimaryBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+ context: Context,
transitionInteractor: KeyguardTransitionInteractor,
+ inWindowLauncherUnlockAnimationInteractor: Lazy<InWindowLauncherUnlockAnimationInteractor>,
+ swipeToDismissInteractor: SwipeToDismissInteractor,
) {
-
- @OptIn(ExperimentalCoroutinesApi::class)
+ /**
+ * The view params to use for the surface. These params describe the alpha/translation values to
+ * apply, as well as animation parameters if necessary.
+ */
val viewParams: Flow<KeyguardSurfaceBehindModel> =
- transitionInteractor.isInTransitionToAnyState
- .flatMapLatest { isInTransition ->
- if (!isInTransition) {
- defaultParams
- } else {
- combine(
- transitionSpecificViewParams,
- defaultParams,
- ) { transitionParams, defaultParams ->
- transitionParams ?: defaultParams
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ transitionInteractor.currentKeyguardState,
+ ) { startedStep, currentState ->
+ // If we're in transition to GONE, special unlock animation params apply.
+ if (startedStep.to == KeyguardState.GONE && currentState != KeyguardState.GONE) {
+ if (inWindowLauncherUnlockAnimationInteractor.get().isLauncherUnderneath()) {
+ // The Launcher icons have their own translation/alpha animations during the
+ // in-window animation. We'll just make the surface visible and let Launcher
+ // do its thing.
+ return@combine KeyguardSurfaceBehindModel(
+ alpha = 1f,
+ )
+ } else {
+ // Otherwise, animate a surface in via alpha/translation, and apply the
+ // swipe velocity (if available) to the translation spring.
+ return@combine KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 1f,
+ animateFromTranslationY =
+ SURFACE_TRANSLATION_Y_DISTANCE_DP.toPx(context).toFloat(),
+ translationY = 0f,
+ startVelocity = swipeToDismissInteractor.dismissFling.value?.velocity
+ ?: 0f,
+ )
}
}
+
+ // Default to the visibility of the current state, with no animations.
+ KeyguardSurfaceBehindModel(alpha = if (isSurfaceVisible(currentState)) 1f else 0f)
}
.distinctUntilChanged()
val isAnimatingSurface = repository.isAnimatingSurface
- private val defaultParams =
- transitionInteractor.finishedKeyguardState.map { state ->
- KeyguardSurfaceBehindModel(
- alpha =
- if (WindowManagerLockscreenVisibilityInteractor.isSurfaceVisible(state)) 1f
- else 0f
- )
- }
-
- /**
- * View params provided by the transition interactor for the most recently STARTED transition.
- * This is used to run transition-specific animations on the surface.
- *
- * If null, there are no transition-specific view params needed for this transition and we will
- * use a reasonable default.
- */
- @OptIn(ExperimentalCoroutinesApi::class)
- private val transitionSpecificViewParams: Flow<KeyguardSurfaceBehindModel?> =
- transitionInteractor.startedKeyguardTransitionStep.flatMapLatest { startedStep ->
- when (startedStep.from) {
- KeyguardState.LOCKSCREEN -> fromLockscreenInteractor.surfaceBehindModel
- KeyguardState.PRIMARY_BOUNCER -> fromPrimaryBouncerInteractor.surfaceBehindModel
- // Return null for other states, where no transition specific params are needed.
- else -> flowOf(null)
- }
- }
-
fun setAnimatingSurface(animating: Boolean) {
repository.setAnimatingSurface(animating)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
new file mode 100644
index 0000000..86e4115
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.kotlin.Utils.Companion.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Handles logic around the swipe to dismiss gesture, where the user swipes up on the dismissable
+ * lockscreen to unlock the device.
+ */
+@SysUISingleton
+class SwipeToDismissInteractor
+@Inject
+constructor(
+ @Background backgroundScope: CoroutineScope,
+ shadeRepository: ShadeRepository,
+ transitionInteractor: KeyguardTransitionInteractor,
+ keyguardInteractor: KeyguardInteractor,
+) {
+ /**
+ * Emits a [FlingInfo] whenever a swipe to dismiss gesture has started a fling animation on the
+ * lockscreen while it's dismissable.
+ *
+ * This value is collected by [FromLockscreenTransitionInteractor] to start a transition from
+ * LOCKSCREEN -> GONE, and by [KeyguardSurfaceBehindInteractor] to match the surface remote
+ * animation's velocity to the fling velocity, if applicable.
+ */
+ val dismissFling =
+ shadeRepository.currentFling
+ .sample(
+ transitionInteractor.startedKeyguardState,
+ keyguardInteractor.isKeyguardDismissible
+ )
+ .filter { (flingInfo, startedState, keyguardDismissable) ->
+ flingInfo != null &&
+ !flingInfo.expand &&
+ startedState == KeyguardState.LOCKSCREEN &&
+ keyguardDismissable
+ }
+ .map { (flingInfo, _) -> flingInfo }
+ .stateIn(backgroundScope, SharingStarted.Eagerly, null)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
index 7fb5cfd..aad8a4b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
@@ -44,6 +44,9 @@
* running, in which case we'll animate from the current value to [translationY].
*/
val animateFromTranslationY: Float = translationY,
+
+ /** Velocity with which to start the Y-translation spring animation. */
+ val startVelocity: Float = 0f,
) {
fun willAnimateAlpha(): Boolean {
return animateFromAlpha != alpha
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
index 8587022..fb6efd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
@@ -67,8 +67,8 @@
SpringAnimation(animatedTranslationY).apply {
spring =
SpringForce().apply {
- stiffness = 200f
- dampingRatio = 1f
+ stiffness = 275f
+ dampingRatio = 0.98f
}
addUpdateListener { _, _, _ -> applyToSurfaceBehind() }
addEndListener { _, _, _, _ ->
@@ -84,7 +84,7 @@
private var animatedAlpha = 0f
private var alphaAnimator =
ValueAnimator.ofFloat(0f, 1f).apply {
- duration = 500
+ duration = 150
interpolator = Interpolators.ALPHA_IN
addUpdateListener {
animatedAlpha = it.animatedValue as Float
@@ -162,6 +162,7 @@
// If the spring isn't running yet, set the start value. Otherwise, respect the
// current position.
animatedTranslationY.value = viewParams.animateFromTranslationY
+ translateYSpring.setStartVelocity(viewParams.startVelocity)
}
translateYSpring.animateToFinalPosition(viewParams.translationY)
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 16dfc21..47df021 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.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 @@
}
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/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 7d13397..dfe41eb 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -56,7 +56,6 @@
import androidx.annotation.NonNull;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.systemui.Dumpable;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -381,7 +380,7 @@
// permission
final List<String> a11yButtonTargets =
mAccessibilityManager.getAccessibilityShortcutTargets(
- ShortcutConstants.UserShortcutType.SOFTWARE);
+ AccessibilityManager.ACCESSIBILITY_BUTTON);
final int requestingServices = a11yButtonTargets.size();
clickable = requestingServices >= 1;
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 53f287b..720120b 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 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 @@
@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 @@
}
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 @@
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 @@
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 88863cb..a474868 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.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 @@
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 0000000..f487258
--- /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 e051df4..80f11f1 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.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 @@
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 b5a1313..b355d2d 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.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 @@
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 @@
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 @@
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 @@
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 @@
.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 @@
*/
@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 @@
@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 @@
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 @@
@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 @@
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 @@
.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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
* @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 0000000..fdb1eb6a
--- /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/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 06ca3af..e219bcc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -165,6 +165,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.shared.model.WakefulnessModel;
import com.android.systemui.res.R;
+import com.android.systemui.shade.data.repository.FlingInfo;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.shade.transition.ShadeTransitionController;
@@ -2083,6 +2084,7 @@
mKeyguardStateController.notifyPanelFlingStart(!expand /* flingingToDismiss */);
setClosingWithAlphaFadeout(!expand && !isKeyguardShowing() && getFadeoutAlpha() == 1.0f);
mNotificationStackScrollLayoutController.setPanelFlinging(true);
+ mShadeRepository.setCurrentFling(new FlingInfo(expand, vel));
if (target == mExpandedHeight && mOverExpansion == 0.0f) {
// We're at the target and didn't fling and there's no overshoot
onFlingEnd(false /* cancelled */);
@@ -2199,6 +2201,7 @@
mShadeLog.d("onFlingEnd called"); // TODO(b/277909752): remove log when bug is fixed
// expandImmediate should be always reset at the end of animation
mQsController.setExpandImmediate(false);
+ mShadeRepository.setCurrentFling(null);
}
private boolean isInContentBounds(float x, float y) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FlingInfo.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FlingInfo.kt
new file mode 100644
index 0000000..d7f96e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FlingInfo.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.shade.data.repository
+
+/**
+ * Information about a fling on the shade: whether we're flinging expanded or collapsed, and the
+ * velocity of the touch gesture that started the fling (if applicable).
+ */
+data class FlingInfo(
+ val expand: Boolean,
+ val velocity: Float = 0f,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 2445bdb..e5ff977 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -29,6 +29,15 @@
*/
val qsExpansion: StateFlow<Float>
+ /** Amount shade has expanded with regard to the UDFPS location */
+ val udfpsTransitionToFullShadeProgress: StateFlow<Float>
+
+ /**
+ * Information about the currently running fling animation, or null if no fling animation is
+ * running.
+ */
+ val currentFling: StateFlow<FlingInfo?>
+
/**
* The amount the lockscreen shade has dragged down by the user, [0-1]. 0 means fully collapsed,
* 1 means fully expanded. Value resets to 0 when the user finishes dragging.
@@ -132,15 +141,18 @@
@Deprecated("Use ShadeInteractor instead")
fun setLegacyLockscreenShadeTracking(tracking: Boolean)
- /** Amount shade has expanded with regard to the UDFPS location */
- val udfpsTransitionToFullShadeProgress: StateFlow<Float>
-
/** The amount QS has expanded without notifications */
fun setQsExpansion(qsExpansion: Float)
fun setUdfpsTransitionToFullShadeProgress(progress: Float)
/**
+ * Sets the [FlingInfo] of the currently animating fling. If [info] is null, no fling is
+ * animating.
+ */
+ fun setCurrentFling(info: FlingInfo?)
+
+ /**
* Set the amount the shade has dragged down by the user, [0-1]. 0 means fully collapsed, 1
* means fully expanded.
*/
@@ -168,6 +180,9 @@
override val udfpsTransitionToFullShadeProgress: StateFlow<Float> =
_udfpsTransitionToFullShadeProgress.asStateFlow()
+ private val _currentFling: MutableStateFlow<FlingInfo?> = MutableStateFlow(null)
+ override val currentFling: StateFlow<FlingInfo?> = _currentFling.asStateFlow()
+
private val _legacyShadeExpansion = MutableStateFlow(0f)
@Deprecated("Use ShadeInteractor.shadeExpansion instead")
override val legacyShadeExpansion: StateFlow<Float> = _legacyShadeExpansion.asStateFlow()
@@ -247,11 +262,6 @@
_qsExpansion.value = qsExpansion
}
- @Deprecated("Should only be called by NPVC and tests")
- override fun setLegacyShadeExpansion(expandedFraction: Float) {
- _legacyShadeExpansion.value = expandedFraction
- }
-
override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) {
_lockscreenShadeExpansion.value = lockscreenShadeExpansion
}
@@ -260,6 +270,15 @@
_udfpsTransitionToFullShadeProgress.value = progress
}
+ override fun setCurrentFling(info: FlingInfo?) {
+ _currentFling.value = info
+ }
+
+ @Deprecated("Should only be called by NPVC and tests")
+ override fun setLegacyShadeExpansion(expandedFraction: Float) {
+ _legacyShadeExpansion.value = expandedFraction
+ }
+
companion object {
private const val TAG = "ShadeRepository"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
index 783488af..4e40888 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
@@ -37,11 +37,12 @@
) {
suspend fun bind(view: NotificationIconContainer) {
viewModel.icons.bindIcons(
- view,
- configuration,
- systemBarUtilsState,
+ logTag = "shelf",
+ view = view,
+ configuration = configuration,
+ systemBarUtilsState = systemBarUtilsState,
notifyBindingFailures = { failureTracker.shelfFailures = it },
- viewStore,
+ viewStore = viewStore,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index f375ebc..de3a626 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -72,11 +72,12 @@
val iconColors: StateFlow<NotificationIconColors> =
viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) }.stateIn(this)
viewModel.icons.bindIcons(
- view,
- configuration,
- systemBarUtilsState,
+ logTag = "statusbar",
+ view = view,
+ configuration = configuration,
+ systemBarUtilsState = systemBarUtilsState,
notifyBindingFailures = { failureTracker.statusBarFailures = it },
- viewStore,
+ viewStore = viewStore,
) { _, sbiv ->
StatusBarIconViewBinder.bindIconColors(
sbiv,
@@ -124,11 +125,12 @@
val tintAlpha = viewModel.tintAlpha.stateIn(this)
val animsEnabled = viewModel.areIconAnimationsEnabled.stateIn(this)
viewModel.icons.bindIcons(
- view,
- configuration,
- systemBarUtilsState,
+ logTag = "aod",
+ view = view,
+ configuration = configuration,
+ systemBarUtilsState = systemBarUtilsState,
notifyBindingFailures = { failureTracker.aodFailures = it },
- viewStore,
+ viewStore = viewStore,
) { _, sbiv ->
coroutineScope {
launch { StatusBarIconViewBinder.bindColor(sbiv, color) }
@@ -176,6 +178,7 @@
* view is to be unbound.
*/
suspend fun Flow<NotificationIconsViewData>.bindIcons(
+ logTag: String,
view: NotificationIconContainer,
configuration: ConfigurationState,
systemBarUtilsState: SystemBarUtilsState,
@@ -197,7 +200,7 @@
}
.stateIn(this)
try {
- bindIcons(view, layoutParams, notifyBindingFailures, viewStore, bindIcon)
+ bindIcons(logTag, view, layoutParams, notifyBindingFailures, viewStore, bindIcon)
} finally {
// Detach everything so that child SBIVs don't hold onto a reference to the container.
view.detachAllIcons()
@@ -205,6 +208,7 @@
}
private suspend fun Flow<NotificationIconsViewData>.bindIcons(
+ logTag: String,
view: NotificationIconContainer,
layoutParams: StateFlow<FrameLayout.LayoutParams>,
notifyBindingFailures: (Collection<String>) -> Unit,
@@ -214,7 +218,7 @@
val failedBindings = mutableSetOf<String>()
val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>()
var prevIcons = NotificationIconsViewData()
- collectTracingEach("NIC#bindIcons") { iconsData: NotificationIconsViewData ->
+ collectTracingEach({ "NIC($logTag)#bindIcons" }) { iconsData: NotificationIconsViewData ->
val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons)
prevIcons = iconsData
@@ -249,7 +253,7 @@
if (this !== view) {
Log.wtf(
TAG,
- "StatusBarIconView($notifKey) has an unexpected parent",
+ "[$logTag] SBIV($notifKey) has an unexpected parent",
)
}
// If the container was re-inflated and re-bound, then SBIVs might still
@@ -266,7 +270,9 @@
sbiv,
launch {
launch {
- layoutParams.collectTracingEach("SBIV#bindLayoutParams") {
+ layoutParams.collectTracingEach(
+ tag = { "[$logTag] SBIV#bindLayoutParams" },
+ ) {
if (it != sbiv.layoutParams) {
sbiv.layoutParams = it
}
@@ -307,7 +313,12 @@
val childCount = view.childCount
for (i in 0 until childCount) {
val actual = view.getChildAt(i)
- val expected = expectedChildren[i]
+ val expected = expectedChildren.getOrNull(i)
+ if (expected == null) {
+ Log.wtf(TAG, "[$logTag] Unexpected child $actual")
+ view.removeView(actual)
+ continue
+ }
if (actual === expected) {
continue
}
@@ -379,3 +390,11 @@
tag: String,
crossinline collector: (T) -> Unit,
) = collect { traceSection(tag) { collector(it) } }
+
+private suspend inline fun <T> Flow<T>.collectTracingEach(
+ noinline tag: () -> String,
+ crossinline collector: (T) -> Unit,
+) {
+ val lazyTag = lazy(mode = LazyThreadSafetyMode.PUBLICATION, tag)
+ collect { traceSection({ lazyTag.value }) { collector(it) } }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
index 99177c2..195fe78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row
+import android.widget.flags.Flags.notifLinearlayoutOptimized
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import javax.inject.Inject
@@ -31,6 +32,7 @@
precomputedTextViewFactory: PrecomputedTextViewFactory,
bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
callLayoutSetDataAsyncFactory: CallLayoutSetDataAsyncFactory,
+ optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory
) : NotifRemoteViewsFactoryContainer {
override val factories: Set<NotifRemoteViewsFactory> = buildSet {
add(precomputedTextViewFactory)
@@ -40,5 +42,8 @@
if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
add(callLayoutSetDataAsyncFactory)
}
+ if (notifLinearlayoutOptimized()) {
+ add(optimizedLinearLayoutFactory)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationOptimizedLinearLayoutFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationOptimizedLinearLayoutFactory.kt
new file mode 100644
index 0000000..f231fbc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationOptimizedLinearLayoutFactory.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.LinearLayout
+import com.android.internal.widget.NotificationOptimizedLinearLayout
+import javax.inject.Inject
+
+class NotificationOptimizedLinearLayoutFactory @Inject constructor() : NotifRemoteViewsFactory {
+ override fun instantiate(
+ row: ExpandableNotificationRow,
+ @NotificationRowContentBinder.InflationFlag layoutType: Int,
+ parent: View?,
+ name: String,
+ context: Context,
+ attrs: AttributeSet
+ ): View? {
+ return when (name) {
+ LinearLayout::class.java.name,
+ LinearLayout::class.java.simpleName -> NotificationOptimizedLinearLayout(context, attrs)
+ else -> null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 5e0110b..0a11eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1445,12 +1445,12 @@
if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
}
- final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
// TODO(b/322228881): Clean up scene container vs legacy behavior in NSSL
if (SceneContainerFlag.isEnabled()) {
// stackY should be driven by scene container, not NSSL
mAmbientState.setStackY(mTopPadding);
} else {
+ final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
mAmbientState.setStackY(stackY);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index a3e0941..b38d619 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -59,9 +59,6 @@
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
public static final int ANIMATION_DURATION_FOLD_TO_AOD =
AnimatableClockView.ANIMATION_DURATION_FOLD_TO_AOD;
- public static final int ANIMATION_DURATION_PULSE_APPEAR =
- KeyguardSliceView.DEFAULT_ANIM_DURATION;
- public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240;
public static final int ANIMATION_DURATION_PRIORITY_CHANGE = 500;
public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
@@ -70,7 +67,6 @@
private static final int MAX_STAGGER_COUNT = 5;
private final int mGoToFullShadeAppearingTranslation;
- private final int mPulsingAppearingTranslation;
@VisibleForTesting
float mHeadsUpAppearStartAboveScreen;
private final ExpandableViewState mTmpState = new ExpandableViewState();
@@ -102,9 +98,6 @@
mGoToFullShadeAppearingTranslation =
hostLayout.getContext().getResources().getDimensionPixelSize(
R.dimen.go_to_full_shade_appearing_translation);
- mPulsingAppearingTranslation =
- hostLayout.getContext().getResources().getDimensionPixelSize(
- R.dimen.pulsing_notification_appear_translation);
mHeadsUpAppearStartAboveScreen = hostLayout.getContext().getResources()
.getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
mAnimationProperties = new AnimationProperties() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index 0197264..311ba83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -28,9 +28,6 @@
/** The bounds of the notification stack in the current scene. */
val stackBounds = MutableStateFlow(NotificationContainerBounds())
- /** The corner radius of the notification stack, in dp. */
- val cornerRadiusDp = MutableStateFlow(32f)
-
/**
* The height in px of the contents of notification stack. Depending on the number of
* notifications, this can exceed the space available on screen to show notifications, at which
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 8307397..9984ba9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -34,9 +34,6 @@
/** The bounds of the notification stack in the current scene. */
val stackBounds: StateFlow<NotificationContainerBounds> = repository.stackBounds.asStateFlow()
- /** The corner radius of the notification stack, in dp. */
- val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow()
-
/**
* The height in px of the contents of notification stack. Depending on the number of
* notifications, this can exceed the space available on screen to show notifications, at which
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index 50b08b8..814146c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -31,6 +31,7 @@
/** Binds the shared notification container to its view-model. */
object NotificationStackAppearanceViewBinder {
+ const val SCRIM_CORNER_RADIUS = 32f
@JvmStatic
fun bind(
@@ -49,8 +50,8 @@
bounds.top.roundToInt(),
bounds.right.roundToInt(),
bounds.bottom.roundToInt(),
- viewModel.cornerRadiusDp.value.dpToPx(context),
- viewModel.cornerRadiusDp.value.dpToPx(context),
+ SCRIM_CORNER_RADIUS.dpToPx(context),
+ 0,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index fe5bdd4..f3d0d2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -136,8 +136,12 @@
.collect { y -> controller.setTranslationY(y) }
}
- launch {
- viewModel.expansionAlpha.collect { controller.setMaxAlphaForExpansion(it) }
+ if (!sceneContainerFlags.isEnabled()) {
+ launch {
+ viewModel.expansionAlpha.collect {
+ controller.setMaxAlphaForExpansion(it)
+ }
+ }
}
launch {
viewModel.glanceableHubAlpha.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index 56ff7f9..bdf1a64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -74,9 +74,6 @@
/** The bounds of the notification stack in the current scene. */
val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds
- /** The corner radius of the notification stack, in dp. */
- val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp
-
/** The y-coordinate in px of top of the contents of the notification stack. */
val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 8b723da..65d9c9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -28,7 +28,6 @@
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
/**
* ViewModel used by the Notification placeholders inside the scene container to update the
@@ -74,9 +73,6 @@
interactor.setStackBounds(notificationContainerBounds)
}
- /** The corner radius of the placeholder, in dp. */
- val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp
-
/**
* The height in px of the contents of notification stack. Depending on the number of
* notifications, this can exceed the space available on screen to show notifications, at which
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 877bd7c..e84b7a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -44,6 +44,8 @@
import com.android.app.tracing.traceSection
import com.android.systemui.BottomMarginCommand
import com.android.systemui.StatusBarInsetsCommand
+import com.android.systemui.SysUICutoutInformation
+import com.android.systemui.SysUICutoutProvider
import com.android.systemui.statusbar.commandline.CommandRegistry
import java.io.PrintWriter
import java.lang.Math.max
@@ -69,6 +71,7 @@
val configurationController: ConfigurationController,
val dumpManager: DumpManager,
val commandRegistry: CommandRegistry,
+ val sysUICutoutProvider: SysUICutoutProvider,
) : CallbackController<StatusBarContentInsetsChangedListener>,
ConfigurationController.ConfigurationListener,
Dumpable {
@@ -176,7 +179,8 @@
*/
fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets =
traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
- val displayCutout = checkNotNull(context.display).cutout
+ val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+ val displayCutout = sysUICutout?.cutout
val key = getCacheKey(rotation, displayCutout)
val screenBounds = context.resources.configuration.windowConfiguration.maxBounds
@@ -187,7 +191,7 @@
val width = point.logicalWidth(rotation)
val area = insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
- rotation, displayCutout, getResourcesForRotation(rotation, context), key)
+ rotation, sysUICutout, getResourcesForRotation(rotation, context), key)
Insets.of(area.left, area.top, /* right= */ width - area.right, /* bottom= */ 0)
}
@@ -212,10 +216,11 @@
fun getStatusBarContentAreaForRotation(
@Rotation rotation: Int
): Rect {
- val displayCutout = checkNotNull(context.display).cutout
+ val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+ val displayCutout = sysUICutout?.cutout
val key = getCacheKey(rotation, displayCutout)
return insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
- rotation, displayCutout, getResourcesForRotation(rotation, context), key)
+ rotation, sysUICutout, getResourcesForRotation(rotation, context), key)
}
/**
@@ -228,18 +233,18 @@
private fun getAndSetCalculatedAreaForRotation(
@Rotation targetRotation: Int,
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
rotatedResources: Resources,
key: CacheKey
): Rect {
- return getCalculatedAreaForRotation(displayCutout, targetRotation, rotatedResources)
+ return getCalculatedAreaForRotation(sysUICutout, targetRotation, rotatedResources)
.also {
insetsCache.put(key, it)
}
}
private fun getCalculatedAreaForRotation(
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
@Rotation targetRotation: Int,
rotatedResources: Resources
): Rect {
@@ -271,7 +276,7 @@
return calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- displayCutout,
+ sysUICutout,
context.resources.configuration.windowConfiguration.maxBounds,
SystemBarUtils.getStatusBarHeightForRotation(context, targetRotation),
minLeft,
@@ -415,7 +420,7 @@
fun calculateInsetsForRotationWithRotatedResources(
@Rotation currentRotation: Int,
@Rotation targetRotation: Int,
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
maxBounds: Rect,
statusBarHeight: Int,
minLeft: Int,
@@ -434,7 +439,7 @@
val rotZeroBounds = getRotationZeroDisplayBounds(maxBounds, currentRotation)
return getStatusBarContentBounds(
- displayCutout,
+ sysUICutout,
statusBarHeight,
rotZeroBounds.right,
rotZeroBounds.bottom,
@@ -470,7 +475,7 @@
* rotation
*/
private fun getStatusBarContentBounds(
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
sbHeight: Int,
width: Int,
height: Int,
@@ -489,19 +494,17 @@
val logicalDisplayWidth = if (targetRotation.isHorizontal()) height else width
- val cutoutRects = displayCutout?.boundingRects
- if (cutoutRects == null || cutoutRects.isEmpty()) {
- return Rect(minLeft,
- insetTop,
- logicalDisplayWidth - minRight,
- sbHeight)
+ val cutoutRects = sysUICutout?.cutout?.boundingRects
+ if (cutoutRects.isNullOrEmpty()) {
+ return Rect(minLeft, insetTop, logicalDisplayWidth - minRight, sbHeight)
}
- val relativeRotation = if (currentRotation - targetRotation < 0) {
- currentRotation - targetRotation + 4
- } else {
- currentRotation - targetRotation
- }
+ val relativeRotation =
+ if (currentRotation - targetRotation < 0) {
+ currentRotation - targetRotation + 4
+ } else {
+ currentRotation - targetRotation
+ }
// Size of the status bar window for the given rotation relative to our exact rotation
val sbRect = sbRect(relativeRotation, sbHeight, Pair(cWidth, cHeight))
@@ -509,19 +512,26 @@
var leftMargin = minLeft
var rightMargin = minRight
for (cutoutRect in cutoutRects) {
+ val protectionRect = sysUICutout.cameraProtection?.cutoutBounds
+ val actualCutoutRect =
+ if (protectionRect?.intersects(cutoutRect) == true) {
+ rectUnion(cutoutRect, protectionRect)
+ } else {
+ cutoutRect
+ }
// There is at most one non-functional area per short edge of the device. So if the status
// bar doesn't share a short edge with the cutout, we can ignore its insets because there
// will be no letter-boxing to worry about
- if (!shareShortEdge(sbRect, cutoutRect, cWidth, cHeight)) {
+ if (!shareShortEdge(sbRect, actualCutoutRect, cWidth, cHeight)) {
continue
}
- if (cutoutRect.touchesLeftEdge(relativeRotation, cWidth, cHeight)) {
- var logicalWidth = cutoutRect.logicalWidth(relativeRotation)
+ if (actualCutoutRect.touchesLeftEdge(relativeRotation, cWidth, cHeight)) {
+ var logicalWidth = actualCutoutRect.logicalWidth(relativeRotation)
if (isRtl) logicalWidth += dotWidth
leftMargin = max(logicalWidth, leftMargin)
- } else if (cutoutRect.touchesRightEdge(relativeRotation, cWidth, cHeight)) {
- var logicalWidth = cutoutRect.logicalWidth(relativeRotation)
+ } else if (actualCutoutRect.touchesRightEdge(relativeRotation, cWidth, cHeight)) {
+ var logicalWidth = actualCutoutRect.logicalWidth(relativeRotation)
if (!isRtl) logicalWidth += dotWidth
rightMargin = max(rightMargin, logicalWidth)
}
@@ -532,6 +542,11 @@
return Rect(leftMargin, insetTop, logicalDisplayWidth - rightMargin, sbHeight)
}
+private fun rectUnion(first: Rect, second: Rect) = Rect(first).apply { union(second) }
+
+private fun Rect.intersects(other: Rect): Boolean =
+ intersects(other.left, other.top, other.right, other.bottom)
+
/*
* Returns the inset top of the status bar.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 4c83ca2..69282ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -24,6 +24,7 @@
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -71,6 +72,7 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.keyguard.shared.model.DismissAction;
@@ -345,6 +347,7 @@
}
};
private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
+ private Lazy<KeyguardSurfaceBehindInteractor> mSurfaceBehindInteractor;
private Lazy<KeyguardDismissActionInteractor> mKeyguardDismissActionInteractor;
@Inject
@@ -377,7 +380,8 @@
@Main CoroutineDispatcher mainDispatcher,
Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor,
Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy,
- SelectedUserInteractor selectedUserInteractor
+ SelectedUserInteractor selectedUserInteractor,
+ Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor
) {
mContext = context;
mViewMediatorCallback = callback;
@@ -410,6 +414,7 @@
mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
mKeyguardDismissActionInteractor = keyguardDismissActionInteractorLazy;
mSelectedUserInteractor = selectedUserInteractor;
+ mSurfaceBehindInteractor = surfaceBehindInteractor;
}
KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -480,7 +485,16 @@
mShadeViewController.postToView(() ->
collectFlow(
getViewRootImpl().getView(),
- mWmLockscreenVisibilityInteractor.get().getLockscreenVisibility(),
+ combineFlows(
+ mWmLockscreenVisibilityInteractor.get().getLockscreenVisibility(),
+ mSurfaceBehindInteractor.get().isAnimatingSurface(),
+ (lockscreenVis, animatingSurface) ->
+ // TODO(b/322546110): Waiting until we're not animating the
+ // surface is a workaround to avoid jank. We should actually
+ // fix the source of the jank, and then hide the keyguard
+ // view without waiting for the animation to end.
+ lockscreenVis || animatingSurface
+ ),
this::consumeShowStatusBarKeyguardView));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index 11e374f..d4c180d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -18,25 +18,36 @@
import static com.android.server.notification.Flags.screenshareNotificationHiding;
+import android.annotation.MainThread;
+import android.app.IActivityManager;
+import android.content.Context;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
+import android.os.RemoteException;
import android.os.Trace;
import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.Assert;
import com.android.systemui.util.ListenerSet;
+import java.util.concurrent.Executor;
+
import javax.inject.Inject;
/** Implementation of SensitiveNotificationProtectionController. **/
@SysUISingleton
public class SensitiveNotificationProtectionControllerImpl
implements SensitiveNotificationProtectionController {
- private final MediaProjectionManager mMediaProjectionManager;
+ private static final String LOG_TAG = "SNPC";
+ private final ArraySet<String> mExemptPackages = new ArraySet<>();
private final ListenerSet<Runnable> mListeners = new ListenerSet<>();
private volatile MediaProjectionInfo mProjection;
@@ -45,44 +56,100 @@
new MediaProjectionManager.Callback() {
@Override
public void onStart(MediaProjectionInfo info) {
- Trace.beginSection(
- "SNPC.onProjectionStart");
- // Only enable sensitive content protection if sharing full screen
- // Launch cookie only set (non-null) if sharing single app/task
- updateProjectionState((info.getLaunchCookie() == null) ? info : null);
- Trace.endSection();
+ Trace.beginSection("SNPC.onProjectionStart");
+ try {
+ // Only enable sensitive content protection if sharing full screen
+ // Launch cookie only set (non-null) if sharing single app/task
+ updateProjectionStateAndNotifyListeners(
+ (info.getLaunchCookie() == null) ? info : null);
+ } finally {
+ Trace.endSection();
+ }
}
@Override
public void onStop(MediaProjectionInfo info) {
- Trace.beginSection(
- "SNPC.onProjectionStop");
- updateProjectionState(null);
- Trace.endSection();
- }
-
- private void updateProjectionState(MediaProjectionInfo info) {
- // capture previous state
- boolean wasSensitive = isSensitiveStateActive();
-
- // update internal state
- mProjection = info;
-
- // if either previous or new state is sensitive, notify listeners.
- if (wasSensitive || isSensitiveStateActive()) {
- mListeners.forEach(Runnable::run);
+ Trace.beginSection("SNPC.onProjectionStop");
+ try {
+ updateProjectionStateAndNotifyListeners(null);
+ } finally {
+ Trace.endSection();
}
}
};
@Inject
public SensitiveNotificationProtectionControllerImpl(
+ Context context,
MediaProjectionManager mediaProjectionManager,
- @Main Handler mainHandler) {
- mMediaProjectionManager = mediaProjectionManager;
+ IActivityManager activityManager,
+ @Main Handler mainHandler,
+ @Background Executor bgExecutor) {
+ if (!screenshareNotificationHiding()) {
+ return;
+ }
- if (screenshareNotificationHiding()) {
- mMediaProjectionManager.addCallback(mMediaProjectionCallback, mainHandler);
+ bgExecutor.execute(() -> {
+ ArraySet<String> exemptPackages = new ArraySet<>();
+ // Exempt SystemUI
+ exemptPackages.add(context.getPackageName());
+
+ // Exempt approved bug report handlers
+ try {
+ exemptPackages.addAll(activityManager.getBugreportWhitelistedPackages());
+ } catch (RemoteException e) {
+ Log.e(
+ LOG_TAG,
+ "Error adding bug report handlers to exemption, continuing without",
+ e);
+ // silent failure, skip adding packages to exemption
+ }
+
+ // if currently projecting, notify listeners of exemption changes
+ mainHandler.post(() -> {
+ Trace.beginSection("SNPC.exemptPackagesUpdated");
+ try {
+ updateExemptPackagesAndNotifyListeners(exemptPackages);
+ } finally {
+ Trace.endSection();
+ }
+ });
+ });
+
+ mediaProjectionManager.addCallback(mMediaProjectionCallback, mainHandler);
+ }
+
+ /**
+ * Notify listeners of possible ProjectionState change regardless of current
+ * isSensitiveStateActive value. Method used to ensure updates occur after mExemptPackages gets
+ * updated, which directly changes the outcome of isSensitiveStateActive
+ */
+ @MainThread
+ private void updateExemptPackagesAndNotifyListeners(ArraySet<String> exemptPackages) {
+ Assert.isMainThread();
+ mExemptPackages.addAll(exemptPackages);
+
+ if (mProjection != null) {
+ mListeners.forEach(Runnable::run);
+ }
+ }
+
+ /**
+ * Update ProjectionState respecting current isSensitiveStateActive value. Only notifies
+ * listeners
+ */
+ @MainThread
+ private void updateProjectionStateAndNotifyListeners(MediaProjectionInfo info) {
+ Assert.isMainThread();
+ // capture previous state
+ boolean wasSensitive = isSensitiveStateActive();
+
+ // update internal state
+ mProjection = info;
+
+ // if either previous or new state is sensitive, notify listeners.
+ if (wasSensitive || isSensitiveStateActive()) {
+ mListeners.forEach(Runnable::run);
}
}
@@ -96,11 +163,17 @@
mListeners.remove(onSensitiveStateChanged);
}
+ // TODO(b/323396693): opportunity for optimization
@Override
public boolean isSensitiveStateActive() {
+ MediaProjectionInfo projection = mProjection;
+ if (projection == null) {
+ return false;
+ }
+
// TODO(b/316955558): Add disabled by developer option
- // TODO(b/316955306): Add feature exemption for sysui and bug handlers
- return mProjection != null;
+
+ return !mExemptPackages.contains(projection.getPackageName());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
index ceebcb7..e5179dd 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
@@ -37,19 +37,13 @@
*/
fun registerTask(name: String): Runnable {
pendingTasksCount.incrementAndGet()
-
- if (ENABLE_TRACE) {
- Trace.beginAsyncSection("PendingTasksContainer#$name", 0)
- }
+ Trace.beginAsyncSection("PendingTasksContainer#$name", 0)
return Runnable {
+ Trace.endAsyncSection("PendingTasksContainer#$name", 0)
if (pendingTasksCount.decrementAndGet() == 0) {
val onComplete = completionCallback.getAndSet(null)
onComplete?.run()
-
- if (ENABLE_TRACE) {
- Trace.endAsyncSection("PendingTasksContainer#$name", 0)
- }
}
}
}
@@ -82,4 +76,3 @@
fun getPendingCount(): Int = pendingTasksCount.get()
}
-private const val ENABLE_TRACE = false
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
index 6993c96..fa0d030 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
@@ -16,6 +16,7 @@
package com.android.systemui.util.kotlin
+import android.content.Context
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -110,3 +111,11 @@
val fifth: E,
val sixth: F,
)
+
+fun Int.toPx(context: Context): Int {
+ return (this * context.resources.displayMetrics.density).toInt()
+}
+
+fun Int.toDp(context: Context): Int {
+ return (this / context.resources.displayMetrics.density).toInt()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 2acd4b9..139d190 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 @@
// 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 @@
// 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/Android.bp b/packages/SystemUI/tests/Android.bp
index ec0414e..88939a2 100644
--- a/packages/SystemUI/tests/Android.bp
+++ b/packages/SystemUI/tests/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index da6bfe8..d742da7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -47,7 +47,6 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -167,9 +166,7 @@
mVibrator,
mAuthRippleController,
mResources,
- KeyguardTransitionInteractorFactory.create(
- TestScopeProvider.getTestScope().getBackgroundScope())
- .getKeyguardTransitionInteractor(),
+ mKosmos.getKeyguardTransitionInteractor(),
KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
mFeatureFlags,
mPrimaryBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
index 64cd526..f776a63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
@@ -347,8 +347,8 @@
return CameraAvailabilityListener.build(
context,
context.mainExecutor,
- CameraProtectionLoader((context))
- )
+ CameraProtectionLoaderImpl((context))
+ )
.also {
it.addTransitionCallback(cameraTransitionCallback)
it.startListening()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
similarity index 75%
rename from packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
index 238e5e9..a19a0c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
@@ -27,9 +27,9 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-class CameraProtectionLoaderTest : SysuiTestCase() {
+class CameraProtectionLoaderImplTest : SysuiTestCase() {
- private val loader = CameraProtectionLoader(context)
+ private val loader = CameraProtectionLoaderImpl(context)
@Before
fun setUp() {
@@ -39,19 +39,21 @@
R.string.config_frontBuiltInDisplayCutoutProtection,
OUTER_CAMERA_PROTECTION_PATH
)
+ overrideResource(R.string.config_protectedScreenUniqueId, OUTER_SCREEN_UNIQUE_ID)
overrideResource(R.string.config_protectedInnerCameraId, INNER_CAMERA_LOGICAL_ID)
overrideResource(R.string.config_protectedInnerPhysicalCameraId, INNER_CAMERA_PHYSICAL_ID)
overrideResource(
R.string.config_innerBuiltInDisplayCutoutProtection,
INNER_CAMERA_PROTECTION_PATH
)
+ overrideResource(R.string.config_protectedInnerScreenUniqueId, INNER_SCREEN_UNIQUE_ID)
}
@Test
fun loadCameraProtectionInfoList() {
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos)
+ assertThat(protectionList)
.containsExactly(OUTER_CAMERA_PROTECTION_INFO, INNER_CAMERA_PROTECTION_INFO)
}
@@ -59,18 +61,18 @@
fun loadCameraProtectionInfoList_outerCameraIdEmpty_onlyReturnsInnerInfo() {
overrideResource(R.string.config_protectedCameraId, "")
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos).containsExactly(INNER_CAMERA_PROTECTION_INFO)
+ assertThat(protectionList).containsExactly(INNER_CAMERA_PROTECTION_INFO)
}
@Test
fun loadCameraProtectionInfoList_innerCameraIdEmpty_onlyReturnsOuterInfo() {
overrideResource(R.string.config_protectedInnerCameraId, "")
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos).containsExactly(OUTER_CAMERA_PROTECTION_INFO)
+ assertThat(protectionList).containsExactly(OUTER_CAMERA_PROTECTION_INFO)
}
@Test
@@ -78,13 +80,16 @@
overrideResource(R.string.config_protectedCameraId, "")
overrideResource(R.string.config_protectedInnerCameraId, "")
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos).isEmpty()
+ assertThat(protectionList).isEmpty()
}
+ private fun loadProtectionList() =
+ loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+
private fun CameraProtectionInfo.toTestableVersion() =
- TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds)
+ TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds, displayUniqueId)
/**
* "Testable" version, because the original version contains a Path property, which doesn't
@@ -94,6 +99,7 @@
val logicalCameraId: String,
val physicalCameraId: String?,
val cutoutBounds: Rect,
+ val displayUniqueId: String?,
)
companion object {
@@ -102,11 +108,13 @@
private const val OUTER_CAMERA_PROTECTION_PATH = "M 0,0 H 10,10 V 10,10 H 0,10 Z"
private val OUTER_CAMERA_PROTECTION_BOUNDS =
Rect(/* left = */ 0, /* top = */ 0, /* right = */ 10, /* bottom = */ 10)
+ private const val OUTER_SCREEN_UNIQUE_ID = "111"
private val OUTER_CAMERA_PROTECTION_INFO =
TestableProtectionInfo(
OUTER_CAMERA_LOGICAL_ID,
OUTER_CAMERA_PHYSICAL_ID,
- OUTER_CAMERA_PROTECTION_BOUNDS
+ OUTER_CAMERA_PROTECTION_BOUNDS,
+ OUTER_SCREEN_UNIQUE_ID,
)
private const val INNER_CAMERA_LOGICAL_ID = "2"
@@ -114,11 +122,13 @@
private const val INNER_CAMERA_PROTECTION_PATH = "M 0,0 H 20,20 V 20,20 H 0,20 Z"
private val INNER_CAMERA_PROTECTION_BOUNDS =
Rect(/* left = */ 0, /* top = */ 0, /* right = */ 20, /* bottom = */ 20)
+ private const val INNER_SCREEN_UNIQUE_ID = "222"
private val INNER_CAMERA_PROTECTION_INFO =
TestableProtectionInfo(
INNER_CAMERA_LOGICAL_ID,
INNER_CAMERA_PHYSICAL_ID,
- INNER_CAMERA_PROTECTION_BOUNDS
+ INNER_CAMERA_PROTECTION_BOUNDS,
+ INNER_SCREEN_UNIQUE_ID,
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
new file mode 100644
index 0000000..f769b4e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import com.android.systemui.res.R
+
+class FakeCameraProtectionLoader(private val context: SysuiTestableContext) :
+ CameraProtectionLoader {
+
+ private val realLoader = CameraProtectionLoaderImpl(context)
+
+ override fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> =
+ realLoader.loadCameraProtectionInfoList()
+
+ fun clearProtectionInfoList() {
+ context.orCreateTestableResources.addOverride(R.string.config_protectedCameraId, "")
+ context.orCreateTestableResources.addOverride(R.string.config_protectedInnerCameraId, "")
+ }
+
+ fun addAllProtections() {
+ addOuterCameraProtection()
+ addInnerCameraProtection()
+ }
+
+ fun addOuterCameraProtection(displayUniqueId: String = "111") {
+ context.orCreateTestableResources.addOverride(R.string.config_protectedCameraId, "1")
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedPhysicalCameraId,
+ "11"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_frontBuiltInDisplayCutoutProtection,
+ "M 0,0 H 10,10 V 10,10 H 0,10 Z"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedScreenUniqueId,
+ displayUniqueId
+ )
+ }
+
+ fun addInnerCameraProtection(displayUniqueId: String = "222") {
+ context.orCreateTestableResources.addOverride(R.string.config_protectedInnerCameraId, "2")
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedInnerPhysicalCameraId,
+ "22"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_innerBuiltInDisplayCutoutProtection,
+ "M 0,0 H 20,20 V 20,20 H 0,20 Z"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedInnerScreenUniqueId,
+ displayUniqueId
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 1f1fa72..c20367e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -177,7 +177,7 @@
new FakeFacePropertyRepository();
private List<DecorProvider> mMockCutoutList;
private final CameraProtectionLoader mCameraProtectionLoader =
- new CameraProtectionLoader(mContext);
+ new CameraProtectionLoaderImpl(mContext);
@Before
public void setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
new file mode 100644
index 0000000..f37c4ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayCutout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysUICutoutProviderTest : SysuiTestCase() {
+
+ private val fakeProtectionLoader = FakeCameraProtectionLoader(context)
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_noCutout_returnsNull() {
+ val noCutoutDisplay = createDisplay(cutout = null)
+ val noCutoutDisplayContext = context.createDisplayContext(noCutoutDisplay)
+ val provider = SysUICutoutProvider(noCutoutDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()
+
+ assertThat(sysUICutout).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_returnsCutout() {
+ val cutoutDisplay = createDisplay()
+ val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
+ val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cutout).isEqualTo(cutoutDisplay.cutout)
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_noAssociatedProtection_returnsNoProtection() {
+ val cutoutDisplay = createDisplay()
+ val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
+ val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_outerDisplay_protectionAssociated_returnsProtection() {
+ fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = OUTER_DISPLAY_UNIQUE_ID)
+ val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
+ val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNotNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_outerDisplay_protectionNotAvailable_returnsNullProtection() {
+ fakeProtectionLoader.clearProtectionInfoList()
+ val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
+ val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_displayWithNullId_protectionsWithNoId_returnsNullProtection() {
+ fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = "")
+ val displayContext = context.createDisplayContext(createDisplay(uniqueId = null))
+ val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_displayWithEmptyId_protectionsWithNoId_returnsNullProtection() {
+ fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = "")
+ val displayContext = context.createDisplayContext(createDisplay(uniqueId = ""))
+ val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ companion object {
+ private const val OUTER_DISPLAY_UNIQUE_ID = "outer"
+ private val OUTER_DISPLAY = createDisplay(uniqueId = OUTER_DISPLAY_UNIQUE_ID)
+
+ private fun createDisplay(
+ uniqueId: String? = "uniqueId",
+ cutout: DisplayCutout? = mock<DisplayCutout>()
+ ) =
+ mock<Display> {
+ whenever(this.displayAdjustments).thenReturn(DisplayAdjustments())
+ whenever(this.cutout).thenReturn(cutout)
+ whenever(this.uniqueId).thenReturn(uniqueId)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index ca3eb3e..4a1bdbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -69,7 +69,6 @@
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.test.filters.SmallTest;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
@@ -238,7 +237,7 @@
final List<String> stubShortcutTargets = new ArrayList<>();
stubShortcutTargets.add(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
when(mStubAccessibilityManager.getAccessibilityShortcutTargets(
- ShortcutConstants.UserShortcutType.HARDWARE)).thenReturn(stubShortcutTargets);
+ AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY)).thenReturn(stubShortcutTargets);
mMenuViewLayer.mDismissMenuAction.run();
final String value = Settings.Secure.getString(mSpyContext.getContentResolver(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index fc34255..3f83ce3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
@@ -26,7 +26,7 @@
import com.android.systemui.display.data.repository.fakeDeviceStateRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -47,7 +47,6 @@
@SmallTest
@RunWith(JUnit4::class)
class LogContextInteractorImplTest : SysuiTestCase() {
-
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
private val kosmos = testKosmos()
@@ -64,11 +63,7 @@
LogContextInteractorImpl(
testScope.backgroundScope,
deviceStateRepository,
- KeyguardTransitionInteractorFactory.create(
- repository = keyguardTransitionRepository,
- scope = testScope.backgroundScope,
- )
- .keyguardTransitionInteractor,
+ kosmos.keyguardTransitionInteractor,
udfpsOverlayInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 368d1d9..b28d0c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -16,71 +16,56 @@
package com.android.systemui.deviceentry.domain.interactor
-import android.app.trust.TrustManager
import android.content.pm.UserInfo
import android.hardware.biometrics.BiometricFaceConstants
import android.hardware.biometrics.BiometricSourceType
-import android.os.Handler
import android.os.PowerManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.keyguard.trustManager
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FaceSensorInfo
-import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.facePropertyRepository
import com.android.systemui.biometrics.shared.model.LockoutMode
import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig
+import com.android.systemui.deviceentry.data.repository.faceWakeUpTriggersConfig
import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
-import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestCoroutineScheduler
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -89,84 +74,39 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
+ val kosmos =
+ testKosmos().apply { this.faceWakeUpTriggersConfig = mock<FaceWakeUpTriggersConfig>() }
private lateinit var underTest: SystemUIDeviceEntryFaceAuthInteractor
- private lateinit var testScope: TestScope
- private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
- private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
- private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
- private lateinit var faceAuthRepository: FakeDeviceEntryFaceAuthRepository
- private lateinit var fakeUserRepository: FakeUserRepository
- private lateinit var facePropertyRepository: FakeFacePropertyRepository
- private lateinit var fakeDeviceEntryFingerprintAuthRepository:
- FakeDeviceEntryFingerprintAuthRepository
- private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
- private lateinit var powerInteractor: PowerInteractor
- private lateinit var fakeBiometricSettingsRepository: FakeBiometricSettingsRepository
+ private val testScope = kosmos.testScope
+ private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
+ private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
+ private val fakeUserRepository = kosmos.fakeUserRepository
+ private val facePropertyRepository = kosmos.facePropertyRepository
+ private val fakeDeviceEntryFingerprintAuthRepository =
+ kosmos.fakeDeviceEntryFingerprintAuthRepository
+ private val powerInteractor = kosmos.powerInteractor
+ private val fakeBiometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
- @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig
- @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
- @Mock private lateinit var trustManager: TrustManager
+ private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
+ private val faceWakeUpTriggersConfig = kosmos.faceWakeUpTriggersConfig
+ private val trustManager = kosmos.trustManager
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- val scheduler = TestCoroutineScheduler()
- val dispatcher = StandardTestDispatcher(scheduler)
- testScope = TestScope(dispatcher)
- bouncerRepository = FakeKeyguardBouncerRepository()
- faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
- keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- keyguardTransitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = TestScope().backgroundScope,
- repository = keyguardTransitionRepository,
- )
- .keyguardTransitionInteractor
-
- fakeDeviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
- fakeUserRepository = FakeUserRepository()
fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser))
- facePropertyRepository = FakeFacePropertyRepository()
- fakeKeyguardRepository = FakeKeyguardRepository()
- powerInteractor = PowerInteractorFactory.create().powerInteractor
- fakeBiometricSettingsRepository = FakeBiometricSettingsRepository()
underTest =
SystemUIDeviceEntryFaceAuthInteractor(
mContext,
testScope.backgroundScope,
- dispatcher,
+ kosmos.testDispatcher,
faceAuthRepository,
- {
- PrimaryBouncerInteractor(
- bouncerRepository,
- mock(BouncerView::class.java),
- mock(Handler::class.java),
- mock(KeyguardStateController::class.java),
- mock(KeyguardSecurityModel::class.java),
- mock(PrimaryBouncerCallbackInteractor::class.java),
- mock(FalsingCollector::class.java),
- mock(DismissCallbackRegistry::class.java),
- context,
- keyguardUpdateMonitor,
- FakeTrustRepository(),
- testScope.backgroundScope,
- selectedUserInteractor,
- underTest,
- )
- },
- AlternateBouncerInteractor(
- mock(StatusBarStateController::class.java),
- mock(KeyguardStateController::class.java),
- bouncerRepository,
- FakeFingerprintPropertyRepository(),
- fakeBiometricSettingsRepository,
- FakeSystemClock(),
- keyguardUpdateMonitor,
- testScope.backgroundScope,
- ),
+ { kosmos.primaryBouncerInteractor },
+ kosmos.alternateBouncerInteractor,
keyguardTransitionInteractor,
FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
keyguardUpdateMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index 852f9a5..cf8fe79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -5,16 +5,16 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.utils.GlobalWindowManager
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -36,13 +36,14 @@
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ResourceTrimmerTest : SysuiTestCase() {
+ val kosmos = testKosmos()
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
- private val keyguardRepository = FakeKeyguardRepository()
- private val featureFlags = FakeFeatureFlags()
- private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- private lateinit var powerInteractor: PowerInteractor
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val featureFlags = kosmos.fakeFeatureFlagsClassic
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val powerInteractor = kosmos.powerInteractor
@Mock private lateinit var globalWindowManager: GlobalWindowManager
private lateinit var resourceTrimmer: ResourceTrimmer
@@ -52,7 +53,6 @@
MockitoAnnotations.initMocks(this)
featureFlags.set(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK, true)
featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, true)
- powerInteractor = PowerInteractorFactory.create().powerInteractor
keyguardRepository.setDozeAmount(0f)
keyguardRepository.setKeyguardGoingAway(false)
@@ -66,11 +66,7 @@
ResourceTrimmer(
keyguardInteractor,
powerInteractor,
- KeyguardTransitionInteractorFactory.create(
- scope = TestScope().backgroundScope,
- repository = keyguardTransitionRepository,
- )
- .keyguardTransitionInteractor,
+ kosmos.keyguardTransitionInteractor,
globalWindowManager,
testScope.backgroundScope,
testDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
index 722c11d..668fb64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -16,89 +16,60 @@
package com.android.systemui.keyguard.domain.interactor
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestModule
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.util.mockTopActivityClassName
-import com.android.systemui.shared.system.ActivityManagerWrapper
-import dagger.BindsInstance
-import dagger.Component
-import dagger.Lazy
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.FlingInfo
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
-import junit.framework.Assert.assertTrue
-import junit.framework.Assert.fail
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
-class FromLockscreenTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
- private lateinit var underTest: FromLockscreenTransitionInteractor
-
- // Override the fromLockscreenTransitionInteractor provider from the superclass so our underTest
- // interactor is provided to any classes that need it.
- override var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
- Lazy {
- underTest
+class FromLockscreenTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
}
- private lateinit var testComponent: TestComponent
- @Mock private lateinit var activityManagerWrapper: ActivityManagerWrapper
-
- private var topActivityClassName = "launcher"
-
- @Before
- override fun setUp() {
- super.setUp()
- MockitoAnnotations.initMocks(this)
-
- testComponent =
- DaggerFromLockscreenTransitionInteractorTest_TestComponent.factory()
- .create(
- test = this,
- mocks =
- TestMocksModule(
- activityManagerWrapper = activityManagerWrapper,
- ),
- )
- underTest = testComponent.underTest
- testScope = testComponent.testScope
- transitionRepository = testComponent.transitionRepository
-
- activityManagerWrapper.mockTopActivityClassName(topActivityClassName)
- }
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromLockscreenTransitionInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val shadeRepository = kosmos.fakeShadeRepository
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
@Test
- fun testSurfaceBehindVisibility_nonNullOnlyForRelevantTransitions() =
+ fun testSurfaceBehindVisibility() =
testScope.runTest {
val values by collectValues(underTest.surfaceBehindVisibility)
runCurrent()
// Transition-specific surface visibility should be null ("don't care") initially.
- assertEquals(
- listOf(
- null,
- ),
- values
- )
+ assertThat(values).containsExactly(null)
transitionRepository.sendTransitionStep(
TransitionStep(
@@ -107,15 +78,12 @@
to = KeyguardState.AOD,
)
)
-
runCurrent()
- assertEquals(
- listOf(
+ assertThat(values)
+ .containsExactly(
null, // LOCKSCREEN -> AOD does not have any specific surface visibility.
- ),
- values
- )
+ )
transitionRepository.sendTransitionStep(
TransitionStep(
@@ -124,159 +92,69 @@
to = KeyguardState.GONE,
)
)
-
runCurrent()
- assertEquals(
- listOf(
+ assertThat(values)
+ .containsExactly(
null,
true, // Surface is made visible immediately during LOCKSCREEN -> GONE
- ),
- values
+ )
+ .inOrder()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ null,
+ true, // Surface remains visible.
+ )
+ .inOrder()
}
@Test
- fun testSurfaceBehindModel() =
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionsToGone_whenDismissFlingWhileDismissable_flagEnabled() =
testScope.runTest {
- val values by collectValues(underTest.surfaceBehindModel)
+ underTest.start()
+ verify(transitionRepository, never()).startTransition(any())
+
+ keyguardRepository.setKeyguardDismissible(true)
runCurrent()
-
- assertEquals(
- values,
- listOf(
- null, // We should start null ("don't care").
- )
- )
-
- transitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- )
+ shadeRepository.setCurrentFling(
+ FlingInfo(expand = true) // Not a dismiss fling (expand = true).
)
runCurrent()
- assertEquals(
- listOf(
- null, // LOCKSCREEN -> AOD does not have specific view params.
- ),
- values
- )
-
- transitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- )
- )
- runCurrent()
-
- transitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.RUNNING,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- value = 0.01f,
- )
- )
- runCurrent()
-
- transitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.RUNNING,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- value = 0.99f,
- )
- )
- runCurrent()
-
- assertEquals(3, values.size)
- val model1percent = values[1]
- val model99percent = values[2]
-
- try {
- // We should initially have an alpha of 0f when unlocking, so the surface is not
- // visible
- // while lockscreen UI animates out.
- assertEquals(0f, model1percent!!.alpha)
-
- // By the end it should probably be visible.
- assertTrue(model99percent!!.alpha > 0f)
- } catch (e: NullPointerException) {
- fail("surfaceBehindModel was unexpectedly null.")
- }
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ .also {
+ assertEquals(KeyguardState.LOCKSCREEN, it.from)
+ assertEquals(KeyguardState.GONE, it.to)
+ }
}
@Test
- fun testSurfaceBehindModel_alpha1_whenTransitioningWithInWindowAnimation() =
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testDoesNotTransitionToGone_whenDismissFlingWhileDismissable_flagDisabled() =
testScope.runTest {
- testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
- topActivityClassName
+ underTest.start()
+ verify(transitionRepository, never()).startTransition(any())
+
+ keyguardRepository.setKeyguardDismissible(true)
+ runCurrent()
+ shadeRepository.setCurrentFling(
+ FlingInfo(expand = true) // Not a dismiss fling (expand = true).
)
runCurrent()
- val values by collectValues(underTest.surfaceBehindModel)
- runCurrent()
-
- transitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- )
- )
- runCurrent()
-
- assertEquals(1f, values[values.size - 1]?.alpha)
+ verify(transitionRepository, never()).startTransition(any())
}
-
- @Test
- fun testSurfaceBehindModel_alphaZero_whenNotTransitioningWithInWindowAnimation() =
- testScope.runTest {
- testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
- "not_launcher"
- )
- runCurrent()
-
- val values by collectValues(underTest.surfaceBehindModel)
- runCurrent()
-
- transitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- )
- )
- runCurrent()
-
- assertEquals(0f, values[values.size - 1]?.alpha)
- }
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- ]
- )
- interface TestComponent {
- val underTest: FromLockscreenTransitionInteractor
- val testScope: TestScope
- val transitionRepository: FakeKeyguardTransitionRepository
- val surfaceBehindRepository: FakeKeyguardSurfaceBehindRepository
- val inWindowLauncherUnlockAnimationRepository: InWindowLauncherUnlockAnimationRepository
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- mocks: TestMocksModule,
- ): TestComponent
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index 6092b6b35..f33a5c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -18,61 +18,33 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.mockito.mock
-import dagger.Lazy
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
import junit.framework.Assert.fail
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
-class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
- private lateinit var underTest: FromPrimaryBouncerTransitionInteractor
-
- private val mSelectedUserInteractor = SelectedUserInteractor(FakeUserRepository())
-
- // Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our
- // underTest interactor is provided to any classes that need it.
- override var fromPrimaryBouncerTransitionInteractorLazy:
- Lazy<FromPrimaryBouncerTransitionInteractor>? =
- Lazy {
- underTest
- }
-
- @Before
- override fun setUp() {
- super.setUp()
-
- underTest =
- FromPrimaryBouncerTransitionInteractor(
- transitionRepository = super.transitionRepository,
- transitionInteractor = super.transitionInteractor,
- scope = super.testScope.backgroundScope,
- bgDispatcher = super.testDispatcher,
- mainDispatcher = super.testDispatcher,
- keyguardInteractor = super.keyguardInteractor,
- communalInteractor = super.communalInteractor,
- flags = FakeFeatureFlags(),
- keyguardSecurityModel = mock(),
- powerInteractor = PowerInteractorFactory.create().powerInteractor,
- selectedUserInteractor = mSelectedUserInteractor
- )
- }
+class FromPrimaryBouncerTransitionInteractorTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val underTest = kosmos.fromPrimaryBouncerTransitionInteractor
+ val testScope = kosmos.testScope
+ val selectedUserInteractor = kosmos.selectedUserInteractor
+ val transitionRepository = kosmos.fakeKeyguardTransitionRepository
@Test
fun testSurfaceBehindVisibility() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index e75f557..62855d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -21,16 +21,15 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -42,11 +41,12 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardDismissActionInteractorTest : SysuiTestCase() {
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+ val kosmos = testKosmos()
- private lateinit var dispatcher: TestDispatcher
- private lateinit var testScope: TestScope
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ private val testScope = kosmos.testScope
private lateinit var dismissInteractorWithDependencies:
KeyguardDismissInteractorFactory.WithDependencies
@@ -55,25 +55,18 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- dispatcher = StandardTestDispatcher()
- testScope = TestScope(dispatcher)
dismissInteractorWithDependencies =
KeyguardDismissInteractorFactory.create(
context = context,
testScope = testScope,
+ keyguardRepository = keyguardRepository,
)
- keyguardRepository = dismissInteractorWithDependencies.keyguardRepository
- transitionRepository = FakeKeyguardTransitionRepository()
underTest =
KeyguardDismissActionInteractor(
keyguardRepository,
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = transitionRepository,
- )
- .keyguardTransitionInteractor,
+ kosmos.keyguardTransitionInteractor,
dismissInteractorWithDependencies.interactor,
testScope.backgroundScope,
)
@@ -180,7 +173,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
- testScope
+ testScope,
)
assertThat(executeDismissAction).isNotNull()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
index 71fcf6f..f23dd55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -20,148 +20,176 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.util.mockito.whenever
-import junit.framework.Assert.assertEquals
-import junit.framework.Assert.assertTrue
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.TestScope
+import com.android.systemui.keyguard.util.mockTopActivityClassName
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shared.system.activityManagerWrapper
+import com.android.systemui.testKosmos
+import com.android.systemui.util.assertValuesMatch
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
-import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations.initMocks
@SmallTest
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class KeyguardSurfaceBehindInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.keyguardSurfaceBehindInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val inWindowUnlockInteractor = kosmos.inWindowLauncherUnlockAnimationInteractor
+ private val activityManagerWrapper = kosmos.activityManagerWrapper
- private lateinit var underTest: KeyguardSurfaceBehindInteractor
- private lateinit var repository: FakeKeyguardSurfaceBehindRepository
-
- @Mock
- private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
- @Mock
- private lateinit var fromPrimaryBouncerTransitionInteractor:
- FromPrimaryBouncerTransitionInteractor
-
- private val lockscreenSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.33f)
- private val primaryBouncerSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.66f)
-
- private val testScope = TestScope()
-
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
- private lateinit var transitionInteractor: KeyguardTransitionInteractor
+ private val LAUNCHER_ACTIVITY_NAME = "launcher"
@Before
fun setUp() {
- initMocks(this)
+ inWindowUnlockInteractor.setLauncherActivityClass(LAUNCHER_ACTIVITY_NAME)
- whenever(fromLockscreenTransitionInteractor.surfaceBehindModel)
- .thenReturn(flowOf(lockscreenSurfaceBehindModel))
- whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindModel)
- .thenReturn(flowOf(primaryBouncerSurfaceBehindModel))
-
- transitionRepository = FakeKeyguardTransitionRepository()
-
- transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = transitionRepository,
- )
- .keyguardTransitionInteractor
-
- repository = FakeKeyguardSurfaceBehindRepository()
- underTest =
- KeyguardSurfaceBehindInteractor(
- repository = repository,
- fromLockscreenInteractor = fromLockscreenTransitionInteractor,
- fromPrimaryBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
- transitionInteractor = transitionInteractor,
- )
+ // Default to having something other than Launcher on top.
+ activityManagerWrapper.mockTopActivityClassName("not_launcher")
}
@Test
- fun viewParamsSwitchToCorrectFlow() =
+ fun testSurfaceBehindModel_toAppSurface() =
testScope.runTest {
val values by collectValues(underTest.viewParams)
-
- // Start on the LOCKSCREEN.
- transitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- )
- )
-
runCurrent()
- transitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.FINISHED,
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
+ assertThat(values)
+ .containsExactly(
+ // We're initialized in LOCKSCREEN.
+ KeyguardSurfaceBehindModel(alpha = 0f),
)
- )
-
- runCurrent()
-
- // We're on LOCKSCREEN; we should be using the default params.
- assertEquals(1, values.size)
- assertTrue(values[0].alpha == 0f)
transitionRepository.sendTransitionStep(
TransitionStep(
- transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
- )
- )
-
- runCurrent()
-
- // We're going from LOCKSCREEN -> GONE, we should be using the lockscreen interactor's
- // surface behind model.
- assertEquals(2, values.size)
- assertEquals(values[1], lockscreenSurfaceBehindModel)
-
- transitionRepository.sendTransitionStep(
- TransitionStep(
transitionState = TransitionState.STARTED,
- from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.GONE,
)
)
-
runCurrent()
- // We're going from PRIMARY_BOUNCER -> GONE, we should be using the bouncer interactor's
- // surface behind model.
- assertEquals(3, values.size)
- assertEquals(values[2], primaryBouncerSurfaceBehindModel)
+ values.assertValuesMatch(
+ { it == KeyguardSurfaceBehindModel(alpha = 0f) },
+ // Once we start a transition to GONE, we should fade in and translate up. The exact
+ // start value depends on screen density, so just look for != 0.
+ {
+ it.animateFromAlpha == 0f &&
+ it.alpha == 1f &&
+ it.animateFromTranslationY != 0f &&
+ it.translationY == 0f
+ }
+ )
transitionRepository.sendTransitionStep(
TransitionStep(
- transitionState = TransitionState.FINISHED,
- from = KeyguardState.PRIMARY_BOUNCER,
+ from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
+ transitionState = TransitionState.RUNNING,
)
)
-
runCurrent()
- // Once PRIMARY_BOUNCER -> GONE finishes, we should be using default params, which is
- // alpha=1f when we're GONE.
- assertEquals(4, values.size)
- assertEquals(1f, values[3].alpha)
+ values.assertValuesMatch(
+ { it == KeyguardSurfaceBehindModel(alpha = 0f) },
+ // There should be no change as we're RUNNING.
+ {
+ it.animateFromAlpha == 0f &&
+ it.alpha == 1f &&
+ it.animateFromTranslationY != 0f &&
+ it.translationY == 0f
+ }
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+ runCurrent()
+
+ values.assertValuesMatch(
+ { it == KeyguardSurfaceBehindModel(alpha = 0f) },
+ {
+ it.animateFromAlpha == 0f &&
+ it.alpha == 1f &&
+ it.animateFromTranslationY != 0f &&
+ it.translationY == 0f
+ },
+ // Once the current state is GONE, we should default to alpha = 1f.
+ { it == KeyguardSurfaceBehindModel(alpha = 1f) }
+ )
+ }
+
+ @Test
+ fun testSurfaceBehindModel_toLauncher() =
+ testScope.runTest {
+ val values by collectValues(underTest.viewParams)
+ activityManagerWrapper.mockTopActivityClassName(LAUNCHER_ACTIVITY_NAME)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ // We're initialized in LOCKSCREEN.
+ KeyguardSurfaceBehindModel(alpha = 0f),
+ )
+ .inOrder()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ KeyguardSurfaceBehindModel(alpha = 0f),
+ // We should instantly set alpha = 1, with no animations, when Launcher is
+ // behind
+ // the keyguard since we're playing in-window animations.
+ KeyguardSurfaceBehindModel(alpha = 1f),
+ )
+ .inOrder()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.RUNNING,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ KeyguardSurfaceBehindModel(alpha = 0f),
+ // Should have remained at alpha = 1f through the entire animation.
+ KeyguardSurfaceBehindModel(alpha = 1f),
+ )
+ .inOrder()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
deleted file mode 100644
index a03aed0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.interactor
-
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.mock
-import dagger.Lazy
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-
-open class KeyguardTransitionInteractorTestCase : SysuiTestCase() {
- private val kosmos = testKosmos()
- val testDispatcher = StandardTestDispatcher()
- var testScope = TestScope(testDispatcher)
-
- lateinit var keyguardRepository: FakeKeyguardRepository
- lateinit var transitionRepository: FakeKeyguardTransitionRepository
-
- lateinit var keyguardInteractor: KeyguardInteractor
- lateinit var communalInteractor: CommunalInteractor
- lateinit var transitionInteractor: KeyguardTransitionInteractor
-
- /**
- * Replace these lazy providers with non-null ones if you want test dependencies to use a real
- * instance of the interactor for the test.
- */
- open var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
- null
- open var fromPrimaryBouncerTransitionInteractorLazy:
- Lazy<FromPrimaryBouncerTransitionInteractor>? =
- null
-
- open fun setUp() {
- keyguardRepository = FakeKeyguardRepository()
- transitionRepository = FakeKeyguardTransitionRepository()
-
- keyguardInteractor =
- KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor
-
- communalInteractor = kosmos.communalInteractor
-
- transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- repository = transitionRepository,
- keyguardInteractor = keyguardInteractor,
- scope = testScope.backgroundScope,
- fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractorLazy
- ?: Lazy { mock() },
- fromPrimaryBouncerTransitionInteractor =
- fromPrimaryBouncerTransitionInteractorLazy ?: Lazy { mock() },
- )
- .also {
- fromLockscreenTransitionInteractorLazy = it.fromLockscreenTransitionInteractor
- fromPrimaryBouncerTransitionInteractorLazy =
- it.fromPrimaryBouncerTransitionInteractor
- }
- .keyguardTransitionInteractor
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 2812718..bb61d18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -23,18 +23,13 @@
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
@@ -47,16 +42,14 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.statusbar.commandQueue
import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
@@ -89,22 +82,26 @@
@SmallTest
@RunWith(JUnit4::class)
class KeyguardTransitionScenariosTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos =
+ testKosmos().apply {
+ fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ this.commandQueue = fakeCommandQueue
+ }
private val testScope = kosmos.testScope
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
- private lateinit var commandQueue: FakeCommandQueue
- private lateinit var shadeRepository: FakeShadeRepository
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
- private lateinit var transitionInteractor: KeyguardTransitionInteractor
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ private var commandQueue = kosmos.fakeCommandQueue
+ private val shadeRepository = kosmos.fakeShadeRepository
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val transitionInteractor = kosmos.keyguardTransitionInteractor
private lateinit var featureFlags: FakeFeatureFlags
// Used to verify transition requests for test output
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
@Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
- private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+ private val fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
private lateinit var fromDozingTransitionInteractor: FromDozingTransitionInteractor
private lateinit var fromOccludedTransitionInteractor: FromOccludedTransitionInteractor
@@ -112,48 +109,26 @@
private lateinit var fromAodTransitionInteractor: FromAodTransitionInteractor
private lateinit var fromAlternateBouncerTransitionInteractor:
FromAlternateBouncerTransitionInteractor
- private lateinit var fromPrimaryBouncerTransitionInteractor:
- FromPrimaryBouncerTransitionInteractor
+ private val fromPrimaryBouncerTransitionInteractor =
+ kosmos.fromPrimaryBouncerTransitionInteractor
private lateinit var fromDreamingLockscreenHostedTransitionInteractor:
FromDreamingLockscreenHostedTransitionInteractor
private lateinit var fromGlanceableHubTransitionInteractor:
FromGlanceableHubTransitionInteractor
- private lateinit var powerInteractor: PowerInteractor
- private lateinit var keyguardInteractor: KeyguardInteractor
- private lateinit var communalInteractor: CommunalInteractor
+ private val powerInteractor = kosmos.powerInteractor
+ private val keyguardInteractor = kosmos.keyguardInteractor
+ private val communalInteractor = kosmos.communalInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- keyguardRepository = kosmos.fakeKeyguardRepository
- bouncerRepository = kosmos.fakeKeyguardBouncerRepository
- commandQueue = FakeCommandQueue()
- shadeRepository = kosmos.fakeShadeRepository
- transitionRepository = spy(kosmos.fakeKeyguardTransitionRepository)
- powerInteractor = PowerInteractorFactory.create().powerInteractor
- communalInteractor = kosmos.communalInteractor
-
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
featureFlags = FakeFeatureFlags()
- keyguardInteractor = createKeyguardInteractor()
-
- transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope,
- repository = transitionRepository,
- keyguardInteractor = keyguardInteractor,
- fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
- fromPrimaryBouncerTransitionInteractor = {
- fromPrimaryBouncerTransitionInteractor
- },
- )
- .keyguardTransitionInteractor
-
val glanceableHubTransitions =
GlanceableHubTransitions(
scope = testScope,
@@ -162,45 +137,9 @@
transitionRepository = transitionRepository,
communalInteractor = communalInteractor
)
- fromLockscreenTransitionInteractor =
- FromLockscreenTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- flags = featureFlags,
- shadeRepository = shadeRepository,
- powerInteractor = powerInteractor,
- glanceableHubTransitions = glanceableHubTransitions,
- inWindowLauncherUnlockAnimationInteractor = {
- InWindowLauncherUnlockAnimationInteractor(
- InWindowLauncherUnlockAnimationRepository(),
- testScope,
- transitionInteractor,
- { FakeKeyguardSurfaceBehindRepository() },
- mock(),
- )
- },
- )
- .apply { start() }
- fromPrimaryBouncerTransitionInteractor =
- FromPrimaryBouncerTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- flags = featureFlags,
- keyguardSecurityModel = keyguardSecurityModel,
- powerInteractor = powerInteractor,
- communalInteractor = communalInteractor,
- selectedUserInteractor = mSelectedUserInteractor,
- )
- .apply { start() }
+ fromLockscreenTransitionInteractor.start()
+ fromPrimaryBouncerTransitionInteractor.start()
fromDreamingTransitionInteractor =
FromDreamingTransitionInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorTest.kt
new file mode 100644
index 0000000..d77519d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorTest.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.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.FlingInfo
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Correspondence
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class SwipeToDismissInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.swipeToDismissInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val shadeRepository = kosmos.fakeShadeRepository
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+
+ @Test
+ fun testDismissFling_emitsInLockscreenWhenDismissable() =
+ testScope.runTest {
+ val values by collectValues(underTest.dismissFling)
+ keyguardRepository.setKeyguardDismissible(true)
+ runCurrent()
+ shadeRepository.setCurrentFling(FlingInfo(expand = false))
+ runCurrent()
+
+ assertThat(values)
+ .comparingElementsUsing(
+ Correspondence.transforming(
+ { fling: FlingInfo? -> fling?.expand },
+ "expand equals"
+ )
+ )
+ .containsExactly(null, false)
+ .inOrder()
+ }
+
+ @Test
+ fun testDismissFling_doesNotEmitInWrongStateOrNotDismissable() =
+ testScope.runTest {
+ val values by collectValues(underTest.dismissFling)
+ keyguardRepository.setKeyguardDismissible(false)
+ shadeRepository.setCurrentFling(FlingInfo(expand = false))
+ runCurrent()
+
+ assertThat(values).containsExactly(null)
+
+ // Not in LOCKSCREEN, but dismissable.
+ transitionRepository.sendTransitionSteps(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ testScope
+ )
+ keyguardRepository.setKeyguardDismissible(true)
+
+ // Re-emit a valid dismiss fling.
+ shadeRepository.setCurrentFling(null)
+ shadeRepository.setCurrentFling(FlingInfo(expand = false))
+ runCurrent()
+
+ assertThat(values).containsExactly(null)
+ }
+
+ @Test
+ fun testExpandFling_doesNotEmit() =
+ testScope.runTest {
+ val values by collectValues(underTest.dismissFling)
+ keyguardRepository.setKeyguardDismissible(true)
+ runCurrent()
+ shadeRepository.setCurrentFling(
+ FlingInfo(expand = true) // Not a dismiss fling (expand = true).
+ )
+ runCurrent()
+
+ assertThat(values).containsExactly(null)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index 6eed427..a4483bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -20,76 +20,46 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations.initMocks
@SmallTest
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
-
- private lateinit var underTest: WindowManagerLockscreenVisibilityInteractor
-
- @Mock private lateinit var surfaceBehindInteractor: KeyguardSurfaceBehindInteractor
- @Mock
- private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
- @Mock
- private lateinit var fromPrimaryBouncerTransitionInteractor:
- FromPrimaryBouncerTransitionInteractor
-
private val lockscreenSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
private val primaryBouncerSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
private val surfaceBehindIsAnimatingFlow = MutableStateFlow(false)
- private val testScope = TestScope()
+ private val kosmos =
+ testKosmos().apply {
+ fromLockscreenTransitionInteractor = mock<FromLockscreenTransitionInteractor>()
+ fromPrimaryBouncerTransitionInteractor = mock<FromPrimaryBouncerTransitionInteractor>()
+ keyguardSurfaceBehindInteractor = mock<KeyguardSurfaceBehindInteractor>()
- private lateinit var keyguardInteractor: KeyguardInteractor
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
- private lateinit var transitionInteractor: KeyguardTransitionInteractor
+ whenever(fromLockscreenTransitionInteractor.surfaceBehindVisibility)
+ .thenReturn(lockscreenSurfaceVisibilityFlow)
+ whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindVisibility)
+ .thenReturn(primaryBouncerSurfaceVisibilityFlow)
+ whenever(keyguardSurfaceBehindInteractor.isAnimatingSurface)
+ .thenReturn(surfaceBehindIsAnimatingFlow)
+ }
- @Before
- fun setUp() {
- initMocks(this)
-
- whenever(fromLockscreenTransitionInteractor.surfaceBehindVisibility)
- .thenReturn(lockscreenSurfaceVisibilityFlow)
- whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindVisibility)
- .thenReturn(primaryBouncerSurfaceVisibilityFlow)
- whenever(surfaceBehindInteractor.isAnimatingSurface)
- .thenReturn(surfaceBehindIsAnimatingFlow)
-
- transitionRepository = FakeKeyguardTransitionRepository()
-
- transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = transitionRepository,
- )
- .also { keyguardInteractor = it.keyguardInteractor }
- .keyguardTransitionInteractor
-
- underTest =
- WindowManagerLockscreenVisibilityInteractor(
- keyguardInteractor = keyguardInteractor,
- transitionInteractor = transitionInteractor,
- surfaceBehindInteractor = surfaceBehindInteractor,
- fromLockscreenTransitionInteractor,
- fromPrimaryBouncerTransitionInteractor,
- )
- }
+ private val underTest = kosmos.windowManagerLockscreenVisibilityInteractor
+ private val testScope = kosmos.testScope
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
@Test
fun surfaceBehindVisibility_switchesToCorrectFlow() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index af38523c..90943de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -46,7 +46,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
@@ -67,7 +67,6 @@
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -83,7 +82,6 @@
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@@ -208,11 +206,7 @@
KeyguardLongPressInteractor(
appContext = mContext,
scope = testScope.backgroundScope,
- transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = TestScope().backgroundScope,
- )
- .keyguardTransitionInteractor,
+ transitionInteractor = kosmos.keyguardTransitionInteractor,
repository = repository,
logger = UiEventLoggerFake(),
featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 310e0b8..f3b9102 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -30,13 +30,12 @@
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.keyguard.TestScopeProvider
import com.android.systemui.SysuiTestCase
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.media.controls.MediaTestUtils
import com.android.systemui.media.controls.models.player.MediaData
@@ -53,6 +52,7 @@
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -93,6 +93,7 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidTestingRunner::class)
class MediaCarouselControllerTest : SysuiTestCase() {
+ val kosmos = testKosmos()
@Mock lateinit var mediaControlPanelFactory: Provider<MediaControlPanel>
@Mock lateinit var panel: MediaControlPanel
@@ -115,7 +116,7 @@
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock lateinit var globalSettings: GlobalSettings
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
@Captor
lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener>
@@ -132,7 +133,6 @@
fun setup() {
MockitoAnnotations.initMocks(this)
context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK))
- transitionRepository = FakeKeyguardTransitionRepository()
bgExecutor = FakeExecutor(clock)
mediaCarouselController =
MediaCarouselController(
@@ -152,11 +152,7 @@
debugLogger,
mediaFlags,
keyguardUpdateMonitor,
- KeyguardTransitionInteractorFactory.create(
- scope = TestScopeProvider.getTestScope().backgroundScope,
- repository = transitionRepository,
- )
- .keyguardTransitionInteractor,
+ kosmos.keyguardTransitionInteractor,
globalSettings
)
verify(configurationController).addCallback(capture(configListener))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 28d35ce..e5ba569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -41,7 +41,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -57,8 +56,6 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import dagger.Lazy;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -70,6 +67,8 @@
import java.util.Optional;
import java.util.concurrent.Executor;
+import dagger.Lazy;
+
/**
* Tests for {@link NavBarHelper}.
*/
@@ -271,8 +270,7 @@
@Test
public void initNavBarHelper_buttonModeNavBar_a11yButtonClickableState() {
when(mAccessibilityManager.getAccessibilityShortcutTargets(
- ShortcutConstants.UserShortcutType.SOFTWARE)).thenReturn(
- createFakeShortcutTargets());
+ AccessibilityManager.ACCESSIBILITY_BUTTON)).thenReturn(createFakeShortcutTargets());
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
@@ -297,8 +295,7 @@
ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
when(mAccessibilityManager.getAccessibilityShortcutTargets(
- ShortcutConstants.UserShortcutType.SOFTWARE)).thenReturn(
- createFakeShortcutTargets());
+ AccessibilityManager.ACCESSIBILITY_BUTTON)).thenReturn(createFakeShortcutTargets());
mAccessibilityServicesStateChangeListener.onAccessibilityServicesStateChanged(
mAccessibilityManager);
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 e8aa8f0..bbae0c9 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 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 @@
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 9ce77e5..deecc5b 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 @@
@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/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 7f4508a..c772ee2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -63,13 +63,9 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository;
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
-import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
-import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -87,7 +83,6 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
@@ -213,45 +208,9 @@
() -> mFromLockscreenTransitionInteractor,
() -> mFromPrimaryBouncerTransitionInteractor);
- mFromLockscreenTransitionInteractor = new FromLockscreenTransitionInteractor(
- keyguardTransitionRepository,
- keyguardTransitionInteractor,
- mTestScope.getBackgroundScope(),
- mKosmos.getTestDispatcher(),
- mKosmos.getTestDispatcher(),
- keyguardInteractor,
- featureFlags,
- shadeRepository,
- powerInteractor,
- new GlanceableHubTransitions(
- mTestScope,
- mKosmos.getTestDispatcher(),
- keyguardTransitionInteractor,
- keyguardTransitionRepository,
- communalInteractor
- ),
- () ->
- new InWindowLauncherUnlockAnimationInteractor(
- new InWindowLauncherUnlockAnimationRepository(),
- mTestScope.getBackgroundScope(),
- keyguardTransitionInteractor,
- () -> new FakeKeyguardSurfaceBehindRepository(),
- mock(ActivityManagerWrapper.class)
- )
- );
-
- mFromPrimaryBouncerTransitionInteractor = new FromPrimaryBouncerTransitionInteractor(
- keyguardTransitionRepository,
- keyguardTransitionInteractor,
- mTestScope.getBackgroundScope(),
- mKosmos.getTestDispatcher(),
- mKosmos.getTestDispatcher(),
- keyguardInteractor,
- communalInteractor,
- featureFlags,
- mKeyguardSecurityModel,
- mSelectedUserInteractor,
- powerInteractor);
+ mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
+ mFromPrimaryBouncerTransitionInteractor =
+ mKosmos.getFromPrimaryBouncerTransitionInteractor();
DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
mock(DeviceEntryUdfpsInteractor.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 7bd9d92..72c52ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -35,7 +35,6 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
-import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
@@ -51,13 +50,9 @@
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository;
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
-import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
-import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -78,7 +73,6 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
import com.android.systemui.shade.transition.ShadeTransitionController;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -242,45 +236,9 @@
() -> mFromLockscreenTransitionInteractor,
() -> mFromPrimaryBouncerTransitionInteractor);
- mFromLockscreenTransitionInteractor = new FromLockscreenTransitionInteractor(
- keyguardTransitionRepository,
- keyguardTransitionInteractor,
- mTestScope.getBackgroundScope(),
- mKosmos.getTestDispatcher(),
- mKosmos.getTestDispatcher(),
- keyguardInteractor,
- featureFlags,
- mShadeRepository,
- powerInteractor,
- new GlanceableHubTransitions(
- mTestScope,
- mKosmos.getTestDispatcher(),
- keyguardTransitionInteractor,
- keyguardTransitionRepository,
- communalInteractor
- ),
- () ->
- new InWindowLauncherUnlockAnimationInteractor(
- new InWindowLauncherUnlockAnimationRepository(),
- mTestScope.getBackgroundScope(),
- keyguardTransitionInteractor,
- FakeKeyguardSurfaceBehindRepository::new,
- mock(ActivityManagerWrapper.class)
- )
- );
-
- mFromPrimaryBouncerTransitionInteractor = new FromPrimaryBouncerTransitionInteractor(
- keyguardTransitionRepository,
- keyguardTransitionInteractor,
- mTestScope.getBackgroundScope(),
- mKosmos.getTestDispatcher(),
- mKosmos.getTestDispatcher(),
- keyguardInteractor,
- communalInteractor,
- featureFlags,
- mock(KeyguardSecurityModel.class),
- mSelectedUserInteractor,
- powerInteractor);
+ mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
+ mFromPrimaryBouncerTransitionInteractor =
+ mKosmos.getFromPrimaryBouncerTransitionInteractor();
ResourcesSplitShadeStateController splitShadeStateController =
new ResourcesSplitShadeStateController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 5450537..8fd9c80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -27,20 +27,17 @@
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions
-import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -74,8 +71,8 @@
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -144,49 +141,8 @@
{ fromLockscreenTransitionInteractor },
{ fromPrimaryBouncerTransitionInteractor }
)
- val communalInteractor = kosmos.communalInteractor
- fromLockscreenTransitionInteractor =
- FromLockscreenTransitionInteractor(
- keyguardTransitionRepository,
- keyguardTransitionInteractor,
- testScope.backgroundScope,
- testDispatcher,
- testDispatcher,
- keyguardInteractor,
- featureFlags,
- shadeRepository,
- powerInteractor,
- GlanceableHubTransitions(
- testScope,
- testDispatcher,
- keyguardTransitionInteractor,
- keyguardTransitionRepository,
- communalInteractor
- ),
- {
- InWindowLauncherUnlockAnimationInteractor(
- InWindowLauncherUnlockAnimationRepository(),
- testScope,
- keyguardTransitionInteractor,
- { FakeKeyguardSurfaceBehindRepository() },
- mock(),
- )
- }
- )
- fromPrimaryBouncerTransitionInteractor =
- FromPrimaryBouncerTransitionInteractor(
- keyguardTransitionRepository,
- keyguardTransitionInteractor,
- testScope.backgroundScope,
- testDispatcher,
- testDispatcher,
- keyguardInteractor,
- communalInteractor,
- featureFlags,
- mock(),
- mock(),
- powerInteractor
- )
+ fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
+ fromPrimaryBouncerTransitionInteractor = kosmos.fromPrimaryBouncerTransitionInteractor
whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(emptyFlow())
shadeInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index cb45315..4015361 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -79,6 +79,7 @@
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -222,7 +223,8 @@
StandardTestDispatcher(null, null),
() -> mock(WindowManagerLockscreenVisibilityInteractor.class),
() -> mock(KeyguardDismissActionInteractor.class),
- mSelectedUserInteractor) {
+ mSelectedUserInteractor,
+ () -> mock(KeyguardSurfaceBehindInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -730,7 +732,8 @@
StandardTestDispatcher(null, null),
() -> mock(WindowManagerLockscreenVisibilityInteractor.class),
() -> mock(KeyguardDismissActionInteractor.class),
- mSelectedUserInteractor) {
+ mSelectedUserInteractor,
+ () -> mock(KeyguardSurfaceBehindInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
new file mode 100644
index 0000000..98be163
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.statusbar.policy
+
+import android.app.IActivityManager
+import android.media.projection.MediaProjectionManager
+import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.server.notification.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@DisableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+class SensitiveNotificationProtectionControllerFlagDisabledTest : SysuiTestCase() {
+ @Mock private lateinit var handler: Handler
+ @Mock private lateinit var activityManager: IActivityManager
+ @Mock private lateinit var mediaProjectionManager: MediaProjectionManager
+ private lateinit var controller: SensitiveNotificationProtectionControllerImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ controller =
+ SensitiveNotificationProtectionControllerImpl(
+ mContext,
+ mediaProjectionManager,
+ activityManager,
+ handler,
+ FakeExecutor(FakeSystemClock())
+ )
+ }
+
+ @Test
+ fun init_noRegisterMediaProjectionManagerCallback() {
+ verifyZeroInteractions(mediaProjectionManager)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index 9919c6b..a1aff48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -17,88 +17,93 @@
package com.android.systemui.statusbar.policy
import android.app.ActivityOptions
+import android.app.IActivityManager
import android.app.Notification
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
-import android.os.Handler
+import android.platform.test.annotations.EnableFlags
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.server.notification.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.concurrency.mockExecutorHandler
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
-import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.mock
-import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+@EnableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
- @Mock private lateinit var handler: Handler
-
+ @Mock private lateinit var activityManager: IActivityManager
@Mock private lateinit var mediaProjectionManager: MediaProjectionManager
-
@Mock private lateinit var mediaProjectionInfo: MediaProjectionInfo
-
@Mock private lateinit var listener1: Runnable
@Mock private lateinit var listener2: Runnable
@Mock private lateinit var listener3: Runnable
- @Captor
- private lateinit var mediaProjectionCallbackCaptor:
- ArgumentCaptor<MediaProjectionManager.Callback>
-
+ private lateinit var mediaProjectionCallback: MediaProjectionManager.Callback
private lateinit var controller: SensitiveNotificationProtectionControllerImpl
@Before
fun setUp() {
+ allowTestableLooperAsMainThread() // for updating exempt packages and notifying listeners
MockitoAnnotations.initMocks(this)
- mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
setShareFullScreen()
+ whenever(activityManager.bugreportWhitelistedPackages)
+ .thenReturn(listOf(BUGREPORT_PACKAGE_NAME))
- controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
+ val executor = FakeExecutor(FakeSystemClock())
+
+ controller =
+ SensitiveNotificationProtectionControllerImpl(
+ mContext,
+ mediaProjectionManager,
+ activityManager,
+ mockExecutorHandler(executor),
+ executor
+ )
+
+ // Process exemption processing
+ executor.runAllReady()
// Obtain useful MediaProjectionCallback
- verify(mediaProjectionManager).addCallback(mediaProjectionCallbackCaptor.capture(), any())
+ mediaProjectionCallback = withArgCaptor {
+ verify(mediaProjectionManager).addCallback(capture(), any())
+ }
}
@Test
- fun init_flagEnabled_registerMediaProjectionManagerCallback() {
- assertNotNull(mediaProjectionCallbackCaptor.value)
- }
-
- @Test
- fun init_flagDisabled_noRegisterMediaProjectionManagerCallback() {
- mSetFlagsRule.disableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
- reset(mediaProjectionManager)
-
- controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
-
- verifyZeroInteractions(mediaProjectionManager)
+ fun init_registerMediaProjectionManagerCallback() {
+ assertNotNull(mediaProjectionCallback)
}
@Test
fun registerSensitiveStateListener_singleListener() {
controller.registerSensitiveStateListener(listener1)
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
verify(listener1, times(2)).run()
}
@@ -108,8 +113,8 @@
controller.registerSensitiveStateListener(listener1)
controller.registerSensitiveStateListener(listener2)
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
verify(listener1, times(2)).run()
verify(listener2, times(2)).run()
@@ -117,12 +122,12 @@
@Test
fun registerSensitiveStateListener_afterProjectionActive() {
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
controller.registerSensitiveStateListener(listener1)
verifyZeroInteractions(listener1)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
verify(listener1).run()
}
@@ -131,15 +136,15 @@
fun unregisterSensitiveStateListener_singleListener() {
controller.registerSensitiveStateListener(listener1)
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
verify(listener1, times(2)).run()
controller.unregisterSensitiveStateListener(listener1)
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
verifyNoMoreInteractions(listener1)
}
@@ -150,8 +155,8 @@
controller.registerSensitiveStateListener(listener2)
controller.registerSensitiveStateListener(listener3)
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
verify(listener1, times(2)).run()
verify(listener2, times(2)).run()
@@ -160,8 +165,8 @@
controller.unregisterSensitiveStateListener(listener1)
controller.unregisterSensitiveStateListener(listener2)
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
verifyNoMoreInteractions(listener1)
verifyNoMoreInteractions(listener2)
@@ -175,24 +180,24 @@
@Test
fun isSensitiveStateActive_projectionActive_true() {
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
assertTrue(controller.isSensitiveStateActive)
}
@Test
fun isSensitiveStateActive_projectionInactiveAfterActive_false() {
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
assertFalse(controller.isSensitiveStateActive)
}
@Test
fun isSensitiveStateActive_projectionActiveAfterInactive_true() {
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStop(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
assertTrue(controller.isSensitiveStateActive)
}
@@ -200,7 +205,25 @@
@Test
fun isSensitiveStateActive_projectionActive_singleActivity_false() {
setShareSingleApp()
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ assertFalse(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionActive_sysuiExempt_false() {
+ // SystemUi context packge name is exempt, but in test scenarios its
+ // com.android.systemui.tests so use that instead of hardcoding
+ whenever(mediaProjectionInfo.packageName).thenReturn(mContext.packageName)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ assertFalse(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionActive_bugReportHandlerExempt_false() {
+ whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
assertFalse(controller.isSensitiveStateActive)
}
@@ -215,7 +238,7 @@
@Test
fun shouldProtectNotification_projectionActive_singleActivity_false() {
setShareSingleApp()
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
@@ -224,7 +247,7 @@
@Test
fun shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_false() {
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
val notificationEntry = setupFgsNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
@@ -233,7 +256,7 @@
@Test
fun shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_true() {
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
val notificationEntry = setupFgsNotificationEntry(TEST_PACKAGE_NAME)
@@ -242,21 +265,43 @@
@Test
fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
- mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
assertTrue(controller.shouldProtectNotification(notificationEntry))
}
+ @Test
+ fun shouldProtectNotification_projectionActive_sysuiExempt_false() {
+ // SystemUi context packge name is exempt, but in test scenarios its
+ // com.android.systemui.tests so use that instead of hardcoding
+ whenever(mediaProjectionInfo.packageName).thenReturn(mContext.packageName)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
+ fun shouldProtectNotification_projectionActive_bugReportHandlerExempt_false() {
+ whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
private fun setShareFullScreen() {
- `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
- `when`(mediaProjectionInfo.launchCookie).thenReturn(null)
+ whenever(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+ whenever(mediaProjectionInfo.launchCookie).thenReturn(null)
}
private fun setShareSingleApp() {
- `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
- `when`(mediaProjectionInfo.launchCookie).thenReturn(ActivityOptions.LaunchCookie())
+ whenever(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+ whenever(mediaProjectionInfo.launchCookie).thenReturn(ActivityOptions.LaunchCookie())
}
private fun setupNotificationEntry(
@@ -266,10 +311,10 @@
val notificationEntry = mock(NotificationEntry::class.java)
val sbn = mock(StatusBarNotification::class.java)
val notification = mock(Notification::class.java)
- `when`(notificationEntry.sbn).thenReturn(sbn)
- `when`(sbn.packageName).thenReturn(packageName)
- `when`(sbn.notification).thenReturn(notification)
- `when`(notification.isFgsOrUij).thenReturn(isFgs)
+ whenever(notificationEntry.sbn).thenReturn(sbn)
+ whenever(sbn.packageName).thenReturn(packageName)
+ whenever(sbn.notification).thenReturn(notification)
+ whenever(notification.isFgsOrUij).thenReturn(isFgs)
return notificationEntry
}
@@ -282,5 +327,6 @@
private const val TEST_PROJECTION_PACKAGE_NAME =
"com.android.systemui.statusbar.policy.projectionpackage"
private const val TEST_PACKAGE_NAME = "com.android.systemui.statusbar.policy.testpackage"
+ private const val BUGREPORT_PACKAGE_NAME = "com.android.test.bugreporthandler"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/TestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/util/TestUtils.kt
new file mode 100644
index 0000000..27cd4b6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/TestUtils.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.util
+
+import junit.framework.Assert
+
+/**
+ * Assert that a list's values match the corresponding predicates.
+ *
+ * Useful to test animations or more complex objects where you only care about some of an object's
+ * properties.
+ */
+fun <T> List<T>.assertValuesMatch(vararg matchers: (T) -> Boolean) {
+ if (size != matchers.size) {
+ Assert.fail("Expected size ${matchers.size}, but was size $size:\n$this")
+ }
+
+ for (i in indices) {
+ if (!matchers[i].invoke(this[i])) {
+ Assert.fail("Assertion failed. Element #$i did not match:\n${this[i]}")
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index f4b36659..1d428c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -91,7 +91,6 @@
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.KeyguardSecurityModel;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
@@ -108,13 +107,9 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository;
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
-import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
-import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -138,7 +133,6 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationEntryHelper;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -446,45 +440,9 @@
() -> mFromPrimaryBouncerTransitionInteractor);
CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
- mFromLockscreenTransitionInteractor = new FromLockscreenTransitionInteractor(
- keyguardTransitionRepository,
- keyguardTransitionInteractor,
- mTestScope.getBackgroundScope(),
- mKosmos.getTestDispatcher(),
- mKosmos.getTestDispatcher(),
- keyguardInteractor,
- featureFlags,
- shadeRepository,
- powerInteractor,
- new GlanceableHubTransitions(
- mTestScope,
- mKosmos.getTestDispatcher(),
- keyguardTransitionInteractor,
- keyguardTransitionRepository,
- communalInteractor
- ),
- () ->
- new InWindowLauncherUnlockAnimationInteractor(
- new InWindowLauncherUnlockAnimationRepository(),
- mTestScope.getBackgroundScope(),
- keyguardTransitionInteractor,
- FakeKeyguardSurfaceBehindRepository::new,
- mock(ActivityManagerWrapper.class)
- )
- );
-
- mFromPrimaryBouncerTransitionInteractor = new FromPrimaryBouncerTransitionInteractor(
- keyguardTransitionRepository,
- keyguardTransitionInteractor,
- mTestScope.getBackgroundScope(),
- mKosmos.getTestDispatcher(),
- mKosmos.getTestDispatcher(),
- keyguardInteractor,
- communalInteractor,
- featureFlags,
- mock(KeyguardSecurityModel.class),
- mSelectedUserInteractor,
- powerInteractor);
+ mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
+ mFromPrimaryBouncerTransitionInteractor =
+ mKosmos.getFromPrimaryBouncerTransitionInteractor();
ResourcesSplitShadeStateController splitShadeStateController =
new ResourcesSplitShadeStateController();
diff --git a/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt
index 9059da2..20b9e84 100644
--- a/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt
@@ -20,4 +20,4 @@
import com.android.systemui.kosmos.Kosmos
var Kosmos.uiEventLogger: UiEventLogger by Kosmos.Fixture { uiEventLoggerFake }
-val Kosmos.uiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() }
+var Kosmos.uiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/keyguard/logging/ScrimLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/keyguard/logging/ScrimLoggerKosmos.kt
new file mode 100644
index 0000000..901bdcc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/keyguard/logging/ScrimLoggerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.keyguard.logging
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.scrimLogger by Kosmos.Fixture { mock<ScrimLogger>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
index 7b36a29..365d97f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
@@ -35,6 +35,7 @@
FakeFeatureFlagsClassic().apply {
set(Flags.FULL_SCREEN_USER_SWITCHER, false)
set(Flags.NSSL_DEBUG_LINES, false)
+ set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/LightRevealScrimRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/LightRevealScrimRepositoryKosmos.kt
new file mode 100644
index 0000000..046946e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/LightRevealScrimRepositoryKosmos.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.keyguard.data
+
+import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.lightRevealScrimRepository by Kosmos.Fixture { fakeLightRevealScrimRepository }
+
+var Kosmos.fakeLightRevealScrimRepository by Kosmos.Fixture { FakeLightRevealScrimRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueueKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueueKosmos.kt
new file mode 100644
index 0000000..d0a0e1d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueueKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeCommandQueue by Kosmos.Fixture { FakeCommandQueue() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
index 008f79a..408157b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
@@ -20,4 +20,4 @@
var Kosmos.keyguardTransitionRepository: KeyguardTransitionRepository by
Kosmos.Fixture { fakeKeyguardTransitionRepository }
-val Kosmos.fakeKeyguardTransitionRepository by Kosmos.Fixture { FakeKeyguardTransitionRepository() }
+var Kosmos.fakeKeyguardTransitionRepository by Kosmos.Fixture { FakeKeyguardTransitionRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index 3b38342..3b52676 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -23,9 +23,8 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.shade.data.repository.shadeRepository
-import dagger.Lazy
-val Kosmos.fromLockscreenTransitionInteractor by
+var Kosmos.fromLockscreenTransitionInteractor by
Kosmos.Fixture {
FromLockscreenTransitionInteractor(
transitionRepository = keyguardTransitionRepository,
@@ -38,7 +37,6 @@
shadeRepository = shadeRepository,
powerInteractor = powerInteractor,
glanceableHubTransitions = glanceableHubTransitions,
- inWindowLauncherUnlockAnimationInteractor =
- Lazy { inWindowLauncherUnlockAnimationInteractor },
+ swipeToDismissInteractor = swipeToDismissInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index 719686e..6b76449 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -26,7 +26,7 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.user.domain.interactor.selectedUserInteractor
-val Kosmos.fromPrimaryBouncerTransitionInteractor by
+var Kosmos.fromPrimaryBouncerTransitionInteractor by
Kosmos.Fixture {
FromPrimaryBouncerTransitionInteractor(
transitionRepository = keyguardTransitionRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
index 638a6a3..c06f833 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
@@ -18,12 +18,12 @@
import android.content.applicationContext
import android.view.accessibility.accessibilityManagerWrapper
+import com.android.internal.logging.uiEventLogger
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.qs.uiEventLogger
val Kosmos.keyguardLongPressInteractor by
Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt
new file mode 100644
index 0000000..a646bc6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.keyguard.data.repository.keyguardSurfaceBehindRepository
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.keyguardSurfaceBehindInteractor by
+ Kosmos.Fixture {
+ KeyguardSurfaceBehindInteractor(
+ repository = keyguardSurfaceBehindRepository,
+ context = applicationContext,
+ transitionInteractor = keyguardTransitionInteractor,
+ inWindowLauncherUnlockAnimationInteractor = {
+ inWindowLauncherUnlockAnimationInteractor
+ },
+ swipeToDismissInteractor = swipeToDismissInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
deleted file mode 100644
index 5cf656c..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.keyguard.domain.interactor
-
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.util.mockito.mock
-import dagger.Lazy
-import kotlinx.coroutines.CoroutineScope
-
-/**
- * Helper to create a new KeyguardTransitionInteractor in a way that doesn't require modifying 20+
- * tests whenever we add a constructor param.
- */
-object KeyguardTransitionInteractorFactory {
- @JvmOverloads
- @JvmStatic
- fun create(
- scope: CoroutineScope,
- repository: FakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository(),
- featureFlags: FakeFeatureFlags = FakeFeatureFlagsClassic(),
- keyguardInteractor: KeyguardInteractor =
- KeyguardInteractorFactory.create(featureFlags = featureFlags).keyguardInteractor,
- fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor> = Lazy {
- mock<FromLockscreenTransitionInteractor>()
- },
- fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor> =
- Lazy {
- mock<FromPrimaryBouncerTransitionInteractor>()
- },
- ): WithDependencies {
- return WithDependencies(
- repository = repository,
- keyguardInteractor = keyguardInteractor,
- fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
- fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
- KeyguardTransitionInteractor(
- scope = scope,
- repository = repository,
- keyguardInteractor = { keyguardInteractor },
- fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
- fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
- )
- )
- }
-
- data class WithDependencies(
- val repository: FakeKeyguardTransitionRepository,
- val keyguardInteractor: KeyguardInteractor,
- val fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor>,
- val fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor>,
- val keyguardTransitionInteractor: KeyguardTransitionInteractor,
- )
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
new file mode 100644
index 0000000..58e0a3b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.keyguard.logging.scrimLogger
+import com.android.systemui.keyguard.data.lightRevealScrimRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.lightRevealScrimInteractor by
+ Kosmos.Fixture {
+ LightRevealScrimInteractor(
+ keyguardTransitionInteractor,
+ lightRevealScrimRepository,
+ applicationCoroutineScope,
+ scrimLogger,
+ powerInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorKosmos.kt
new file mode 100644
index 0000000..25aad04
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.shade.data.repository.shadeRepository
+
+val Kosmos.swipeToDismissInteractor by
+ Kosmos.Fixture {
+ SwipeToDismissInteractor(
+ backgroundScope = applicationCoroutineScope,
+ shadeRepository = shadeRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ keyguardInteractor = keyguardInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
new file mode 100644
index 0000000..0207280
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.windowManagerLockscreenVisibilityInteractor by
+ Kosmos.Fixture {
+ WindowManagerLockscreenVisibilityInteractor(
+ keyguardInteractor = keyguardInteractor,
+ transitionInteractor = keyguardTransitionInteractor,
+ surfaceBehindInteractor = keyguardSurfaceBehindInteractor,
+ fromLockscreenInteractor = fromLockscreenTransitionInteractor,
+ fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index b9a3d38..10305f7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -33,6 +33,8 @@
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.model.sceneContainerPlugin
import com.android.systemui.plugins.statusbar.statusBarStateController
@@ -84,6 +86,10 @@
val sceneContainerPlugin by lazy { kosmos.sceneContainerPlugin }
val deviceProvisioningInteractor by lazy { kosmos.deviceProvisioningInteractor }
val fakeDeviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository }
+ val fromLockscreenTransitionInteractor by lazy { kosmos.fromLockscreenTransitionInteractor }
+ val fromPrimaryBouncerTransitionInteractor by lazy {
+ kosmos.fromPrimaryBouncerTransitionInteractor
+ }
init {
kosmos.applicationContext = testCase.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 73b7c50..be559ef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -16,9 +16,9 @@
package com.android.systemui.plugins.statusbar
+import com.android.internal.logging.uiEventLogger
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.uiEventLogger
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.StatusBarStateControllerImpl
import com.android.systemui.util.mockito.mock
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
index 6332c1a..23d657d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -16,7 +16,7 @@
package com.android.systemui.qs
-import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.plugins.qs.QSFactory
@@ -24,9 +24,8 @@
val Kosmos.instanceIdSequenceFake: InstanceIdSequenceFake by
Kosmos.Fixture { InstanceIdSequenceFake(0) }
-val Kosmos.uiEventLogger: UiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() }
val Kosmos.qsEventLogger: QsEventLoggerFake by
- Kosmos.Fixture { QsEventLoggerFake(uiEventLogger, instanceIdSequenceFake) }
+ Kosmos.Fixture { QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake) }
var Kosmos.newQSTileFactory by Kosmos.Fixture<NewQSTileFactory>()
var Kosmos.qsTileFactory by Kosmos.Fixture<QSFactory>()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index f7005ab..4aab822 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -22,6 +22,7 @@
import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
/** Fake implementation of [ShadeRepository] */
@SysUISingleton
@@ -32,6 +33,9 @@
private val _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
override val udfpsTransitionToFullShadeProgress = _udfpsTransitionToFullShadeProgress
+ private val _currentFling: MutableStateFlow<FlingInfo?> = MutableStateFlow(null)
+ override val currentFling: StateFlow<FlingInfo?> = _currentFling
+
private val _lockscreenShadeExpansion = MutableStateFlow(0f)
override val lockscreenShadeExpansion = _lockscreenShadeExpansion
@@ -115,6 +119,10 @@
_udfpsTransitionToFullShadeProgress.value = progress
}
+ override fun setCurrentFling(info: FlingInfo?) {
+ _currentFling.value = info
+ }
+
override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) {
_lockscreenShadeExpansion.value = lockscreenShadeExpansion
}
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index 81fd8ce..32b7020 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 44682e2..f902439 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -52,6 +52,13 @@
}
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/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 44144f8..c58cb72 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -31,7 +31,10 @@
import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static android.view.accessibility.AccessibilityManager.FlashNotificationReason;
+import static android.view.accessibility.AccessibilityManager.ShortcutType;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
@@ -140,7 +143,6 @@
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkFeatureInfo;
import com.android.internal.accessibility.AccessibilityShortcutController.LaunchableFrameworkFeatureInfo;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
import com.android.internal.accessibility.util.AccessibilityUtils;
@@ -1720,7 +1722,7 @@
}
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::performAccessibilityShortcutInternal, this,
- displayId, ShortcutConstants.UserShortcutType.SOFTWARE, targetName));
+ displayId, ACCESSIBILITY_BUTTON, targetName));
}
/**
@@ -2199,12 +2201,11 @@
}
private void showAccessibilityTargetsSelection(int displayId,
- @ShortcutConstants.UserShortcutType int shortcutType) {
+ @ShortcutType int shortcutType) {
final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
- final String chooserClassName =
- (shortcutType == ShortcutConstants.UserShortcutType.HARDWARE)
- ? AccessibilityShortcutChooserActivity.class.getName()
- : AccessibilityButtonChooserActivity.class.getName();
+ final String chooserClassName = (shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
+ ? AccessibilityShortcutChooserActivity.class.getName()
+ : AccessibilityButtonChooserActivity.class.getName();
intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
@@ -3275,7 +3276,7 @@
}
final Set<String> currentTargets =
- userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.HARDWARE);
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
if (targetsFromSetting.equals(currentTargets)) {
return false;
}
@@ -3291,7 +3292,7 @@
userState.mUserId, str -> str, targetsFromSetting);
final Set<String> currentTargets =
- userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.SOFTWARE);
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
if (targetsFromSetting.equals(currentTargets)) {
return false;
}
@@ -3346,7 +3347,7 @@
*/
private void updateAccessibilityShortcutKeyTargetsLocked(AccessibilityUserState userState) {
final Set<String> currentTargets =
- userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.HARDWARE);
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
final int lastSize = currentTargets.size();
if (lastSize == 0) {
return;
@@ -3531,7 +3532,7 @@
}
final Set<String> currentTargets =
- userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.SOFTWARE);
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
final int lastSize = currentTargets.size();
if (lastSize == 0) {
return;
@@ -3571,7 +3572,7 @@
return;
}
final Set<String> buttonTargets =
- userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.SOFTWARE);
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
int lastSize = buttonTargets.size();
buttonTargets.removeIf(name -> {
if (packageName != null && name != null && !name.contains(packageName)) {
@@ -3608,7 +3609,7 @@
lastSize = buttonTargets.size();
final Set<String> shortcutKeyTargets =
- userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.HARDWARE);
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
userState.mEnabledServices.forEach(componentName -> {
if (packageName != null && componentName != null
&& !packageName.equals(componentName.getPackageName())) {
@@ -3665,18 +3666,16 @@
return;
}
final ComponentName serviceName = service.getComponentName();
- if (userState.removeShortcutTargetLocked(
- ShortcutConstants.UserShortcutType.HARDWARE, serviceName)) {
+ if (userState.removeShortcutTargetLocked(ACCESSIBILITY_SHORTCUT_KEY, serviceName)) {
final Set<String> currentTargets = userState.getShortcutTargetsLocked(
- ShortcutConstants.UserShortcutType.HARDWARE);
+ ACCESSIBILITY_SHORTCUT_KEY);
persistColonDelimitedSetToSettingLocked(
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
userState.mUserId, currentTargets, str -> str);
}
- if (userState.removeShortcutTargetLocked(
- ShortcutConstants.UserShortcutType.SOFTWARE, serviceName)) {
+ if (userState.removeShortcutTargetLocked(ACCESSIBILITY_BUTTON, serviceName)) {
final Set<String> currentTargets = userState.getShortcutTargetsLocked(
- ShortcutConstants.UserShortcutType.SOFTWARE);
+ ACCESSIBILITY_BUTTON);
persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
userState.mUserId, currentTargets, str -> str);
}
@@ -3752,7 +3751,7 @@
}
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::performAccessibilityShortcutInternal, this,
- Display.DEFAULT_DISPLAY, ShortcutConstants.UserShortcutType.HARDWARE, targetName));
+ Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY, targetName));
}
/**
@@ -3765,7 +3764,7 @@
* specified target.
*/
private void performAccessibilityShortcutInternal(int displayId,
- @ShortcutConstants.UserShortcutType int shortcutType, @Nullable String targetName) {
+ @ShortcutType int shortcutType, @Nullable String targetName) {
final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType);
if (shortcutTargets.isEmpty()) {
Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType);
@@ -3816,7 +3815,7 @@
}
private boolean performAccessibilityFrameworkFeature(int displayId,
- ComponentName assignedTarget, @ShortcutConstants.UserShortcutType int shortcutType) {
+ ComponentName assignedTarget, @ShortcutType int shortcutType) {
final Map<ComponentName, FrameworkFeatureInfo> frameworkFeatureMap =
AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
if (!frameworkFeatureMap.containsKey(assignedTarget)) {
@@ -3865,12 +3864,12 @@
/**
* Perform accessibility service shortcut action.
*
- * 1) For {@link ShortcutConstants.UserShortcutType.SOFTWARE} type and services targeting sdk
+ * 1) For {@link AccessibilityManager#ACCESSIBILITY_BUTTON} type and services targeting sdk
* version <= Q: callbacks to accessibility service if service is bounded and requests
* accessibility button.
- * 2) For {@link ShortcutConstants.UserShortcutType.HARDWARE} type and service targeting sdk
+ * 2) For {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY} type and service targeting sdk
* version <= Q: turns on / off the accessibility service.
- * 3) For {@link ShortcutConstants.UserShortcutType.HARDWARE} type and service targeting sdk
+ * 3) For {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY} type and service targeting sdk
* version > Q and request accessibility button: turn on the accessibility service if it's
* not in the enabled state.
* (It'll happen when a service is disabled and assigned to shortcut then upgraded.)
@@ -3881,7 +3880,7 @@
* button.
*/
private boolean performAccessibilityShortcutTargetService(int displayId,
- @ShortcutConstants.UserShortcutType int shortcutType, ComponentName assignedTarget) {
+ @ShortcutType int shortcutType, ComponentName assignedTarget) {
synchronized (mLock) {
final AccessibilityUserState userState = getCurrentUserStateLocked();
final AccessibilityServiceInfo installedServiceInfo =
@@ -3899,8 +3898,7 @@
final boolean requestA11yButton = (installedServiceInfo.flags
& FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
// Turns on / off the accessibility service
- if ((targetSdk <= Build.VERSION_CODES.Q
- && shortcutType == ShortcutConstants.UserShortcutType.HARDWARE)
+ if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
|| (targetSdk > Build.VERSION_CODES.Q && !requestA11yButton)) {
if (serviceConnection == null) {
logAccessibilityShortcutActivated(mContext, assignedTarget, shortcutType,
@@ -3914,8 +3912,7 @@
}
return true;
}
- if (shortcutType == ShortcutConstants.UserShortcutType.HARDWARE
- && targetSdk > Build.VERSION_CODES.Q
+ if (shortcutType == ACCESSIBILITY_SHORTCUT_KEY && targetSdk > Build.VERSION_CODES.Q
&& requestA11yButton) {
if (!userState.getEnabledServicesLocked().contains(assignedTarget)) {
enableAccessibilityServiceLocked(assignedTarget, mCurrentUserId);
@@ -3945,8 +3942,7 @@
}
@Override
- public List<String> getAccessibilityShortcutTargets(
- @ShortcutConstants.UserShortcutType int shortcutType) {
+ public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getAccessibilityShortcutTargets",
FLAGS_ACCESSIBILITY_MANAGER, "shortcutType=" + shortcutType);
@@ -3960,13 +3956,12 @@
return getAccessibilityShortcutTargetsInternal(shortcutType);
}
- private List<String> getAccessibilityShortcutTargetsInternal(
- @ShortcutConstants.UserShortcutType int shortcutType) {
+ private List<String> getAccessibilityShortcutTargetsInternal(@ShortcutType int shortcutType) {
synchronized (mLock) {
final AccessibilityUserState userState = getCurrentUserStateLocked();
final ArrayList<String> shortcutTargets = new ArrayList<>(
userState.getShortcutTargetsLocked(shortcutType));
- if (shortcutType != ShortcutConstants.UserShortcutType.SOFTWARE) {
+ if (shortcutType != ACCESSIBILITY_BUTTON) {
return shortcutTargets;
}
// Adds legacy a11y services requesting a11y button into the list.
@@ -4429,7 +4424,7 @@
}
}
// Warning is not required if the service is already assigned to a shortcut.
- for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
+ for (int shortcutType : AccessibilityManager.SHORTCUT_TYPES) {
if (getAccessibilityShortcutTargets(shortcutType).contains(
componentName.flattenToString())) {
return false;
@@ -4609,8 +4604,8 @@
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- new AccessibilityShellCommand(this, mSystemActionPerformer)
- .exec(this, in, out, err, args, callback, resultReceiver);
+ new AccessibilityShellCommand(this, mSystemActionPerformer).exec(this, in, out, err, args,
+ callback, resultReceiver);
}
private final class InteractionBridge {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 41165b6..68ee780 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -24,6 +24,9 @@
import static android.accessibilityservice.AccessibilityService.SHOW_MODE_MASK;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static android.view.accessibility.AccessibilityManager.ShortcutType;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -48,7 +51,6 @@
import com.android.internal.R;
import com.android.internal.accessibility.AccessibilityShortcutController;
-import com.android.internal.accessibility.common.ShortcutConstants;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -755,21 +757,13 @@
* @param shortcutType The shortcut type.
* @return The array set of the strings
*/
- public ArraySet<String> getShortcutTargetsLocked(
- @ShortcutConstants.UserShortcutType int shortcutType) {
- if (shortcutType == ShortcutConstants.UserShortcutType.HARDWARE) {
+ public ArraySet<String> getShortcutTargetsLocked(@ShortcutType int shortcutType) {
+ if (shortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
return mAccessibilityShortcutKeyTargets;
- } else if (shortcutType == ShortcutConstants.UserShortcutType.SOFTWARE) {
+ } else if (shortcutType == ACCESSIBILITY_BUTTON) {
return mAccessibilityButtonTargets;
- } else if ((shortcutType == ShortcutConstants.UserShortcutType.TRIPLETAP
- && isMagnificationSingleFingerTripleTapEnabledLocked()) || (
- shortcutType == ShortcutConstants.UserShortcutType.TWO_FINGERS_TRIPLE_TAP
- && isMagnificationTwoFingerTripleTapEnabledLocked())) {
- ArraySet<String> targets = new ArraySet<>();
- targets.add(MAGNIFICATION_CONTROLLER_NAME);
- return targets;
}
- return new ArraySet<>();
+ return null;
}
/**
@@ -808,22 +802,12 @@
/**
* Removes given shortcut target in the list.
*
- * @param shortcutType one of {@link ShortcutConstants.UserShortcutType.HARDWARE} or
- * {@link ShortcutConstants.UserShortcutType.SOFTWARE}. Other types are not
- * implemented.
- * @param target The component name of the shortcut target.
+ * @param shortcutType The shortcut type.
+ * @param target The component name of the shortcut target.
* @return true if the shortcut target is removed.
*/
- public boolean removeShortcutTargetLocked(@ShortcutConstants.UserShortcutType int shortcutType,
+ public boolean removeShortcutTargetLocked(@ShortcutType int shortcutType,
ComponentName target) {
- if (shortcutType == ShortcutConstants.UserShortcutType.TRIPLETAP
- || shortcutType == ShortcutConstants.UserShortcutType.TWO_FINGERS_TRIPLE_TAP) {
- throw new UnsupportedOperationException(
- "removeShortcutTargetLocked only support shortcut type: "
- + "software and hardware for now"
- );
- }
-
return getShortcutTargetsLocked(shortcutType).removeIf(name -> {
ComponentName componentName;
if (name == null
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 805f6e3..351760b 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 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.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.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 @@
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 @@
DisplayMagnification(int displayId) {
mDisplayId = displayId;
- mSpecAnimationBridge = new SpecAnimationBridge(mControllerCtx, mLock, mDisplayId);
+ mSpecAnimationBridge =
+ new SpecAnimationBridge(
+ mControllerCtx,
+ mLock,
+ mDisplayId,
+ mScrollerSupplier,
+ mTimeAnimatorSupplier);
}
/**
@@ -406,6 +418,52 @@
}
}
+ 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 @@
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 @@
magnificationInfoChangedCallback,
scaleProvider,
/* thumbnailSupplier= */ null,
- backgroundExecutor);
+ backgroundExecutor,
+ () -> new Scroller(context),
+ TimeAnimator::new);
}
/** Constructor for tests */
@@ -849,9 +966,13 @@
@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 @@
}
/**
+ * 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 @@
@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 @@
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 @@
}
}
- 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 @@
}
}
+ @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 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 baae1d93..f4ea754 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.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 @@
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 @@
@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 @@
@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 @@
+ ", 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 @@
@Override
void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (event.getActionMasked() == ACTION_DOWN) {
+ cancelFling();
+ }
+
handleEventWith(mCurrentState, event, rawEvent, policyFlags);
}
@@ -501,6 +530,7 @@
}
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 @@
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 @@
&& 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 @@
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 @@
}
}
+ /** 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 @@
int action = event.getActionMasked();
switch (action) {
case ACTION_UP:
+ onPanningFinished(event);
+ // fall-through!
case ACTION_CANCEL:
mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
mOverscrollHandler.clearEdgeState();
@@ -1770,6 +1868,7 @@
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 586aa8a..af0777c 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.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 @@
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 @@
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 @@
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 5019428..0054bc8 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 @@
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 @@
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 @@
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 @@
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/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 797a2e6..a341b4a 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -1676,7 +1676,7 @@
}
byte[] certificateDigest = null;
try {
- certificateDigest = new Signature(certificateDigestStr).toByteArray();
+ certificateDigest = new Signature(certificateDigestStr.replace(":", "")).toByteArray();
} catch (IllegalArgumentException e) {
Slog.w(TAG, "<" + elementName + "> with invalid sha256-cert-digest in "
+ permFile + " at " + parser.getPositionDescription());
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 10cd6e5..d110349 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1950,7 +1950,8 @@
mService.mNativeDebuggingApp = null;
}
- if (app.info.isEmbeddedDexUsed()) {
+ if (app.info.isEmbeddedDexUsed()
+ || (app.processInfo != null && app.processInfo.useEmbeddedDex)) {
runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
}
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index e90910a..bd3c8e0 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -155,6 +155,16 @@
{ "exclude-annotation": "androidx.test.filters.FlakyTest" },
{ "exclude-annotation": "org.junit.Ignore" }
]
+ },
+ {
+ "file_patterns": ["Broadcast.*"],
+ "name": "CtsContentTestCases",
+ "options": [
+ { "include-filter": "android.content.cts.BroadcastReceiverTest" },
+ { "exclude-annotation": "androidx.test.filters.LargeTest" },
+ { "exclude-annotation": "androidx.test.filters.FlakyTest" },
+ { "exclude-annotation": "org.junit.Ignore" }
+ ]
}
]
}
diff --git a/services/core/java/com/android/server/broadcastradio/OWNERS b/services/core/java/com/android/server/broadcastradio/OWNERS
index d2bdd64..51a85e4 100644
--- a/services/core/java/com/android/server/broadcastradio/OWNERS
+++ b/services/core/java/com/android/server/broadcastradio/OWNERS
@@ -1,3 +1,3 @@
xuweilin@google.com
oscarazu@google.com
-keunyoung@google.com
+ericjeong@google.com
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e930627..7ebc311 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -695,14 +695,14 @@
logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(),
userSerial);
dpc.setBrightnessConfiguration(config, /* shouldResetShortTermModel= */ true);
- // change the brightness value according to the selected user.
- final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked();
- if (device != null) {
- dpc.setBrightness(
- mPersistentDataStore.getBrightness(device, userSerial), userSerial);
- }
}
- dpc.onSwitchUser(newUserId);
+ final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked();
+ float newBrightness = device == null ? PowerManager.BRIGHTNESS_INVALID_FLOAT
+ : mPersistentDataStore.getBrightness(device, userSerial);
+ if (Float.isNaN(newBrightness)) {
+ newBrightness = logicalDisplay.getDisplayInfoLocked().brightnessDefault;
+ }
+ dpc.onSwitchUser(newUserId, userSerial, newBrightness);
});
handleSettingsChange();
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 087cacf..1ca3923 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -685,17 +685,27 @@
}
@Override
- public void onSwitchUser(@UserIdInt int newUserId) {
- Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, newUserId);
- mHandler.sendMessage(msg);
+ public void onSwitchUser(@UserIdInt int newUserId, int userSerial, float newBrightness) {
+ Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, newUserId, userSerial, newBrightness);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
- private void handleOnSwitchUser(@UserIdInt int newUserId) {
- handleSettingsChange(true /* userSwitch */);
+ private void handleOnSwitchUser(@UserIdInt int newUserId, int userSerial, float newBrightness) {
+ Slog.i(mTag, "Switching user newUserId=" + newUserId + " userSerial=" + userSerial
+ + " newBrightness=" + newBrightness);
handleBrightnessModeChange();
if (mBrightnessTracker != null) {
mBrightnessTracker.onSwitchUser(newUserId);
}
+ setBrightness(newBrightness, userSerial);
+
+ // Don't treat user switches as user initiated change.
+ mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(newBrightness);
+
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.resetShortTermModel();
+ }
+ sendUpdatePowerState();
}
@Nullable
@@ -2394,20 +2404,11 @@
MetricsLogger.action(log);
}
- private void handleSettingsChange(boolean userSwitch) {
+ private void handleSettingsChange() {
mDisplayBrightnessController
.setPendingScreenBrightness(mDisplayBrightnessController
.getScreenBrightnessSetting());
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(userSwitch);
- if (userSwitch) {
- // Don't treat user switches as user initiated change.
- mDisplayBrightnessController
- .setAndNotifyCurrentScreenBrightness(mDisplayBrightnessController
- .getPendingScreenBrightness());
- if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.resetShortTermModel();
- }
- }
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
sendUpdatePowerState();
}
@@ -2416,11 +2417,8 @@
mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
- mHandler.postAtTime(() -> {
- mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
- == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- updatePowerState();
- }, mClock.uptimeMillis());
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
+ == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
}
@@ -2430,9 +2428,13 @@
}
@Override
- public void setBrightness(float brightnessValue, int userSerial) {
- mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightnessValue),
- userSerial);
+ public void setBrightness(float brightness) {
+ mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness));
+ }
+
+ @Override
+ public void setBrightness(float brightness, int userSerial) {
+ mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness), userSerial);
}
@Override
@@ -2966,7 +2968,7 @@
if (mStopped) {
return;
}
- handleSettingsChange(false /*userSwitch*/);
+ handleSettingsChange();
break;
case MSG_UPDATE_RBC:
@@ -2985,7 +2987,9 @@
break;
case MSG_SWITCH_USER:
- handleOnSwitchUser(msg.arg1);
+ float newBrightness = msg.obj instanceof Float ? (float) msg.obj
+ : PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ handleOnSwitchUser(msg.arg1, msg.arg2, newBrightness);
break;
case MSG_BOOT_COMPLETED:
@@ -3023,7 +3027,10 @@
@Override
public void onChange(boolean selfChange, Uri uri) {
if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
- handleBrightnessModeChange();
+ mHandler.postAtTime(() -> {
+ handleBrightnessModeChange();
+ updatePowerState();
+ }, mClock.uptimeMillis());
} else if (uri.equals(Settings.System.getUriFor(
Settings.System.SCREEN_BRIGHTNESS_FOR_ALS))) {
int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
@@ -3035,7 +3042,7 @@
setUpAutoBrightness(mContext, mHandler);
sendUpdatePowerState();
} else {
- handleSettingsChange(false /* userSwitch */);
+ handleSettingsChange();
}
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 13acb3f..ecf1635 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -30,7 +30,6 @@
* An interface to manage the display's power state and brightness
*/
public interface DisplayPowerControllerInterface {
- int DEFAULT_USER_SERIAL = -1;
/**
* Notified when the display is changed.
*
@@ -100,15 +99,12 @@
* Set the screen brightness of the associated display
* @param brightness The value to which the brightness is to be set
*/
- default void setBrightness(float brightness) {
- setBrightness(brightness, DEFAULT_USER_SERIAL);
- }
+ void setBrightness(float brightness);
/**
* Set the screen brightness of the associated display
* @param brightness The value to which the brightness is to be set
- * @param userSerial The user for which the brightness value is to be set. Use userSerial = -1,
- * if brightness needs to be updated for the current user.
+ * @param userSerial The user for which the brightness value is to be set.
*/
void setBrightness(float brightness, int userSerial);
@@ -188,8 +184,10 @@
/**
* Handles the changes to be done to update the brightness when the user is changed
* @param newUserId The new userId
+ * @param userSerial The serial number of the new user
+ * @param newBrightness The brightness for the new user
*/
- void onSwitchUser(int newUserId);
+ void onSwitchUser(int newUserId, int userSerial, float newBrightness);
/**
* Get the ID of the display associated with this DPC.
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 3bb7986..f6d02db 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -41,7 +41,6 @@
* display. Applies the chosen brightness.
*/
public final class DisplayBrightnessController {
- private static final int DEFAULT_USER_SERIAL = -1;
// The ID of the display tied to this DisplayBrightnessController
private final int mDisplayId;
@@ -302,16 +301,8 @@
* Notifies the brightnessSetting to persist the supplied brightness value.
*/
public void setBrightness(float brightnessValue) {
- setBrightness(brightnessValue, DEFAULT_USER_SERIAL);
- }
-
- /**
- * Notifies the brightnessSetting to persist the supplied brightness value for a user.
- */
- public void setBrightness(float brightnessValue, int userSerial) {
// Update the setting, which will eventually call back into DPC to have us actually
// update the display with the new value.
- mBrightnessSetting.setUserSerial(userSerial);
mBrightnessSetting.setBrightness(brightnessValue);
if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
float nits = convertToNits(brightnessValue);
@@ -322,6 +313,14 @@
}
/**
+ * Notifies the brightnessSetting to persist the supplied brightness value for a user.
+ */
+ public void setBrightness(float brightnessValue, int userSerial) {
+ mBrightnessSetting.setUserSerial(userSerial);
+ setBrightness(brightnessValue);
+ }
+
+ /**
* Sets the current screen brightness, and notifies the BrightnessSetting about the change.
*/
public void updateScreenBrightnessSetting(float brightnessValue) {
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 3c23b5c..3e6e09d 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -203,14 +203,11 @@
* Sets the pending auto-brightness adjustments in the system settings. Executed
* when there is a change in the brightness system setting, or when there is a user switch.
*/
- public void updatePendingAutoBrightnessAdjustments(boolean userSwitch) {
+ public void updatePendingAutoBrightnessAdjustments() {
final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
mPendingAutoBrightnessAdjustment = Float.isNaN(adj) ? Float.NaN
: BrightnessUtils.clampBrightnessAdjustment(adj);
- if (userSwitch) {
- processPendingAutoBrightnessAdjustments();
- }
}
/**
diff --git a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
index 8278600..202c894 100644
--- a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
@@ -98,18 +98,21 @@
localDevice().getService().enableAbsoluteVolumeBehavior(audioStatus);
mState = STATE_MONITOR_AUDIO_STATUS;
} else if (mState == STATE_MONITOR_AUDIO_STATUS) {
- // On TV panels, we notify AudioService even if neither volume nor mute state changed.
- // This ensures that the user sees volume UI if they tried to adjust the AVR's volume,
- // even if the new volume level is the same as the previous one.
- boolean notifyAvbVolumeToShowUi = localDevice().getService().isTvDevice()
- && audioStatus.equals(mLastAudioStatus);
-
- if (audioStatus.getVolume() != mLastAudioStatus.getVolume()
- || notifyAvbVolumeToShowUi) {
+ // Update volume in AudioService if it has changed since the last <Report Audio Status>
+ boolean updateVolume = audioStatus.getVolume() != mLastAudioStatus.getVolume();
+ if (updateVolume) {
localDevice().getService().notifyAvbVolumeChange(audioStatus.getVolume());
}
- if (audioStatus.getMute() != mLastAudioStatus.getMute()) {
+ // Update mute in AudioService if any of the following conditions are met:
+ // - The mute status changed
+ // - The volume changed - we need to make sure mute is set correctly afterwards, since
+ // setting volume can affect mute status as well as a side effect.
+ // - We're a TV panel - we want to trigger volume UI on TV panels, so that the user
+ // always gets visual feedback when they attempt to adjust the AVR's volume/mute.
+ if ((audioStatus.getMute() != mLastAudioStatus.getMute())
+ || updateVolume
+ || localDevice().getService().isTvDevice()) {
localDevice().getService().notifyAvbMuteChange(audioStatus.getMute());
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index a61199a..7726609 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 @@
private InputMethodManagerInternal mInputMethodManagerInternal;
- // Context cache used for loading pointer resources.
- private Context mPointerIconDisplayContext;
-
private final File mDoubleTouchGestureEnableFile;
private WindowManagerCallbacks mWindowManagerCallbacks;
@@ -416,6 +413,8 @@
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 void onDisplayRemoved(int displayId) {
synchronized (mLoadedPointerIconsByDisplayAndType) {
mLoadedPointerIconsByDisplayAndType.remove(displayId);
+ mDisplayContexts.remove(displayId);
}
}
@@ -440,6 +440,7 @@
return;
}
iconsByType.clear();
+ mDisplayContexts.remove(displayId);
}
mNative.reloadPointerIcons();
}
@@ -1323,11 +1324,6 @@
/** 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 @@
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 @@
}
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 @@
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 f852b81..097daf2 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -97,7 +97,7 @@
private static final float DEFAULT_VOLUME = 1.0f;
// TODO (b/291899544): remove for release
private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED = 1;
- private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 0;
+ private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 1;
private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1;
private static final int DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = 0;
@@ -1405,6 +1405,7 @@
long timestampMillis) {
super.setLastNotificationUpdateTimeMs(record, timestampMillis);
mLastNotificationTimestamp = timestampMillis;
+ mAppStrategy.setLastNotificationUpdateTimeMs(record, timestampMillis);
}
long getLastNotificationUpdateTimeMs(final NotificationRecord record) {
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 0be8e6e..ac89fecc 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -3438,26 +3438,33 @@
}
}
} else {
- if (allowSetMutation) {
- Slog.i(TAG,
- "Result set changed, dropping preferred activity "
- + "for " + intent + " type "
- + resolvedType);
- if (DEBUG_PREFERRED) {
- Slog.v(TAG,
- "Removing preferred activity since set changed "
- + pa.mPref.mComponent);
+ final boolean isHomeActivity = ACTION_MAIN.equals(intent.getAction())
+ && intent.hasCategory(CATEGORY_HOME);
+ if (!Flags.improveHomeAppBehavior() || !isHomeActivity) {
+ // Don't reset the preferred activity just for the home intent, we
+ // should respect the default home app even though there any new
+ // home activity is enabled.
+ if (allowSetMutation) {
+ Slog.i(TAG,
+ "Result set changed, dropping preferred activity "
+ + "for " + intent + " type "
+ + resolvedType);
+ if (DEBUG_PREFERRED) {
+ Slog.v(TAG,
+ "Removing preferred activity since set changed "
+ + pa.mPref.mComponent);
+ }
+ pir.removeFilter(pa);
+ // Re-add the filter as a "last chosen" entry (!always)
+ PreferredActivity lastChosen = new PreferredActivity(
+ pa, pa.mPref.mMatch, null, pa.mPref.mComponent,
+ false);
+ pir.addFilter(this, lastChosen);
+ result.mChanged = true;
}
- pir.removeFilter(pa);
- // Re-add the filter as a "last chosen" entry (!always)
- PreferredActivity lastChosen = new PreferredActivity(
- pa, pa.mPref.mMatch, null, pa.mPref.mComponent,
- false);
- pir.addFilter(this, lastChosen);
- result.mChanged = true;
+ result.mPreferredResolveInfo = null;
+ return result;
}
- result.mPreferredResolveInfo = null;
- return result;
}
}
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index f1dc284..b9aa28e 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -49,6 +49,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
+import android.content.pm.UserProperties;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -268,7 +269,8 @@
private boolean canRequestInteractAcrossProfilesUnchecked(String packageName) {
final int callingUserId = mInjector.getCallingUserId();
final int[] enabledProfileIds =
- mInjector.getUserManager().getEnabledProfileIds(callingUserId);
+ mInjector.getUserManager().getProfileIdsExcludingHidden(
+ callingUserId, /* enabled= */ true);
if (enabledProfileIds.length < 2) {
return false;
}
@@ -350,7 +352,8 @@
String packageName, @UserIdInt int userId) {
return mInjector.withCleanCallingIdentity(() -> {
final int[] enabledProfileIds =
- mInjector.getUserManager().getEnabledProfileIds(userId);
+ mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+ true);
List<UserHandle> targetProfiles = new ArrayList<>();
for (final int profileId : enabledProfileIds) {
@@ -466,7 +469,8 @@
return;
}
final int[] profileIds =
- mInjector.getUserManager().getProfileIds(userId, /* enabledOnly= */ false);
+ mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+ false);
for (int profileId : profileIds) {
if (!isPackageInstalled(packageName, profileId)) {
continue;
@@ -632,7 +636,8 @@
private boolean canUserAttemptToConfigureInteractAcrossProfiles(
String packageName, @UserIdInt int userId) {
final int[] profileIds =
- mInjector.getUserManager().getProfileIds(userId, /* enabledOnly= */ false);
+ mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+ false);
if (profileIds.length < 2) {
return false;
}
@@ -676,7 +681,8 @@
private boolean hasOtherProfileWithPackageInstalled(String packageName, @UserIdInt int userId) {
return mInjector.withCleanCallingIdentity(() -> {
final int[] profileIds =
- mInjector.getUserManager().getProfileIds(userId, /* enabledOnly= */ false);
+ mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+ false);
for (int profileId : profileIds) {
if (profileId != userId && isPackageInstalled(packageName, profileId)) {
return true;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c0596bb..796edde 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1385,7 +1385,7 @@
@Override
public int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) {
- return getProfileIds(userId, null, enabledOnly);
+ return getProfileIds(userId, null, enabledOnly, /* excludeHidden */ false);
}
// TODO(b/142482943): Probably @Override and make this accessible in UserManager.
@@ -1397,14 +1397,14 @@
* If enabledOnly, only returns users that are not {@link UserInfo#FLAG_DISABLED}.
*/
public int[] getProfileIds(@UserIdInt int userId, @Nullable String userType,
- boolean enabledOnly) {
+ boolean enabledOnly, boolean excludeHidden) {
if (userId != UserHandle.getCallingUserId()) {
checkQueryOrCreateUsersPermission("getting profiles related to user " + userId);
}
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mUsersLock) {
- return getProfileIdsLU(userId, userType, enabledOnly).toArray();
+ return getProfileIdsLU(userId, userType, enabledOnly, excludeHidden).toArray();
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -1415,7 +1415,8 @@
@GuardedBy("mUsersLock")
private List<UserInfo> getProfilesLU(@UserIdInt int userId, @Nullable String userType,
boolean enabledOnly, boolean fullInfo) {
- IntArray profileIds = getProfileIdsLU(userId, userType, enabledOnly);
+ IntArray profileIds = getProfileIdsLU(userId, userType, enabledOnly, /* excludeHidden */
+ false);
ArrayList<UserInfo> users = new ArrayList<>(profileIds.size());
for (int i = 0; i < profileIds.size(); i++) {
int profileId = profileIds.get(i);
@@ -1440,7 +1441,7 @@
*/
@GuardedBy("mUsersLock")
private IntArray getProfileIdsLU(@UserIdInt int userId, @Nullable String userType,
- boolean enabledOnly) {
+ boolean enabledOnly, boolean excludeHidden) {
UserInfo user = getUserInfoLU(userId);
IntArray result = new IntArray(mUsers.size());
if (user == null) {
@@ -1465,11 +1466,36 @@
if (userType != null && !userType.equals(profile.userType)) {
continue;
}
+ if (excludeHidden && isProfileHidden(userId)) {
+ continue;
+ }
result.add(profile.id);
}
return result;
}
+ /*
+ * Returns all the users that are in the same profile group as userId excluding those with
+ * {@link UserProperties#getProfileApiVisibility()} set to hidden. The returned list includes
+ * the user itself.
+ */
+ // TODO (b/323011770): Add a permission check to make an exception for App stores if we end
+ // up supporting Private Space on COPE devices
+ @Override
+ public int[] getProfileIdsExcludingHidden(@UserIdInt int userId, boolean enabledOnly) {
+ return getProfileIds(userId, null, enabledOnly, /* excludeHidden */ true);
+ }
+
+ private boolean isProfileHidden(int userId) {
+ UserProperties userProperties = getUserPropertiesCopy(userId);
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enableHidingProfiles()) {
+ return userProperties.getProfileApiVisibility()
+ == UserProperties.PROFILE_API_VISIBILITY_HIDDEN;
+ }
+ return false;
+ }
+
@Override
public int getCredentialOwnerProfile(@UserIdInt int userId) {
checkManageUsersPermission("get the credential owner");
@@ -3630,7 +3656,8 @@
return 0;
}
- final int userTypeCount = getProfileIds(userId, userType, false).length;
+ final int userTypeCount = getProfileIds(userId, userType, false, /* excludeHidden */
+ false).length;
final int profilesRemovedCount = userTypeCount > 0 && allowedToRemoveOne ? 1 : 0;
final int usersCountAfterRemoving = getAliveUsersExcludingGuestsCountLU()
- profilesRemovedCount;
@@ -5931,7 +5958,8 @@
}
userData = mUsers.get(userId);
isProfile = userData.info.isProfile();
- profileIds = isProfile ? null : getProfileIdsLU(userId, null, false);
+ profileIds = isProfile ? null : getProfileIdsLU(userId, null, false, /* excludeHidden */
+ false);
}
if (!isProfile) {
@@ -7458,7 +7486,8 @@
@Override
public @NonNull int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) {
synchronized (mUsersLock) {
- return getProfileIdsLU(userId, null /* userType */, enabledOnly).toArray();
+ return getProfileIdsLU(userId, null /* userType */, enabledOnly, /* excludeHidden */
+ false).toArray();
}
}
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 23d0230..b720304 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -304,7 +304,8 @@
UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
.setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
.setCrossProfileContentSharingStrategy(
- UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT);
+ UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT)
+ .setItemsRestrictedOnHomeScreen(true);
if (android.multiuser.Flags.supportHidingProfiles()) {
userPropertiesBuilder.setProfileApiVisibility(
UserProperties.PROFILE_API_VISIBILITY_HIDDEN);
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 6ed2d31..a9e5a54 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -831,7 +831,7 @@
retProcs.put(proc.getName(),
new ProcessInfo(proc.getName(), new ArraySet<>(proc.getDeniedPermissions()),
proc.getGwpAsanMode(), proc.getMemtagMode(),
- proc.getNativeHeapZeroInitialized()));
+ proc.getNativeHeapZeroInitialized(), proc.isUseEmbeddedDex()));
}
return retProcs;
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b947aa3..775a361 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4708,6 +4708,9 @@
case KeyEvent.KEYCODE_BACK: {
logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.BACK);
if (down) {
+ // There may have other embedded activities on the same Task. Try to move the
+ // focus before processing the back event.
+ mWindowManagerInternal.moveFocusToTopEmbeddedWindowIfNeeded();
mBackKeyHandled = false;
} else {
if (!hasLongPressOnBackBehavior()) {
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 6f75439..35717af 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 @@
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 8c27bb8..118985a 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.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 @@
}
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 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 @@
switchWallpaper(lockWallpaper, null);
}
switchWallpaper(systemWallpaper, reply);
+ mInitialUserSwitch = false;
}
// Offload color extraction to another thread since switchUser will be called
@@ -3326,11 +3320,8 @@
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 @@
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 b1d04c9..7b59759 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -122,6 +122,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED;
+import static android.view.WindowManager.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
@@ -386,6 +387,7 @@
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.WindowManagerService.H;
import com.android.server.wm.utils.InsetUtils;
+import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
@@ -960,6 +962,9 @@
// 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 @@
// 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 @@
// No such property name.
}
mAppActivityEmbeddingSplitsEnabled = appActivityEmbeddingEnabled;
+ mAllowUntrustedEmbeddingStateSharing = getAllowUntrustedEmbeddingStateSharingProperty();
mOptInOnBackInvoked = WindowOnBackInvokedDispatcher
.isOnBackInvokedCallbackEnabled(info, info.applicationInfo,
@@ -3078,6 +3087,32 @@
return parent != null && parent.isEmbedded();
}
+ /**
+ * Returns {@code true} if the system is allowed to share this activity's state with the host
+ * app when this activity is embedded in untrusted mode.
+ */
+ boolean isUntrustedEmbeddingStateSharingAllowed() {
+ if (!Flags.untrustedEmbeddingStateSharing()) {
+ return false;
+ }
+ return mAllowUntrustedEmbeddingStateSharing;
+ }
+
+ private boolean getAllowUntrustedEmbeddingStateSharingProperty() {
+ if (!Flags.untrustedEmbeddingStateSharing()) {
+ return false;
+ }
+ try {
+ return mAtmService.mContext.getPackageManager()
+ .getProperty(PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING,
+ mActivityComponent)
+ .getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ // No such property name.
+ return false;
+ }
+ }
+
@Override
@Nullable
TaskDisplayArea getDisplayArea() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index ec0e3e7..49df396 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 @@
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 @@
// 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/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 13f6a5f..c2dfa21 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -19,7 +19,6 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
-import static android.view.View.FOCUS_FORWARD;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
@@ -61,7 +60,6 @@
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.wm.utils.InsetUtils;
-import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -165,21 +163,12 @@
return null;
}
- // Move focus to the adjacent embedded window if it is higher than this window
- final TaskFragment taskFragment = window.getTaskFragment();
- final TaskFragment adjacentTaskFragment =
- taskFragment != null ? taskFragment.getAdjacentTaskFragment() : null;
- if (adjacentTaskFragment != null && taskFragment.isEmbedded()
- && Flags.embeddedActivityBackNavFlag()) {
- final WindowContainer parent = taskFragment.getParent();
- if (parent.mChildren.indexOf(taskFragment) < parent.mChildren.indexOf(
- adjacentTaskFragment)) {
- mWindowManagerService.moveFocusToAdjacentWindow(window, FOCUS_FORWARD);
- window = wmService.getFocusedWindowLocked();
- if (window == null) {
- Slog.e(TAG, "Adjacent window is null, returning null.");
- return null;
- }
+ // Move focus to the top embedded window if possible
+ if (mWindowManagerService.moveFocusToTopEmbeddedWindow(window)) {
+ window = wmService.getFocusedWindowLocked();
+ if (window == null) {
+ Slog.e(TAG, "New focused window is null, returning null.");
+ return null;
}
}
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index c7df83a..5b4fb3e 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -64,6 +64,10 @@
final IApplicationThread client = transaction.getClient();
try {
transaction.schedule();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to deliver transaction for " + client
+ + "\ntransaction=" + transaction);
+ throw e;
} finally {
if (!(client instanceof Binder)) {
// If client is not an instance of Binder - it's a remote call and at this point it
@@ -106,7 +110,8 @@
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 @@
}
}
+ 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 @@
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 @@
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 @@
/** 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 d9dda4a..cd96806 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -178,6 +178,7 @@
mWindowContainer.cancelAnimation();
mWindowContainer.getInsetsSourceProviders().remove(mSource.getId());
mSeamlessRotating = false;
+ mHasPendingPosition = false;
}
ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s",
windowContainer, WindowInsets.Type.toString(mSource.getType()));
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index f6aad4c..e66321a 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -23,6 +23,7 @@
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.IBinder;
import android.os.InputConfig;
import android.view.GestureDetector;
@@ -258,11 +259,12 @@
private final GestureDetector mDoubleTapDetector;
private final DoubleTapListener mDoubleTapListener;
- TapEventReceiver(InputChannel inputChannel, WindowManagerService wmService) {
- super(inputChannel, UiThread.getHandler().getLooper());
+ TapEventReceiver(InputChannel inputChannel, WindowManagerService wmService,
+ Handler uiHandler) {
+ super(inputChannel, uiHandler.getLooper());
mDoubleTapListener = new DoubleTapListener(wmService);
- mDoubleTapDetector = new GestureDetector(
- wmService.mContext, mDoubleTapListener, UiThread.getHandler());
+ mDoubleTapDetector = new GestureDetector(wmService.mContext, mDoubleTapListener,
+ uiHandler);
}
@Override
@@ -294,19 +296,21 @@
}
}
- private final class InputInterceptor {
+ private final class InputInterceptor implements Runnable {
private final InputChannel mClientChannel;
private final InputWindowHandle mWindowHandle;
private final InputEventReceiver mInputEventReceiver;
private final WindowManagerService mWmService;
private final IBinder mToken;
+ private final Handler mHandler;
InputInterceptor(String namePrefix, WindowState win) {
mWmService = win.mWmService;
+ mHandler = UiThread.getHandler();
final String name = namePrefix + (win.mActivityRecord != null ? win.mActivityRecord : win);
mClientChannel = mWmService.mInputManager.createInputChannel(name);
- mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService);
+ mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService, mHandler);
mToken = mClientChannel.getToken();
@@ -335,11 +339,17 @@
mWindowHandle.touchableRegion.translate(-frame.left, -frame.top);
}
- void dispose() {
- mWmService.mInputManager.removeInputChannel(mToken);
+ @Override
+ public void run() {
mInputEventReceiver.dispose();
mClientChannel.dispose();
}
+
+ void dispose() {
+ mWmService.mInputManager.removeInputChannel(mToken);
+ // Perform dispose on the same thread that dispatches input event
+ mHandler.post(this);
+ }
}
private class LetterboxSurface {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 0e2d3d1..edf9da1 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_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 @@
}
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 @@
}
}
- 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 @@
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/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index cd70447..e06f215 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -17,6 +17,7 @@
rgl@google.com
yunfanc@google.com
wilsonshih@google.com
+jiamingliu@google.com
# Files related to background activity launches
per-file Background*Start* = set noparent
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 083872a..95e6ca6 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -1005,7 +1005,14 @@
public boolean moveFocusToAdjacentWindow(IWindow fromWindow, @FocusDirection int direction) {
final long identity = Binder.clearCallingIdentity();
try {
- return mService.moveFocusToAdjacentWindow(this, fromWindow, direction);
+ synchronized (mService.mGlobalLock) {
+ final WindowState win =
+ mService.windowForClientLocked(this, fromWindow, false /* throwOnError */);
+ if (win == null) {
+ return false;
+ }
+ return mService.moveFocusToAdjacentWindow(win, direction);
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 81fe453..8d054db 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -376,10 +376,15 @@
+ " is not in a task belong to the organizer app.");
return null;
}
- if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED
- || !task.isAllowedToEmbedActivityInTrustedMode(activity, mOrganizerUid)) {
+ if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) {
Slog.d(TAG, "Reparent activity=" + activity.token
- + " is not allowed to be embedded in trusted mode.");
+ + " is not allowed to be embedded.");
+ return null;
+ }
+ if (!task.isAllowedToEmbedActivityInTrustedMode(activity, mOrganizerUid)
+ && !activity.isUntrustedEmbeddingStateSharingAllowed()) {
+ Slog.d(TAG, "Reparent activity=" + activity.token
+ + " is not allowed to be shared with untrusted host.");
return null;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index d0b9a6e..ae4c3b9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -1047,4 +1047,9 @@
* * {@link #addBlockScreenCaptureForApps(ArraySet)}
*/
public abstract void clearBlockedApps();
+
+ /**
+ * Moves the current focus to the top activity window if the top activity is embedded.
+ */
+ public abstract boolean moveFocusToTopEmbeddedWindowIfNeeded();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 554cbce..b3983e7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -346,6 +346,7 @@
import com.android.server.power.ShutdownThread;
import com.android.server.utils.PriorityDump;
import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
+import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
@@ -8629,6 +8630,24 @@
}
}
}
+
+ @Override
+ public boolean moveFocusToTopEmbeddedWindowIfNeeded() {
+ synchronized (mGlobalLock) {
+ final WindowState focusedWindow = getFocusedWindow();
+ if (focusedWindow == null) {
+ return false;
+ }
+
+ if (moveFocusToTopEmbeddedWindow(focusedWindow)) {
+ // Sync the input transactions to ensure the input focus updates as well.
+ syncInputTransactions(false);
+ return true;
+ }
+
+ return false;
+ }
+ }
}
private final class ImeTargetVisibilityPolicyImpl extends ImeTargetVisibilityPolicy {
@@ -9162,18 +9181,48 @@
win.mClient);
}
- boolean moveFocusToAdjacentWindow(Session session, IWindow fromWindow,
- @FocusDirection int direction) {
- synchronized (mGlobalLock) {
- final WindowState fromWin = windowForClientLocked(session, fromWindow, false);
- if (fromWin == null || !fromWin.isFocused()) {
- return false;
- }
- return moveFocusToAdjacentWindow(fromWin, direction);
+ /**
+ * Move focus to the top embedded window if possible.
+ */
+ boolean moveFocusToTopEmbeddedWindow(@NonNull WindowState focusedWindow) {
+ final TaskFragment taskFragment = focusedWindow.getTaskFragment();
+ if (taskFragment == null) {
+ // Skip if not an Activity window.
+ return false;
}
+
+ if (!Flags.embeddedActivityBackNavFlag()) {
+ // Skip if flag is not enabled.
+ return false;
+ }
+
+ final ActivityRecord topActivity =
+ taskFragment.getTask().topRunningActivity(true /* focusableOnly */);
+ if (topActivity == null || topActivity == focusedWindow.mActivityRecord) {
+ // Skip if the focused activity is already the top-most activity on the Task.
+ return false;
+ }
+
+ if (!topActivity.isEmbedded()) {
+ // Skip if the top activity is not embedded
+ return false;
+ }
+
+ final TaskFragment topTaskFragment = topActivity.getTaskFragment();
+ if (topTaskFragment.isIsolatedNav()
+ && taskFragment.getAdjacentTaskFragment() == topTaskFragment) {
+ // Skip if the top TaskFragment is adjacent to current focus and is set to isolated nav.
+ return false;
+ }
+
+ moveFocusToActivity(topActivity);
+ return !focusedWindow.isFocused();
}
- boolean moveFocusToAdjacentWindow(WindowState fromWin, @FocusDirection int direction) {
+ boolean moveFocusToAdjacentWindow(@NonNull WindowState fromWin, @FocusDirection int direction) {
+ if (!fromWin.isFocused()) {
+ return false;
+ }
final TaskFragment fromFragment = fromWin.getTaskFragment();
if (fromFragment == null) {
return false;
@@ -9222,12 +9271,13 @@
if (topRunningActivity == null) {
return false;
}
- moveDisplayToTopInternal(topRunningActivity.getDisplayId());
- handleTaskFocusChange(topRunningActivity.getTask(), topRunningActivity);
- if (fromWin.isFocused()) {
- return false;
- }
- return true;
+ moveFocusToActivity(topRunningActivity);
+ return !fromWin.isFocused();
+ }
+
+ private void moveFocusToActivity(@NonNull ActivityRecord activity) {
+ moveDisplayToTopInternal(activity.getDisplayId());
+ handleTaskFocusChange(activity.getTask(), activity);
}
/** Return whether layer tracing is enabled */
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 44cd23d..09c4f7c 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -499,6 +499,10 @@
}
void applyEnterAnimationLocked() {
+ if (mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow()) {
+ // It's unnecessary to play enter animation below starting window.
+ return;
+ }
final int transit;
if (mEnterAnimationPending) {
mEnterAnimationPending = false;
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index e8b32cd..84b5cb7 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -148,8 +148,8 @@
* Creates a {@link PendingIntent} to be used to invoke the credential manager selector UI,
* by the calling app process.
*
- * @param requestInfo the information about the request
- * @param providerDataList the list of provider data from remote providers
+ * @param requestInfo the information about the request
+ * @param providerDataList the list of provider data from remote providers
* @param isRequestForAllOptions whether the bottom sheet should directly navigate to the
* all options page
*/
@@ -170,7 +170,8 @@
.map(disabledProvider -> new DisabledProviderData(
disabledProvider.getComponentName().flattenToString())).toList();
- Intent intent = IntentFactory.createCredentialSelectorIntent(requestInfo, providerDataList,
+ Intent intent = IntentFactory.createCredentialSelectorIntent(mContext, requestInfo,
+ providerDataList,
new ArrayList<>(disabledProviderDataList), mResultReceiver,
isRequestForAllOptions)
.setAction(UUID.randomUUID().toString());
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index fb0fbe8..50e426c 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -327,7 +327,7 @@
// Sample for a fraction of dex2oat runs.
final int traceFrequency =
DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
- "dex2oat_trace_freq", 10);
+ "dex2oat_trace_freq", 25);
int randomNum = ThreadLocalRandom.current().nextInt(100);
if (randomNum < traceFrequency) {
if (DEBUG) {
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 897afbf..afd6dbd 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_input_method_framework",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
index e1fd2b3..8a12dcd 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_input_method_framework",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
diff --git a/services/tests/PackageManagerComponentOverrideTests/Android.bp b/services/tests/PackageManagerComponentOverrideTests/Android.bp
index 00850a5..e3919a5 100644
--- a/services/tests/PackageManagerComponentOverrideTests/Android.bp
+++ b/services/tests/PackageManagerComponentOverrideTests/Android.bp
@@ -18,6 +18,7 @@
// and this is more representative of a real caller. It also uses Mockito extended, and this
// prevents converting the entire services test module.
package {
+ default_team: "trendy_team_framework_android_packages",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
index ad7af44..f15e533 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
+++ b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_framework_android_packages",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/Android.bp
index 9921106..6da503d 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/Android.bp
+++ b/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_framework_android_packages",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index f8accc3..4f27bc2 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -3,6 +3,7 @@
//########################################################################
package {
+ default_team: "trendy_team_framework_android_packages",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index 129efc6..005cad1 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -46,6 +46,7 @@
import android.permission.PermissionCheckerManager;
import android.permission.PermissionManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.SparseArray;
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
@@ -55,6 +56,7 @@
import com.android.server.wm.ActivityTaskManagerInternal;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -109,6 +111,8 @@
private IApplicationThread mIApplicationThread;
private SparseArray<Boolean> mUserEnabled = new SparseArray<>();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void initCrossProfileAppsServiceImpl() {
@@ -123,8 +127,9 @@
mUserEnabled.put(PRIMARY_USER, true);
mUserEnabled.put(PROFILE_OF_PRIMARY_USER, true);
mUserEnabled.put(SECONDARY_USER, true);
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_ENABLE_HIDING_PROFILES);
- when(mUserManager.getEnabledProfileIds(anyInt())).thenAnswer(
+ when(mUserManager.getProfileIdsExcludingHidden(anyInt(), eq(true))).thenAnswer(
invocation -> {
List<Integer> users = new ArrayList<>();
final int targetUser = invocation.getArgument(0);
diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp
index 85059838..c93f482 100644
--- a/services/tests/PackageManagerServiceTests/unit/Android.bp
+++ b/services/tests/PackageManagerServiceTests/unit/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_framework_android_packages",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
index 93bdeae..d538f25 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
@@ -40,6 +40,7 @@
ParsedProcess::getGwpAsanMode,
ParsedProcess::getMemtagMode,
ParsedProcess::getNativeHeapZeroInitialized,
+ ParsedProcess::isUseEmbeddedDex,
)
override fun extraParams() = listOf(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 42bcb33..b142334 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -72,6 +72,7 @@
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -102,7 +103,9 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.os.UserManager;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
import android.util.SparseArray;
import android.view.ContentRecordingSession;
import android.view.Display;
@@ -210,6 +213,10 @@
private int mPreferredHdrOutputType;
+ private Handler mPowerHandler;
+
+ private UserManager mUserManager;
+
private final DisplayManagerService.Injector mShortMockedInjector =
new DisplayManagerService.Injector() {
@Override
@@ -370,11 +377,14 @@
mContext = spy(new ContextWrapper(
ApplicationProvider.getApplicationContext().createDisplayContext(display)));
mResources = Mockito.spy(mContext.getResources());
+ mPowerHandler = new Handler(Looper.getMainLooper());
manageDisplaysPermission(/* granted= */ false);
when(mContext.getResources()).thenReturn(mResources);
+ mUserManager = Mockito.spy(mContext.getSystemService(UserManager.class));
VirtualDeviceManager vdm = new VirtualDeviceManager(mIVirtualDeviceManager, mContext);
when(mContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
+ when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
// Disable binder caches in this process.
PropertyInvalidatedCache.disableForTestMode();
setUpDisplay();
@@ -2789,6 +2799,85 @@
assertThat(display.getDisplayOffloadSessionLocked()).isNull();
}
+ @Test
+ public void testOnUserSwitching_UpdatesBrightness() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ float brightness1 = 0.3f;
+ float brightness2 = 0.45f;
+
+ int userId1 = 123;
+ int userId2 = 456;
+ UserInfo userInfo1 = new UserInfo();
+ userInfo1.id = userId1;
+ UserInfo userInfo2 = new UserInfo();
+ userInfo2.id = userId2;
+ when(mUserManager.getUserSerialNumber(userId1)).thenReturn(12345);
+ when(mUserManager.getUserSerialNumber(userId2)).thenReturn(45678);
+ final SystemService.TargetUser from = new SystemService.TargetUser(userInfo1);
+ final SystemService.TargetUser to = new SystemService.TargetUser(userInfo2);
+
+ // The same brightness will be restored for a user only if auto-brightness is off,
+ // otherwise the current lux will be used to determine the brightness.
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+ displayManager.onUserSwitching(to, from);
+ waitForIdleHandler(mPowerHandler);
+ displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, brightness1);
+ displayManager.onUserSwitching(from, to);
+ waitForIdleHandler(mPowerHandler);
+ displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, brightness2);
+
+ displayManager.onUserSwitching(to, from);
+ waitForIdleHandler(mPowerHandler);
+ assertEquals(brightness1,
+ displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+ FLOAT_TOLERANCE);
+
+ displayManager.onUserSwitching(from, to);
+ waitForIdleHandler(mPowerHandler);
+ assertEquals(brightness2,
+ displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+ FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testOnUserSwitching_brightnessForNewUserIsDefault() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ int userId1 = 123;
+ int userId2 = 456;
+ UserInfo userInfo1 = new UserInfo();
+ userInfo1.id = userId1;
+ UserInfo userInfo2 = new UserInfo();
+ userInfo2.id = userId2;
+ when(mUserManager.getUserSerialNumber(userId1)).thenReturn(12345);
+ when(mUserManager.getUserSerialNumber(userId2)).thenReturn(45678);
+ final SystemService.TargetUser from = new SystemService.TargetUser(userInfo1);
+ final SystemService.TargetUser to = new SystemService.TargetUser(userInfo2);
+
+ displayManager.onUserSwitching(from, to);
+ waitForIdleHandler(mPowerHandler);
+ assertEquals(displayManagerBinderService.getDisplayInfo(Display.DEFAULT_DISPLAY)
+ .brightnessDefault,
+ displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+ FLOAT_TOLERANCE);
+ }
+
private void initDisplayPowerController(DisplayManagerInternal localService) {
localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
@Override
@@ -2820,7 +2909,7 @@
public void releaseSuspendBlocker(String id) {
}
- }, new Handler(Looper.getMainLooper()), mSensorManager);
+ }, mPowerHandler, mSensorManager);
}
private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 88a9758..57b8632 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -227,18 +227,14 @@
advanceTime(1);
// two times, one for unfinished business and one for proximity
- verify(mHolder.wakelockController, times(2)).acquireWakelock(
+ verify(mHolder.wakelockController).acquireWakelock(
WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
verify(mHolder.wakelockController).acquireWakelock(
WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
mHolder.dpc.stop();
advanceTime(1);
- // two times, one for unfinished business and one for proximity
- verify(mHolder.wakelockController, times(2)).acquireWakelock(
- WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
- verify(mHolder.wakelockController).acquireWakelock(
- WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+ verify(mHolder.wakelockController).releaseAll();
}
@Test
@@ -515,9 +511,8 @@
verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- // One triggered by handleBrightnessModeChange, another triggered by setBrightnessToFollow
- verify(followerDpc.hbmController, times(2)).onAmbientLuxChange(ambientLux);
- verify(followerDpc.animator, times(2)).animateTo(eq(followerBrightness), anyFloat(),
+ verify(followerDpc.hbmController).onAmbientLuxChange(ambientLux);
+ verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
@@ -811,7 +806,7 @@
DisplayPowerRequest dpr = new DisplayPowerRequest();
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
+ verify(mHolder.displayPowerState).setScreenState(anyInt());
mHolder = createDisplayPowerController(42, UNIQUE_ID);
@@ -1024,15 +1019,14 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- // One triggered by the test, the other by handleBrightnessModeChange
- verify(mHolder.automaticBrightnessController, times(2)).configure(
+ verify(mHolder.automaticBrightnessController).configure(
AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
/* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
/* userChangedBrightness= */ false, /* adjustment= */ 0,
/* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
/* shouldResetShortTermModel= */ false
);
- verify(mHolder.hbmController, times(2))
+ verify(mHolder.hbmController)
.setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
}
@@ -1098,8 +1092,7 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- // One triggered by the test, the other by handleBrightnessModeChange
- verify(mHolder.automaticBrightnessController, times(2)).configure(
+ verify(mHolder.automaticBrightnessController).configure(
AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
/* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
/* userChangedBrightness= */ false, /* adjustment= */ 0,
@@ -1136,8 +1129,7 @@
DisplayPowerRequest dpr = new DisplayPowerRequest();
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
- verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat(),
+ verify(mHolder.animator).animateTo(eq(newBrightness), anyFloat(), anyFloat(),
eq(false));
}
@@ -1210,8 +1202,9 @@
when(mHolder.hbmController.getCurrentBrightnessMax()).thenReturn(clampedBrightness);
mHolder.dpc.setBrightness(PowerManager.BRIGHTNESS_MAX);
+ mHolder.dpc.setBrightness(0.8f, /* userSerial= */ 123);
- verify(mHolder.brightnessSetting).setBrightness(clampedBrightness);
+ verify(mHolder.brightnessSetting, times(2)).setBrightness(clampedBrightness);
}
@Test
@@ -1564,9 +1557,7 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- // One triggered by handleBrightnessModeChange, another triggered by
- // setBrightnessFromOffload
- verify(mHolder.animator, times(2)).animateTo(eq(brightness), anyFloat(),
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
}
@@ -1580,9 +1571,7 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- // One triggered by handleBrightnessModeChange, another triggered by requestPowerState
- verify(mHolder.automaticBrightnessController, times(2))
- .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+ verify(mHolder.automaticBrightnessController).switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
// Back to default mode
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
@@ -1620,6 +1609,62 @@
.switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
}
+ @Test
+ public void testOnSwitchUserUpdatesBrightness() {
+ int userSerial = 12345;
+ float brightness = 0.65f;
+
+ mHolder.dpc.onSwitchUser(/* newUserId= */ 15, userSerial, brightness);
+ advanceTime(1);
+
+ verify(mHolder.brightnessSetting).setUserSerial(userSerial);
+ verify(mHolder.brightnessSetting).setBrightness(brightness);
+ }
+
+ @Test
+ public void testOnSwitchUserDoesNotAddUserDataPoint() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ int userSerial = 12345;
+ float brightness = 0.65f;
+ when(mHolder.automaticBrightnessController.hasValidAmbientLux()).thenReturn(true);
+ when(mHolder.automaticBrightnessController.convertToAdjustedNits(brightness))
+ .thenReturn(500f);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.onSwitchUser(/* newUserId= */ 15, userSerial, brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+
+ verify(mHolder.automaticBrightnessController, never()).configure(
+ /* state= */ anyInt(),
+ /* configuration= */ any(),
+ eq(brightness),
+ /* userChangedBrightness= */ eq(true),
+ /* adjustment= */ anyFloat(),
+ /* userChangedAutoBrightnessAdjustment= */ anyBoolean(),
+ /* displayPolicy= */ anyInt(),
+ /* shouldResetShortTermModel= */ anyBoolean());
+ verify(mBrightnessTrackerMock, never()).notifyBrightnessChanged(
+ /* brightness= */ anyFloat(),
+ /* userInitiated= */ eq(true),
+ /* powerBrightnessFactor= */ anyFloat(),
+ /* wasShortTermModelActive= */ anyBoolean(),
+ /* isDefaultBrightnessConfig= */ anyBoolean(),
+ /* uniqueDisplayId= */ any(),
+ /* luxValues */ any(),
+ /* luxTimestamps= */ any());
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index c92ce25..b99ecf3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -178,8 +178,8 @@
.thenReturn(mockArray);
when(mMockedResources.obtainTypedArray(R.array.config_displayCutoutSideOverrideArray))
.thenReturn(mockArray);
- when(mMockedResources.getStringArray(R.array.config_mainBuiltInDisplayCutoutSideOverride))
- .thenReturn(new String[]{});
+ when(mMockedResources.getIntArray(R.array.config_mainBuiltInDisplayCutoutSideOverride))
+ .thenReturn(new int[]{});
when(mMockedResources.obtainTypedArray(R.array.config_waterfallCutoutArray))
.thenReturn(mockArray);
when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerRadiusArray))
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 2d0c3fd..289d54b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -19,7 +19,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -252,7 +251,6 @@
0.0f);
verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
verify(mBrightnessSetting).setBrightness(brightnessValue);
- verify(mBrightnessSetting).setUserSerial(anyInt());
// Does nothing if the value is invalid
mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index 78ec2ff3..5408e11 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -104,7 +104,7 @@
int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
float lastUserSetBrightness = 0.2f;
boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -127,7 +127,7 @@
int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
float lastUserSetBrightness = 0.2f;
boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -150,7 +150,7 @@
int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
float lastUserSetBrightness = 0.2f;
boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -173,7 +173,7 @@
int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
float lastUserSetBrightness = 0.2f;
boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -196,7 +196,7 @@
int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
float lastUserSetBrightness = 0.2f;
boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -221,7 +221,7 @@
boolean userSetBrightnessChanged = true;
Settings.System.putFloat(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -247,7 +247,7 @@
float pendingBrightnessAdjustment = 0.1f;
Settings.System.putFloat(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -411,7 +411,7 @@
private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
Settings.System.putFloat(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
}
private void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index e7aaed4..75409d9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -236,10 +236,6 @@
}).when(mAms).registerUidObserver(any(), anyInt(),
eq(ActivityManager.PROCESS_STATE_TOP), any());
- mConstants.TIMEOUT = 200;
- mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
- mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
-
final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
public void addBroadcastToHistoryLocked(BroadcastRecord original) {
// Ignored
@@ -259,6 +255,12 @@
mBroadcastQueues[0] = mQueue;
mQueue.start(mContext.getContentResolver());
+
+ // Set the constants after invoking BroadcastQueue.start() to ensure they don't
+ // get overridden by the defaults.
+ mConstants.TIMEOUT = 200;
+ mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
+ mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
}
@After
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index eaf0ffd..3355a6c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -29,6 +29,8 @@
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
+import static com.android.server.job.Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS;
+import static com.android.server.job.Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -60,12 +62,15 @@
import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.NetworkPolicyManager;
import android.os.BatteryManagerInternal;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.platform.test.flag.junit.SetFlagsRule;
import com.android.server.AppStateTracker;
import com.android.server.AppStateTrackerImpl;
@@ -82,6 +87,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoSession;
@@ -105,6 +111,9 @@
@Mock
private PackageManagerInternal mPackageManagerInternal;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private class TestJobSchedulerService extends JobSchedulerService {
TestJobSchedulerService(Context context) {
super(context);
@@ -1711,6 +1720,262 @@
/** Tests that rare job batching works as expected. */
@Test
+ public void testConnectivityJobBatching() {
+ mSetFlagsRule.enableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
+ spyOn(mService);
+ doReturn(false).when(mService).evaluateControllerStatesLocked(any());
+ doNothing().when(mService).noteJobsPending(any());
+ doReturn(true).when(mService).isReadyToBeExecutedLocked(any(), anyBoolean());
+ ConnectivityController connectivityController = mService.getConnectivityController();
+ spyOn(connectivityController);
+ advanceElapsedClock(24 * HOUR_IN_MILLIS);
+
+ JobSchedulerService.MaybeReadyJobQueueFunctor maybeQueueFunctor =
+ mService.new MaybeReadyJobQueueFunctor();
+ mService.mConstants.CONN_TRANSPORT_BATCH_THRESHOLD.clear();
+ mService.mConstants.CONN_TRANSPORT_BATCH_THRESHOLD
+ .put(NetworkCapabilities.TRANSPORT_CELLULAR, 5);
+ mService.mConstants.CONN_TRANSPORT_BATCH_THRESHOLD
+ .put(NetworkCapabilities.TRANSPORT_WIFI, 2);
+ mService.mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
+
+ final Network network = mock(Network.class);
+
+ // Not enough connectivity jobs to run.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ NetworkCapabilities capabilities = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
+ doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+ doReturn(false).when(connectivityController).isNetworkInStateForJobRunLocked(any());
+ for (int i = 0; i < 4; ++i) {
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+
+ maybeQueueFunctor.accept(job);
+ assertNull(maybeQueueFunctor.mBatches.get(null));
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(0, mService.getPendingJobQueue().size());
+
+ // Not enough connectivity jobs to run, but the network is already active
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+ doReturn(true).when(connectivityController).isNetworkInStateForJobRunLocked(any());
+ for (int i = 0; i < 4; ++i) {
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+
+ maybeQueueFunctor.accept(job);
+ assertNull(maybeQueueFunctor.mBatches.get(null));
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(0, job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(4, mService.getPendingJobQueue().size());
+
+ // Enough connectivity jobs to run.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ capabilities = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+ doReturn(false).when(connectivityController).isNetworkInStateForJobRunLocked(any());
+ for (int i = 0; i < 3; ++i) {
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(3, mService.getPendingJobQueue().size());
+
+ // Not enough connectivity jobs to run, but a non-batched job saves the day.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ JobStatus runningJob = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ runningJob.network = network;
+ doReturn(true).when(mService).isCurrentlyRunningLocked(runningJob);
+ capabilities = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
+ doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+ for (int i = 0; i < 3; ++i) {
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.accept(runningJob);
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(3, mService.getPendingJobQueue().size());
+
+ // Not enough connectivity jobs to run, but an old connectivity job saves the day.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ JobStatus oldConnJob = createJobStatus("testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ oldConnJob.network = network;
+ final long oldBatchTime = sElapsedRealtimeClock.millis()
+ - 2 * mService.mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS;
+ oldConnJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
+ for (int i = 0; i < 2; ++i) {
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.accept(oldConnJob);
+ assertEquals(oldBatchTime, oldConnJob.getFirstForceBatchedTimeElapsed());
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(3, mService.getPendingJobQueue().size());
+
+ // Transport type doesn't have a set threshold. One job should be the default threshold.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ capabilities = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+ .build();
+ doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+ maybeQueueFunctor.accept(job);
+ assertEquals(1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(1, mService.getPendingJobQueue().size());
+ }
+
+ /** Tests that active job batching works as expected. */
+ @Test
+ public void testActiveJobBatching_activeBatchingEnabled() {
+ mSetFlagsRule.enableFlags(FLAG_BATCH_ACTIVE_BUCKET_JOBS);
+
+ spyOn(mService);
+ doReturn(false).when(mService).evaluateControllerStatesLocked(any());
+ doNothing().when(mService).noteJobsPending(any());
+ doReturn(true).when(mService).isReadyToBeExecutedLocked(any(), anyBoolean());
+ advanceElapsedClock(24 * HOUR_IN_MILLIS);
+
+ JobSchedulerService.MaybeReadyJobQueueFunctor maybeQueueFunctor =
+ mService.new MaybeReadyJobQueueFunctor();
+ mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT = 5;
+ mService.mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
+
+ // Not enough ACTIVE jobs to run.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+ job.setStandbyBucket(ACTIVE_INDEX);
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(0, mService.getPendingJobQueue().size());
+
+ // Enough ACTIVE jobs to run.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT; ++i) {
+ JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+ job.setStandbyBucket(ACTIVE_INDEX);
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(5, mService.getPendingJobQueue().size());
+
+ // Not enough ACTIVE jobs to run, but a non-batched job saves the day.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ JobStatus expeditedJob = createJobStatus("testActiveJobBatching",
+ createJobInfo().setExpedited(true));
+ spyOn(expeditedJob);
+ when(expeditedJob.shouldTreatAsExpeditedJob()).thenReturn(true);
+ expeditedJob.setStandbyBucket(RARE_INDEX);
+ for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+ job.setStandbyBucket(ACTIVE_INDEX);
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.accept(expeditedJob);
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(3, mService.getPendingJobQueue().size());
+
+ // Not enough ACTIVE jobs to run, but an old ACTIVE job saves the day.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ JobStatus oldActiveJob = createJobStatus("testActiveJobBatching", createJobInfo());
+ oldActiveJob.setStandbyBucket(ACTIVE_INDEX);
+ final long oldBatchTime = sElapsedRealtimeClock.millis()
+ - 2 * mService.mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS;
+ oldActiveJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
+ for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+ job.setStandbyBucket(ACTIVE_INDEX);
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.accept(oldActiveJob);
+ assertEquals(oldBatchTime, oldActiveJob.getFirstForceBatchedTimeElapsed());
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(3, mService.getPendingJobQueue().size());
+ }
+
+ /** Tests that rare job batching works as expected. */
+ @Test
public void testRareJobBatching() {
spyOn(mService);
doReturn(false).when(mService).evaluateControllerStatesLocked(any());
@@ -1723,17 +1988,15 @@
mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
- JobStatus job = createJobStatus(
- "testRareJobBatching",
- createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
- job.setStandbyBucket(RARE_INDEX);
-
// Not enough RARE jobs to run.
mService.getPendingJobQueue().clear();
maybeQueueFunctor.reset();
for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+ job.setStandbyBucket(RARE_INDEX);
+
maybeQueueFunctor.accept(job);
- assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
}
@@ -1744,8 +2007,11 @@
mService.getPendingJobQueue().clear();
maybeQueueFunctor.reset();
for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT; ++i) {
+ JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+ job.setStandbyBucket(RARE_INDEX);
+
maybeQueueFunctor.accept(job);
- assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
}
@@ -1753,15 +2019,17 @@
assertEquals(5, mService.getPendingJobQueue().size());
// Not enough RARE jobs to run, but a non-batched job saves the day.
+ mSetFlagsRule.disableFlags(FLAG_BATCH_ACTIVE_BUCKET_JOBS);
mService.getPendingJobQueue().clear();
maybeQueueFunctor.reset();
- JobStatus activeJob = createJobStatus(
- "testRareJobBatching",
- createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ JobStatus activeJob = createJobStatus("testRareJobBatching", createJobInfo());
activeJob.setStandbyBucket(ACTIVE_INDEX);
for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+ job.setStandbyBucket(RARE_INDEX);
+
maybeQueueFunctor.accept(job);
- assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
}
@@ -1778,8 +2046,11 @@
- 2 * mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
oldRareJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+ job.setStandbyBucket(RARE_INDEX);
+
maybeQueueFunctor.accept(job);
- assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 4958f1c..f27d0c2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -36,6 +36,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK;
import static com.android.server.job.Flags.FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
@@ -69,6 +70,7 @@
import android.content.pm.PackageManagerInternal;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.ConnectivityManager.OnNetworkActiveListener;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkPolicyManager;
@@ -102,6 +104,7 @@
import org.mockito.stubbing.Answer;
import java.time.Clock;
+import java.time.Duration;
import java.time.ZoneOffset;
import java.util.Set;
@@ -1650,6 +1653,141 @@
assertEquals((81920 + 4096) * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
}
+ @Test
+ public void testIsNetworkInStateForJobRunLocked_JobStatus() {
+ mSetFlagsRule.enableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
+
+ // Null network
+ final JobStatus expeditedJob =
+ spy(createJobStatus(createJob().setExpedited(true), UID_RED));
+ doReturn(true).when(expeditedJob).shouldTreatAsExpeditedJob();
+ final JobStatus highProcJob = spy(createJobStatus(createJob(), UID_BLUE));
+ doReturn(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE)
+ .when(mService).getUidProcState(eq(UID_BLUE));
+ doReturn(ActivityManager.PROCESS_STATE_CACHED_EMPTY)
+ .when(mService).getUidProcState(eq(UID_RED));
+ final JobStatus regJob = createJobStatus(createJob(), UID_RED);
+ final JobStatus uiJob = spy(createJobStatus(
+ createJob().setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY),
+ UID_RED));
+ doReturn(true).when(uiJob).shouldTreatAsUserInitiatedJob();
+ assertFalse(controller.isNetworkInStateForJobRunLocked(expeditedJob));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(highProcJob));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(regJob));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(uiJob));
+
+ // Privileged jobs are exempted
+ expeditedJob.network = mock(Network.class);
+ highProcJob.network = mock(Network.class);
+ regJob.network = mock(Network.class);
+ uiJob.network = mock(Network.class);
+ assertTrue(controller.isNetworkInStateForJobRunLocked(expeditedJob));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(highProcJob));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(regJob));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(uiJob));
+
+ mSetFlagsRule.disableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+ assertTrue(controller.isNetworkInStateForJobRunLocked(expeditedJob));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(highProcJob));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(regJob));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(uiJob));
+ }
+
+ @Test
+ public void testIsNetworkInStateForJobRunLocked_Network() {
+ mSetFlagsRule.disableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
+ final ArgumentCaptor<NetworkCallback> allNetworkCallbackCaptor =
+ ArgumentCaptor.forClass(NetworkCallback.class);
+ doNothing().when(mConnManager)
+ .registerNetworkCallback(any(), allNetworkCallbackCaptor.capture());
+ final ArgumentCaptor<OnNetworkActiveListener> onNetworkActiveListenerCaptor =
+ ArgumentCaptor.forClass(OnNetworkActiveListener.class);
+ doNothing().when(mConnManager).addDefaultNetworkActiveListener(
+ onNetworkActiveListenerCaptor.capture());
+ final ArgumentCaptor<NetworkCallback> systemDefaultNetworkCallbackCaptor =
+ ArgumentCaptor.forClass(NetworkCallback.class);
+ doNothing().when(mConnManager).registerSystemDefaultNetworkCallback(
+ systemDefaultNetworkCallbackCaptor.capture(), any());
+
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
+
+ assertTrue(controller.isNetworkInStateForJobRunLocked(mock(Network.class)));
+
+ mSetFlagsRule.enableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
+ // Unknown network
+ assertFalse(controller.isNetworkInStateForJobRunLocked(mock(Network.class)));
+
+ final Network systemDefaultNetwork = mock(Network.class);
+ final Network otherNetwork = mock(Network.class);
+
+ controller.startTrackingLocked();
+
+ final NetworkCallback allNetworkCallback = allNetworkCallbackCaptor.getValue();
+ final OnNetworkActiveListener onNetworkActiveListener =
+ onNetworkActiveListenerCaptor.getValue();
+ final NetworkCallback systemDefaultNetworkCallback =
+ systemDefaultNetworkCallbackCaptor.getValue();
+
+ // No capabilities set
+ allNetworkCallback.onAvailable(systemDefaultNetwork);
+ allNetworkCallback.onAvailable(otherNetwork);
+ assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Capabilities set, but never active.
+ allNetworkCallback.onCapabilitiesChanged(
+ systemDefaultNetwork, mock(NetworkCapabilities.class));
+ allNetworkCallback.onCapabilitiesChanged(otherNetwork, mock(NetworkCapabilities.class));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Mark system default network as active before identifying system default network.
+ onNetworkActiveListener.onNetworkActive();
+ assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Identify system default network and mark as active.
+ systemDefaultNetworkCallback.onAvailable(systemDefaultNetwork);
+ onNetworkActiveListener.onNetworkActive();
+ assertTrue(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ advanceElapsedClock(controller.getCcConfig().NETWORK_ACTIVATION_EXPIRATION_MS - 1);
+ assertTrue(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Network stays active beyond expected timeout.
+ advanceElapsedClock(2);
+ doReturn(true).when(mConnManager).isDefaultNetworkActive();
+ assertTrue(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Network becomes inactive after expected timeout.
+ advanceElapsedClock(controller.getCcConfig().NETWORK_ACTIVATION_EXPIRATION_MS);
+ doReturn(false).when(mConnManager).isDefaultNetworkActive();
+ assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Other network hasn't received a signal for a long time. System default network has
+ // been active within the max wait time.
+ advanceElapsedClock(controller.getCcConfig().NETWORK_ACTIVATION_MAX_WAIT_TIME_MS
+ - controller.getCcConfig().NETWORK_ACTIVATION_EXPIRATION_MS);
+ doReturn(false).when(mConnManager).isDefaultNetworkActive();
+ assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+ }
+
+ private void advanceElapsedClock(long incrementMs) {
+ JobSchedulerService.sElapsedRealtimeClock = Clock.offset(
+ JobSchedulerService.sElapsedRealtimeClock, Duration.ofMillis(incrementMs));
+ }
+
private void answerNetwork(@NonNull NetworkCallback generalCallback,
@Nullable NetworkCallback uidCallback, @Nullable Network lastNetwork,
@Nullable Network net, @Nullable NetworkCapabilities caps) {
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index 6537d47..f9c6794 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -47,6 +47,7 @@
deleteAppWithParent='false'
alwaysVisible='true'
crossProfileContentSharingStrategy='0'
+ itemsRestrictedOnHomeScreen='true'
/>
</profile-type>
<profile-type name='custom.test.1' max-allowed-per-parent='14' />
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 52726ca..b224773 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.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.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.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.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 @@
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 @@
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 @@
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 @@
@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 @@
mRequestObserver,
mScaleProvider,
() -> mMockThumbnail,
- ConcurrentUtils.DIRECT_EXECUTOR);
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ () -> mMockScroller,
+ () -> mMockTimeAnimator);
}
@After
@@ -428,7 +444,7 @@
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 @@
mMessageCapturingHandler.sendAllMessages();
verify(mMockValueAnimator, never()).start();
- verify(mAnimationCallback).onResult(true);
+ verify(mAnimationCallback).onResult(eq(true), any());
}
@Test
@@ -653,6 +669,85 @@
}
@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 @@
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 @@
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 @@
mStateListener.onAnimationEnd(mMockValueAnimator);
checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId);
- verify(lastAnimationCallback).onResult(true);
+ verify(lastAnimationCallback).onResult(eq(true), any());
}
@Test
@@ -1379,6 +1474,8 @@
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 71d64cf..8c0d44c 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.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.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.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.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 @@
@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 @@
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 @@
@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 @@
//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 @@
//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 @@
//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 @@
//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 @@
}
@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 @@
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 @@
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 @@
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 @@
/* 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 28d07f9..cd904eb 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.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.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 @@
@Mock
private ValueAnimator mValueAnimator;
@Mock
+ private TimeAnimator mTimeAnimator;
+ @Mock
private MessageCapturingHandler mMessageCapturingHandler;
private FullScreenMagnificationController mScreenMagnificationController;
@@ -195,14 +199,17 @@
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 e7da26e..98789ac 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -519,11 +519,12 @@
eq(AudioManager.ADJUST_MUTE), anyInt());
clearInvocations(mAudioManager);
- // New volume only: sets volume only
+ // New volume only: sets both volume and mute.
+ // Volume changes can affect mute status; we need to set mute afterwards to undo this.
receiveReportAudioStatus(32, true);
verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
anyInt());
- verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
eq(AudioManager.ADJUST_MUTE), anyInt());
clearInvocations(mAudioManager);
@@ -536,17 +537,17 @@
clearInvocations(mAudioManager);
// Repeat of earlier message: sets neither volume nor mute
- // Exception: On TV, volume is set to ensure that UI is shown
+ // Exception: On TV, mute is set to ensure that UI is shown
receiveReportAudioStatus(32, false);
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(32), anyInt());
if (getDeviceType() == HdmiDeviceInfo.DEVICE_TV) {
- verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
- anyInt());
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE), anyInt());
} else {
- verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
- anyInt());
+ verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE), anyInt());
}
- verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
- eq(AudioManager.ADJUST_UNMUTE), anyInt());
clearInvocations(mAudioManager);
// Volume not within range [0, 100]: sets neither volume nor mute
@@ -570,7 +571,8 @@
receiveReportAudioStatus(32, false);
verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
anyInt());
- verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ // Update mute status because we updated volume
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
eq(AudioManager.ADJUST_UNMUTE), anyInt());
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
index a410702..9ad2652 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
@@ -174,11 +174,12 @@
eq(AudioManager.ADJUST_MUTE), anyInt());
clearInvocations(mAudioManager);
- // New volume only: sets volume only
+ // New volume only: sets both volume and mute.
+ // Volume changes can affect mute status; we need to set mute afterwards to undo this.
receiveReportAudioStatus(32, true);
verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
anyInt());
- verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
eq(AudioManager.ADJUST_MUTE), anyInt());
clearInvocations(mAudioManager);
@@ -190,11 +191,11 @@
eq(AudioManager.ADJUST_UNMUTE), anyInt());
clearInvocations(mAudioManager);
- // Repeat of earlier message: sets volume only (to ensure volume UI is shown)
+ // Repeat of earlier message: sets mute only (to ensure volume UI is shown)
receiveReportAudioStatus(32, false);
- verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
anyInt());
- verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
eq(AudioManager.ADJUST_UNMUTE), anyInt());
clearInvocations(mAudioManager);
@@ -392,18 +393,18 @@
INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute()));
mTestLooper.dispatchAll();
- // HdmiControlService calls setStreamVolume to trigger volume UI
- verify(mAudioManager).setStreamVolume(
+ // HdmiControlService calls adjustStreamVolume to trigger volume UI
+ verify(mAudioManager).adjustStreamVolume(
+ eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE),
+ eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
+ // setStreamVolume is not called because volume didn't change,
+ // and adjustStreamVolume is sufficient to show volume UI
+ verify(mAudioManager, never()).setStreamVolume(
eq(AudioManager.STREAM_MUSIC),
// Volume level is rescaled to the max volume of STREAM_MUSIC
eq(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume()
* STREAM_MUSIC_MAX_VOLUME / AudioStatus.MAX_VOLUME),
eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
- // adjustStreamVolume is not called because mute status didn't change,
- // and setStreamVolume is sufficient to show volume UI
- verify(mAudioManager, never()).adjustStreamVolume(
- eq(AudioManager.STREAM_MUSIC),
- eq(AudioManager.ADJUST_UNMUTE),
- eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
index 213e05e..2b07d33 100644
--- a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
@@ -18,6 +18,7 @@
import static android.app.job.JobInfo.NETWORK_TYPE_ANY;
import static android.app.job.JobInfo.NETWORK_TYPE_NONE;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -39,8 +40,6 @@
import org.junit.Test;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Random;
public class PendingJobQueueTest {
@@ -68,7 +67,7 @@
@Test
public void testAdd() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
jobs.add(createJobStatus("testAdd", createJobInfo(1), 1));
jobs.add(createJobStatus("testAdd", createJobInfo(2), 2));
jobs.add(createJobStatus("testAdd", createJobInfo(3).setExpedited(true), 3));
@@ -77,7 +76,7 @@
PendingJobQueue jobQueue = new PendingJobQueue();
for (int i = 0; i < jobs.size(); ++i) {
- jobQueue.add(jobs.get(i));
+ jobQueue.add(jobs.valueAt(i));
assertEquals(i + 1, jobQueue.size());
}
@@ -90,7 +89,7 @@
@Test
public void testAddAll() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
jobs.add(createJobStatus("testAddAll", createJobInfo(1), 1));
jobs.add(createJobStatus("testAddAll", createJobInfo(2), 2));
jobs.add(createJobStatus("testAddAll", createJobInfo(3).setExpedited(true), 3));
@@ -110,7 +109,7 @@
@Test
public void testClear() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
jobs.add(createJobStatus("testClear", createJobInfo(1), 1));
jobs.add(createJobStatus("testClear", createJobInfo(2), 2));
jobs.add(createJobStatus("testClear", createJobInfo(3).setExpedited(true), 3));
@@ -179,7 +178,7 @@
@Test
public void testRemove() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
jobs.add(createJobStatus("testRemove", createJobInfo(1), 1));
jobs.add(createJobStatus("testRemove", createJobInfo(2), 2));
jobs.add(createJobStatus("testRemove", createJobInfo(3).setExpedited(true), 3));
@@ -192,8 +191,8 @@
ArraySet<JobStatus> removed = new ArraySet<>();
JobStatus job;
for (int i = 0; i < jobs.size(); ++i) {
- jobQueue.remove(jobs.get(i));
- removed.add(jobs.get(i));
+ jobQueue.remove(jobs.valueAt(i));
+ removed.add(jobs.valueAt(i));
assertEquals(jobs.size() - i - 1, jobQueue.size());
@@ -209,7 +208,7 @@
@Test
public void testRemove_duringIteration() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
jobs.add(createJobStatus("testRemove", createJobInfo(1), 1));
jobs.add(createJobStatus("testRemove", createJobInfo(2), 2));
jobs.add(createJobStatus("testRemove", createJobInfo(3).setExpedited(true), 3));
@@ -234,7 +233,7 @@
@Test
public void testRemove_outOfOrder() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
JobStatus job1 = createJobStatus("testRemove", createJobInfo(1), 1);
JobStatus job2 = createJobStatus("testRemove", createJobInfo(2), 1);
JobStatus job3 = createJobStatus("testRemove", createJobInfo(3).setExpedited(true), 1);
@@ -269,8 +268,8 @@
Log.d(TAG, testJobToString(job));
}
for (int i = 0; i < jobs.size(); ++i) {
- jobQueue.remove(jobs.get(i));
- removed.add(jobs.get(i));
+ jobQueue.remove(jobs.valueAt(i));
+ removed.add(jobs.valueAt(i));
assertEquals(jobs.size() - i - 1, jobQueue.size());
@@ -294,8 +293,8 @@
removed.clear();
for (int i = 0; i < jobs.size(); ++i) {
- jobQueue.remove(jobs.get(i));
- removed.add(jobs.get(i));
+ jobQueue.remove(jobs.valueAt(i));
+ removed.add(jobs.valueAt(i));
assertEquals(jobs.size() - i - 1, jobQueue.size());
@@ -319,8 +318,8 @@
removed.clear();
for (int i = 0; i < jobs.size(); ++i) {
- jobQueue.remove(jobs.get(i));
- removed.add(jobs.get(i));
+ jobQueue.remove(jobs.valueAt(i));
+ removed.add(jobs.valueAt(i));
assertEquals(jobs.size() - i - 1, jobQueue.size());
diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index c81fbb4..cee7387 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -46,6 +46,7 @@
import android.permission.PermissionCheckerManager;
import android.permission.PermissionManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.SparseArray;
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
@@ -55,6 +56,7 @@
import com.android.server.wm.ActivityTaskManagerInternal;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -109,6 +111,8 @@
private IApplicationThread mIApplicationThread;
private SparseArray<Boolean> mUserEnabled = new SparseArray<>();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void initCrossProfileAppsServiceImpl() {
@@ -123,8 +127,9 @@
mUserEnabled.put(PRIMARY_USER, true);
mUserEnabled.put(PROFILE_OF_PRIMARY_USER, true);
mUserEnabled.put(SECONDARY_USER, true);
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_ENABLE_HIDING_PROFILES);
- when(mUserManager.getEnabledProfileIds(anyInt())).thenAnswer(
+ when(mUserManager.getProfileIdsExcludingHidden(anyInt(), eq(true))).thenAnswer(
invocation -> {
List<Integer> users = new ArrayList<>();
final int targetUser = invocation.getArgument(0);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
index 39cc653..6decf43 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
@@ -114,16 +114,19 @@
final String userType1 = USER_TYPE_PROFILE_MANAGED;
// System user should still have no userType1 profile so getProfileIds should be empty.
- int[] users = mUserManagerService.getProfileIds(UserHandle.USER_SYSTEM, userType1, false);
+ int[] users = mUserManagerService.getProfileIds(UserHandle.USER_SYSTEM, userType1,
+ false, /* excludeHidden */ false);
assertEquals("System user should have no managed profiles", 0, users.length);
// Secondary user should have one userType1 profile, so return just that.
- users = mUserManagerService.getProfileIds(secondaryUser.id, userType1, false);
+ users = mUserManagerService.getProfileIds(secondaryUser.id, userType1,
+ false, /* excludeHidden */ false);
assertEquals("Wrong number of profiles", 1, users.length);
assertEquals("Wrong profile id", profile.id, users[0]);
// The profile itself is a userType1 profile, so it should return just itself.
- users = mUserManagerService.getProfileIds(profile.id, userType1, false);
+ users = mUserManagerService.getProfileIds(profile.id, userType1, false, /* excludeHidden */
+ false);
assertEquals("Wrong number of profiles", 1, users.length);
assertEquals("Wrong profile id", profile.id, users[0]);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 8d8dc9c..3778a32 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -79,6 +79,7 @@
.setAlwaysVisible(false)
.setCrossProfileContentSharingStrategy(0)
.setProfileApiVisibility(34)
+ .setItemsRestrictedOnHomeScreen(false)
.build();
final UserProperties actualProps = new UserProperties(defaultProps);
actualProps.setShowInLauncher(14);
@@ -97,6 +98,7 @@
actualProps.setAlwaysVisible(true);
actualProps.setCrossProfileContentSharingStrategy(1);
actualProps.setProfileApiVisibility(36);
+ actualProps.setItemsRestrictedOnHomeScreen(true);
// Write the properties to xml.
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -144,6 +146,7 @@
.setAllowStoppingUserWithDelayedLocking(false)
.setAlwaysVisible(true)
.setProfileApiVisibility(110)
+ .setItemsRestrictedOnHomeScreen(false)
.build();
final UserProperties orig = new UserProperties(defaultProps);
orig.setShowInLauncher(2841);
@@ -154,6 +157,7 @@
orig.setAuthAlwaysRequiredToDisableQuietMode(true);
orig.setAllowStoppingUserWithDelayedLocking(true);
orig.setAlwaysVisible(false);
+ orig.setItemsRestrictedOnHomeScreen(true);
// Test every permission level. (Currently, it's linear so it's easy.)
for (int permLevel = 0; permLevel < 4; permLevel++) {
@@ -200,6 +204,8 @@
assertEqualGetterOrThrows(orig::getAlwaysVisible, copy::getAlwaysVisible, exposeAll);
assertEqualGetterOrThrows(orig::getAllowStoppingUserWithDelayedLocking,
copy::getAllowStoppingUserWithDelayedLocking, exposeAll);
+ assertEqualGetterOrThrows(orig::areItemsRestrictedOnHomeScreen,
+ copy::areItemsRestrictedOnHomeScreen, exposeAll);
// Items requiring hasManagePermission - put them here using hasManagePermission.
assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
@@ -283,5 +289,7 @@
assertThat(expected.getCrossProfileContentSharingStrategy())
.isEqualTo(actual.getCrossProfileContentSharingStrategy());
assertThat(expected.getProfileApiVisibility()).isEqualTo(actual.getProfileApiVisibility());
+ assertThat(expected.areItemsRestrictedOnHomeScreen())
+ .isEqualTo(actual.areItemsRestrictedOnHomeScreen());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 1ee604e..6cdbc74 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -102,7 +102,8 @@
.setDeleteAppWithParent(true)
.setAlwaysVisible(true)
.setCrossProfileContentSharingStrategy(1)
- .setProfileApiVisibility(34);
+ .setProfileApiVisibility(34)
+ .setItemsRestrictedOnHomeScreen(true);
final UserTypeDetails type = new UserTypeDetails.Builder()
.setName("a.name")
@@ -186,6 +187,7 @@
assertEquals(1, type.getDefaultUserPropertiesReference()
.getCrossProfileContentSharingStrategy());
assertEquals(34, type.getDefaultUserPropertiesReference().getProfileApiVisibility());
+ assertTrue(type.getDefaultUserPropertiesReference().areItemsRestrictedOnHomeScreen());
assertEquals(23, type.getBadgeLabel(0));
assertEquals(24, type.getBadgeLabel(1));
@@ -343,7 +345,8 @@
.setDeleteAppWithParent(true)
.setAlwaysVisible(false)
.setCrossProfileContentSharingStrategy(1)
- .setProfileApiVisibility(36);
+ .setProfileApiVisibility(36)
+ .setItemsRestrictedOnHomeScreen(false);
final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
builders.put(userTypeAosp1, new UserTypeDetails.Builder()
@@ -395,6 +398,7 @@
assertEquals(1, aospType.getDefaultUserPropertiesReference()
.getCrossProfileContentSharingStrategy());
assertEquals(36, aospType.getDefaultUserPropertiesReference().getProfileApiVisibility());
+ assertFalse(aospType.getDefaultUserPropertiesReference().areItemsRestrictedOnHomeScreen());
// userTypeAosp2 should be modified.
aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -452,6 +456,7 @@
assertEquals(0, aospType.getDefaultUserPropertiesReference()
.getCrossProfileContentSharingStrategy());
assertEquals(36, aospType.getDefaultUserPropertiesReference().getProfileApiVisibility());
+ assertTrue(aospType.getDefaultUserPropertiesReference().areItemsRestrictedOnHomeScreen());
// userTypeOem1 should be created.
UserTypeDetails.Builder customType = builders.get(userTypeOem1);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index db561c4..9323b48 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -355,6 +355,8 @@
privateProfileUserProperties::getAllowStoppingUserWithDelayedLocking);
assertThrows(SecurityException.class,
privateProfileUserProperties::getProfileApiVisibility);
+ assertThrows(SecurityException.class,
+ privateProfileUserProperties::areItemsRestrictedOnHomeScreen);
compareDrawables(mUserManager.getUserBadge(),
Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
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 ffb3bce..4ab9d3e 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 @@
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 @@
.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 @@
});
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 @@
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 03cdbbd..eddff9ab 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -716,7 +716,8 @@
String certificateDigestStr = "E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:"
+ "8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0";
- byte[] certificateDigest = new Signature(certificateDigestStr).toByteArray();
+ byte[] certificateDigest = new Signature(certificateDigestStr.replace(":", ""))
+ .toByteArray();
String contents = "<config>"
+ "<" + "enhanced-confirmation-trusted-installer" + " "
+ "package=\"" + pkgName + "\""
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 46e14d51..5f18f84 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 @@
}
@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 0c1a9c3..f42cdb8 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 @@
}
@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 @@
}
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 a88285a..897a3da 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -527,29 +527,11 @@
@Test
public void testOnActivityReparentedToTask_untrustedEmbed_notReported() {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
- DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
- mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
- final Task task = createTask(mDisplayContent);
- task.addChild(mTaskFragment, POSITION_TOP);
- final ActivityRecord activity = createActivityRecord(task);
- // Flush EVENT_APPEARED.
- mController.dispatchPendingEvents();
-
- // Make sure the activity is embedded in untrusted mode.
- activity.info.applicationInfo.uid = uid + 1;
- doReturn(pid + 1).when(activity).getPid();
- task.effectiveUid = uid;
- doReturn(EMBEDDING_ALLOWED).when(task).isAllowedToEmbedActivity(activity, uid);
- doReturn(false).when(task).isAllowedToEmbedActivityInTrustedMode(activity, uid);
- doReturn(true).when(task).isAllowedToEmbedActivityInUntrustedMode(activity);
+ final ActivityRecord activity = setupUntrustedEmbeddingPipReparent();
+ doReturn(false).when(activity).isUntrustedEmbeddingStateSharingAllowed();
// Notify organizer if it was embedded before entered Pip.
// Create a temporary token since the activity doesn't belong to the same process.
- clearInvocations(mOrganizer);
- activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
@@ -558,6 +540,30 @@
}
@Test
+ public void testOnActivityReparentedToTask_untrustedEmbed_reportedWhenAppOptIn() {
+ final ActivityRecord activity = setupUntrustedEmbeddingPipReparent();
+ doReturn(true).when(activity).isUntrustedEmbeddingStateSharingAllowed();
+
+ // Notify organizer if it was embedded before entered Pip.
+ // Create a temporary token since the activity doesn't belong to the same process.
+ mController.onActivityReparentedToTask(activity);
+ mController.dispatchPendingEvents();
+
+ // Allow organizer to reparent activity in other process using the temporary token.
+ verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
+ final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
+ final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
+ assertFalse(changes.isEmpty());
+ final TaskFragmentTransaction.Change change = changes.get(0);
+ assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType());
+ assertEquals(activity.getTask().mTaskId, change.getTaskId());
+ assertIntentsEqualForOrganizer(activity.intent, change.getActivityIntent());
+ assertNotEquals(activity.token, change.getActivityToken());
+ mTransaction.reparentActivityToTaskFragment(mFragmentToken, change.getActivityToken());
+ assertApplyTransactionAllowed(mTransaction);
+ }
+
+ @Test
public void testOnActivityReparentedToTask_trimReportedIntent() {
// Make sure the activity pid/uid is the same as the organizer caller.
final int pid = Binder.getCallingPid();
@@ -1868,6 +1874,34 @@
OP_TYPE_REORDER_TO_TOP_OF_TASK);
}
+ @NonNull
+ private ActivityRecord setupUntrustedEmbeddingPipReparent() {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
+ DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ final Task task = createTask(mDisplayContent);
+ task.addChild(mTaskFragment, POSITION_TOP);
+ final ActivityRecord activity = createActivityRecord(task);
+
+ // Flush EVENT_APPEARED.
+ mController.dispatchPendingEvents();
+
+ // Make sure the activity is embedded in untrusted mode.
+ activity.info.applicationInfo.uid = uid + 1;
+ doReturn(pid + 1).when(activity).getPid();
+ task.effectiveUid = uid;
+ doReturn(EMBEDDING_ALLOWED).when(task).isAllowedToEmbedActivity(activity, uid);
+ doReturn(false).when(task).isAllowedToEmbedActivityInTrustedMode(activity, uid);
+ doReturn(true).when(task).isAllowedToEmbedActivityInUntrustedMode(activity);
+
+ clearInvocations(mOrganizer);
+ activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
+
+ return activity;
+ }
+
private void testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
@TaskFragmentOperation.OperationType int opType) {
final Task task = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 98ca094..01bd96b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -867,24 +867,36 @@
assertEquals(winLeftTop, mDisplayContent.mCurrentFocus);
// Send request from a non-focused window with valid direction.
- assertFalse(mWm.moveFocusToAdjacentWindow(null, winLeftBottom.mClient, View.FOCUS_RIGHT));
+ assertFalse(mWm.moveFocusToAdjacentWindow(winLeftBottom, View.FOCUS_RIGHT));
// The focus should remain the same.
assertEquals(winLeftTop, mDisplayContent.mCurrentFocus);
// Send request from the focused window with valid direction.
- assertTrue(mWm.moveFocusToAdjacentWindow(null, winLeftTop.mClient, View.FOCUS_RIGHT));
+ assertTrue(mWm.moveFocusToAdjacentWindow(winLeftTop, View.FOCUS_RIGHT));
// The focus should change.
assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
// Send request from the focused window with invalid direction.
- assertFalse(mWm.moveFocusToAdjacentWindow(null, winRightTop.mClient, View.FOCUS_UP));
+ assertFalse(mWm.moveFocusToAdjacentWindow(winRightTop, View.FOCUS_UP));
// The focus should remain the same.
assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
// Send request from the focused window with valid direction.
- assertTrue(mWm.moveFocusToAdjacentWindow(null, winRightTop.mClient, View.FOCUS_BACKWARD));
+ assertTrue(mWm.moveFocusToAdjacentWindow(winRightTop, View.FOCUS_BACKWARD));
// The focus should change.
assertEquals(winLeftTop, mDisplayContent.mCurrentFocus);
+
+ if (Flags.embeddedActivityBackNavFlag()) {
+ // Send request to move the focus to top window from the left window.
+ assertTrue(mWm.moveFocusToTopEmbeddedWindow(winLeftTop));
+ // The focus should change.
+ assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
+
+ // Send request to move the focus to top window from the right window.
+ assertFalse(mWm.moveFocusToTopEmbeddedWindow(winRightTop));
+ // The focus should NOT change.
+ assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
+ }
}
private WindowState createAppWindow(ActivityRecord app, String name) {
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 536e458..01448c3 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.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 @@
}
/**
- * 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/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 26c3025..f7793f3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -7517,8 +7517,8 @@
*
* The default value for this key is
* {{@link AccessNetworkConstants.AccessNetworkType#EUTRAN},
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String
KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY = KEY_PREFIX
+ "emergency_over_ims_supported_3gpp_network_types_int_array";
@@ -7535,8 +7535,8 @@
*
* The default value for this key is
* {{@link AccessNetworkConstants.AccessNetworkType#EUTRAN},
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String
KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY = KEY_PREFIX
+ "emergency_over_ims_roaming_supported_3gpp_network_types_int_array";
@@ -7555,8 +7555,8 @@
* The default value for this key is
* {{@link AccessNetworkConstants.AccessNetworkType#UTRAN},
* {@link AccessNetworkConstants.AccessNetworkType#GERAN}}.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY =
KEY_PREFIX + "emergency_over_cs_supported_access_network_types_int_array";
@@ -7574,8 +7574,8 @@
* The default value for this key is
* {{@link AccessNetworkConstants.AccessNetworkType#UTRAN},
* {@link AccessNetworkConstants.AccessNetworkType#GERAN}}.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String
KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY = KEY_PREFIX
+ "emergency_over_cs_roaming_supported_access_network_types_int_array";
@@ -7590,20 +7590,20 @@
/**
* Circuit switched domain.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final int DOMAIN_CS = 1;
/**
* Packet switched domain over 3GPP networks.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final int DOMAIN_PS_3GPP = 2;
/**
* Packet switched domain over non-3GPP networks such as Wi-Fi.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final int DOMAIN_PS_NON_3GPP = 3;
/**
@@ -7620,8 +7620,8 @@
* {{@link #DOMAIN_PS_3GPP},
* {@link #DOMAIN_CS},
* {@link #DOMAIN_PS_NON_3GPP}}.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY =
KEY_PREFIX + "emergency_domain_preference_int_array";
@@ -7639,18 +7639,22 @@
* {{@link #DOMAIN_PS_3GPP},
* {@link #DOMAIN_CS},
* {@link #DOMAIN_PS_NON_3GPP}}.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY =
KEY_PREFIX + "emergency_domain_preference_roaming_int_array";
/**
- * Specifies if emergency call shall be attempted on IMS, if PS is attached even though IMS
- * is not registered and normal calls fallback to the CS networks.
+ * Specifies whether the emergency call shall be preferred over IMS or not
+ * irrespective of IMS registration status.
+ * If the value of the config is {@code true} then emergency calls shall prefer IMS
+ * when device is combined-attached in LTE network and IMS is not registered.
+ * If the value of the config is {@code false} then emergency calls use CS domain
+ * in the same scenario.
*
* The default value for this key is {@code false}.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL =
KEY_PREFIX + "prefer_ims_emergency_when_voice_calls_on_cs_bool";
@@ -7667,32 +7671,39 @@
* If {@link ImsWfc#KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL} is {@code true},
* VoWi-Fi emergency call shall be attempted if Wi-Fi network is connected.
* Otherwise, it shall be attempted if IMS is registered over Wi-Fi.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final int VOWIFI_REQUIRES_NONE = 0;
/**
* VoWi-Fi emergency call shall be attempted on IMS over Wi-Fi if Wi-Fi network is connected
* and Wi-Fi calling setting is enabled. This value is applicable if the value of
* {@link ImsWfc#KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL} is {@code true}.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final int VOWIFI_REQUIRES_SETTING_ENABLED = 1;
/**
* VoWi-Fi emergency call shall be attempted on IMS over Wi-Fi if Wi-Fi network is connected
- * and Wi-Fi calling is activated successfully. This value is applicable if the value of
+ * and Wi-Fi calling is activated successfully. The device shall have the valid
+ * Entitlement ID if the user activates VoWi-Fi emergency calling successfully.
+ * This value is applicable if the value of
* {@link ImsWfc#KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL} is {@code true}.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final int VOWIFI_REQUIRES_VALID_EID = 2;
/**
* Specifies the condition when emergency call shall be attempted on IMS over Wi-Fi.
*
- * The default value for this key is {@code #VOWIFI_REQUIRES_NONE}.
- * @hide
+ * <p>Possible values are,
+ * {@link #VOWIFI_REQUIRES_NONE}
+ * {@link #VOWIFI_REQUIRES_SETTING_ENABLED}
+ * {@link #VOWIFI_REQUIRES_VALID_EID}
+ *
+ * The default value for this key is {@link #VOWIFI_REQUIRES_NONE}.
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT =
KEY_PREFIX + "emergency_vowifi_requires_condition_int";
@@ -7703,8 +7714,8 @@
* {@link #KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY}.
*
* The default value for this key is 1.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT =
KEY_PREFIX + "maximum_number_of_emergency_tries_over_vowifi_int";
@@ -7712,14 +7723,14 @@
* Emergency scan timer to wait for scan results from radio before attempting the call
* over Wi-Fi. On timer expiry, if emergency call on Wi-Fi is allowed and possible,
* telephony shall cancel the scan and place the call on Wi-Fi. If emergency call on Wi-Fi
- * is not possible, then domain seleciton continues to wait for the scan result from the
+ * is not possible, then domain selection continues to wait for the scan result from the
* radio. If an emergency scan result is received before the timer expires, the timer shall
* be stopped and no dialing over Wi-Fi will be tried. If this value is set to 0, then
* the timer is never started and domain selection waits for the scan result from the radio.
*
* The default value for the timer is 10 seconds.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_EMERGENCY_SCAN_TIMER_SEC_INT =
KEY_PREFIX + "emergency_scan_timer_sec_int";
@@ -7737,8 +7748,8 @@
* started.
*
* The default value for the timer is {@link #REDIAL_TIMER_DISABLED}.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT =
KEY_PREFIX + "maximum_cellular_search_timer_sec_int";
@@ -7753,21 +7764,21 @@
/**
* No specific preference given to the modem. Modem can return an emergency
* capable network either with limited service or full service.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final int SCAN_TYPE_NO_PREFERENCE = 0;
/**
* Modem will attempt to camp on a network with full service only.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final int SCAN_TYPE_FULL_SERVICE = 1;
/**
* Telephony shall attempt full service scan first.
* If a full service network is not found, telephony shall attempt a limited service scan.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final int SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE = 2;
/**
@@ -7779,8 +7790,8 @@
* {@link #SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE}
*
* The default value for this key is {@link #SCAN_TYPE_NO_PREFERENCE}.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT =
KEY_PREFIX + "emergency_network_scan_type_int";
@@ -7791,8 +7802,8 @@
* If this value is set to 0, the timer shall be disabled.
*
* The default value for this key is 0.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT =
KEY_PREFIX + "emergency_call_setup_timer_on_current_network_sec_int";
@@ -7801,8 +7812,8 @@
* This is applicable only for the case PS is in service.
*
* The default value for this key is {@code false}.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL =
KEY_PREFIX + "emergency_requires_ims_registration_bool";
@@ -7811,8 +7822,8 @@
* over NR. If not, CS will be preferred.
*
* The default value for this key is {@code false}.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL =
KEY_PREFIX + "emergency_lte_preferred_after_nr_failed_bool";
@@ -7820,8 +7831,8 @@
* Specifies the numbers to be dialed over CDMA network in case of dialing over CS network.
*
* The default value for this key is an empty string array.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY =
KEY_PREFIX + "emergency_cdma_preferred_numbers_string_array";
@@ -7830,8 +7841,8 @@
* only when VoLTE is enabled.
*
* The default value for this key is {@code false}.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL =
KEY_PREFIX + "emergency_requires_volte_enabled_bool";
@@ -7842,8 +7853,8 @@
* @see #KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT
* @see #KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT
* @see #KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final int REDIAL_TIMER_DISABLED = 0;
/**
@@ -7855,8 +7866,8 @@
* This value should be greater than the value of {@link #KEY_EMERGENCY_SCAN_TIMER_SEC_INT}.
*
* The default value for the timer is 120 seconds.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT =
KEY_PREFIX + "cross_stack_redial_timer_sec_int";
@@ -7871,8 +7882,8 @@
* in the roaming networks and non-domestic networks.
*
* The default value for the timer is {@link #REDIAL_TIMER_DISABLED}.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT =
KEY_PREFIX + "quick_cross_stack_redial_timer_sec_int";
@@ -7881,11 +7892,24 @@
* the device is registered to the network.
*
* The default value is {@code true}.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final String KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL =
KEY_PREFIX + "start_quick_cross_stack_redial_timer_when_registered_bool";
+ /**
+ * Indicates whether limited service only scanning will be requested after VoLTE fails.
+ * This value is applicable if the value of
+ * {@link #KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT} is any of {@link #SCAN_TYPE_NO_PREFERENCE}
+ * or {@link #SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE}.
+ *
+ * The default value is {@code false}.
+ */
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
+ public static final String
+ KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL =
+ KEY_PREFIX + "scan_limited_service_after_volte_failure_bool";
+
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
defaults.putBoolean(KEY_RETRY_EMERGENCY_ON_IMS_PDN_BOOL, false);
@@ -7957,6 +7981,7 @@
defaults.putInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, REDIAL_TIMER_DISABLED);
defaults.putBoolean(KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL,
true);
+ defaults.putBoolean(KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL, false);
return defaults;
}
diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java
index 2b0d626..d5db612 100644
--- a/telephony/java/android/telephony/CarrierRestrictionRules.java
+++ b/telephony/java/android/telephony/CarrierRestrictionRules.java
@@ -174,6 +174,7 @@
private int mMultiSimPolicy;
@CarrierRestrictionStatus
private int mCarrierRestrictionStatus;
+ private boolean mUseCarrierLockInfo;
private CarrierRestrictionRules() {
mAllowedCarriers = new ArrayList<CarrierIdentifier>();
@@ -183,6 +184,7 @@
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 @@
if (Flags.carrierRestrictionRulesEnhancement()) {
in.readTypedList(mAllowedCarrierInfo, CarrierInfo.CREATOR);
in.readTypedList(mExcludedCarrierInfo, CarrierInfo.CREATOR);
+ mUseCarrierLockInfo = in.readBoolean();
}
}
@@ -213,6 +216,14 @@
* 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 @@
if (Flags.carrierRestrictionRulesEnhancement()) {
out.writeTypedList(mAllowedCarrierInfo);
out.writeTypedList(mExcludedCarrierInfo);
+ out.writeBoolean(mUseCarrierLockInfo);
}
}
@@ -451,7 +463,8 @@
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 @@
TelephonyManager.CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED;
mRules.mAllowedCarrierInfo.clear();
mRules.mExcludedCarrierInfo.clear();
+ mRules.mUseCarrierLockInfo = false;
}
return this;
}
@@ -572,5 +586,16 @@
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;
+ }
}
}
diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java
index abcce5f..0f54e8d 100644
--- a/telephony/java/android/telephony/DomainSelectionService.java
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -16,12 +16,14 @@
package android.telephony;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
+import android.net.Uri;
import android.os.Build;
import android.os.CancellationSignal;
import android.os.IBinder;
@@ -40,6 +42,7 @@
import com.android.internal.telephony.ITransportSelectorResultCallback;
import com.android.internal.telephony.IWwanSelectorCallback;
import com.android.internal.telephony.IWwanSelectorResultCallback;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.TelephonyUtils;
import com.android.telephony.Rlog;
@@ -55,12 +58,38 @@
import java.util.function.Consumer;
/**
- * Main domain selection implementation for various telephony features.
- *
- * The telephony framework will bind to the {@link DomainSelectionService}.
+ * Base domain selection implementation.
+ * <p>
+ * Services that extend {@link DomainSelectionService} must register the service in their
+ * AndroidManifest.xml to be detected by the framework.
+ * <p>
+ * 1) The application must declare that they use the
+ * android.permission.BIND_DOMAIN_SELECTION_SERVICE permission.
+ * <p>
+ * 2) The DomainSelectionService definition in the manifest must follow this format:
+ * <pre>
+ * {@code
+ * ...
+ * <service android:name=".EgDomainSelectionService"
+ * android:permission="android.permission.BIND_DOMAIN_SELECTION_SERVICE" >
+ * <intent-filter>
+ * <action android:name="android.telephony.DomainSelectionService" />
+ * </intent-filter>
+ * </service>
+ * ...
+ * }
+ * </pre>
+ * <p>
+ * The ComponentName corresponding to this DomainSelectionService component MUST also be set
+ * as the system domain selection implementation in order to be bound.
+ * The system domain selection implementation is set in the device overlay for
+ * {@code config_domain_selection_service_component_name}
+ * in {@code packages/services/Telephony/res/values/config.xml}.
*
* @hide
*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public class DomainSelectionService extends Service {
private static final String LOG_TAG = "DomainSelectionService";
@@ -78,16 +107,13 @@
@IntDef(prefix = "SELECTOR_TYPE_",
value = {
SELECTOR_TYPE_CALLING,
- SELECTOR_TYPE_SMS,
- SELECTOR_TYPE_UT})
+ SELECTOR_TYPE_SMS})
public @interface SelectorType {}
/** Indicates the domain selector type for calling. */
public static final int SELECTOR_TYPE_CALLING = 1;
/** Indicates the domain selector type for sms. */
public static final int SELECTOR_TYPE_SMS = 2;
- /** Indicates the domain selector type for supplementary services. */
- public static final int SELECTOR_TYPE_UT = 3;
/** Indicates that the modem can scan for emergency service as per modem’s implementation. */
public static final int SCAN_TYPE_NO_PREFERENCE = 0;
@@ -110,51 +136,52 @@
/**
* Contains attributes required to determine the domain for a telephony service.
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final class SelectionAttributes implements Parcelable {
private static final String TAG = "SelectionAttributes";
- private int mSlotId;
+ private int mSlotIndex;
private int mSubId;
private @Nullable String mCallId;
- private @Nullable String mNumber;
+ private @Nullable Uri mAddress;
private @SelectorType int mSelectorType;
private boolean mIsVideoCall;
private boolean mIsEmergency;
+ private boolean mIsTestEmergencyNumber;
private boolean mIsExitedFromAirplaneMode;
- //private @Nullable UtAttributes mUtAttributes;
private @Nullable ImsReasonInfo mImsReasonInfo;
private @PreciseDisconnectCauses int mCause;
private @Nullable EmergencyRegResult mEmergencyRegResult;
/**
- * @param slotId The slot identifier.
- * @param subId The subscription identifier.
+ * @param slotIndex The logical slot index.
+ * @param subscriptionId The subscription identifier.
* @param callId The call identifier.
- * @param number The dialed number.
+ * @param address The dialed address.
* @param selectorType Indicates the requested domain selector type.
* @param video Indicates it's a video call.
* @param emergency Indicates it's emergency service.
+ * @param isTest Indicates it's a test emergency number.
* @param exited {@code true} if the request caused the device to move out of airplane mode.
* @param imsReasonInfo The reason why the last PS attempt failed.
* @param cause The reason why the last CS attempt failed.
* @param regResult The current registration result for emergency services.
*/
- private SelectionAttributes(int slotId, int subId, @Nullable String callId,
- @Nullable String number, @SelectorType int selectorType,
- boolean video, boolean emergency, boolean exited,
- /*UtAttributes attr,*/
+ private SelectionAttributes(int slotIndex, int subscriptionId, @Nullable String callId,
+ @Nullable Uri address, @SelectorType int selectorType,
+ boolean video, boolean emergency, boolean isTest, boolean exited,
@Nullable ImsReasonInfo imsReasonInfo, @PreciseDisconnectCauses int cause,
@Nullable EmergencyRegResult regResult) {
- mSlotId = slotId;
- mSubId = subId;
+ mSlotIndex = slotIndex;
+ mSubId = subscriptionId;
mCallId = callId;
- mNumber = number;
+ mAddress = address;
mSelectorType = selectorType;
mIsVideoCall = video;
mIsEmergency = emergency;
+ mIsTestEmergencyNumber = isTest;
mIsExitedFromAirplaneMode = exited;
- //mUtAttributes = attr;
mImsReasonInfo = imsReasonInfo;
mCause = cause;
mEmergencyRegResult = regResult;
@@ -167,14 +194,14 @@
* @hide
*/
public SelectionAttributes(@NonNull SelectionAttributes s) {
- mSlotId = s.mSlotId;
+ mSlotIndex = s.mSlotIndex;
mSubId = s.mSubId;
mCallId = s.mCallId;
- mNumber = s.mNumber;
+ mAddress = s.mAddress;
mSelectorType = s.mSelectorType;
mIsEmergency = s.mIsEmergency;
+ mIsTestEmergencyNumber = s.mIsTestEmergencyNumber;
mIsExitedFromAirplaneMode = s.mIsExitedFromAirplaneMode;
- //mUtAttributes = s.mUtAttributes;
mImsReasonInfo = s.mImsReasonInfo;
mCause = s.mCause;
mEmergencyRegResult = s.mEmergencyRegResult;
@@ -188,16 +215,16 @@
}
/**
- * @return The slot identifier.
+ * @return The logical slot index.
*/
- public int getSlotId() {
- return mSlotId;
+ public int getSlotIndex() {
+ return mSlotIndex;
}
/**
* @return The subscription identifier.
*/
- public int getSubId() {
+ public int getSubscriptionId() {
return mSubId;
}
@@ -209,10 +236,10 @@
}
/**
- * @return The dialed number.
+ * @return The dialed address.
*/
- public @Nullable String getNumber() {
- return mNumber;
+ public @Nullable Uri getAddress() {
+ return mAddress;
}
/**
@@ -237,18 +264,19 @@
}
/**
+ * @return {@code true} if the dialed number is a test emergency number.
+ */
+ public boolean isTestEmergencyNumber() {
+ return mIsTestEmergencyNumber;
+ }
+
+ /**
* @return {@code true} if the request caused the device to move out of airplane mode.
*/
public boolean isExitedFromAirplaneMode() {
return mIsExitedFromAirplaneMode;
}
- /*
- public @Nullable UtAttributes getUtAttributes();
- return mUtAttributes;
- }
- */
-
/**
* @return The PS disconnect cause if trying over PS resulted in a failure and
* reselection is required.
@@ -274,13 +302,14 @@
@Override
public @NonNull String toString() {
- return "{ slotId=" + mSlotId
+ return "{ slotIndex=" + mSlotIndex
+ ", subId=" + mSubId
+ ", callId=" + mCallId
- + ", number=" + (Build.IS_DEBUGGABLE ? mNumber : "***")
+ + ", address=" + (Build.IS_DEBUGGABLE ? mAddress : "***")
+ ", type=" + mSelectorType
+ ", videoCall=" + mIsVideoCall
+ ", emergency=" + mIsEmergency
+ + ", isTest=" + mIsTestEmergencyNumber
+ ", airplaneMode=" + mIsExitedFromAirplaneMode
+ ", reasonInfo=" + mImsReasonInfo
+ ", cause=" + mCause
@@ -293,13 +322,13 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SelectionAttributes that = (SelectionAttributes) o;
- return mSlotId == that.mSlotId && mSubId == that.mSubId
+ return mSlotIndex == that.mSlotIndex && mSubId == that.mSubId
&& TextUtils.equals(mCallId, that.mCallId)
- && TextUtils.equals(mNumber, that.mNumber)
+ && equalsHandlesNulls(mAddress, that.mAddress)
&& mSelectorType == that.mSelectorType && mIsVideoCall == that.mIsVideoCall
&& mIsEmergency == that.mIsEmergency
+ && mIsTestEmergencyNumber == that.mIsTestEmergencyNumber
&& mIsExitedFromAirplaneMode == that.mIsExitedFromAirplaneMode
- //&& equalsHandlesNulls(mUtAttributes, that.mUtAttributes)
&& equalsHandlesNulls(mImsReasonInfo, that.mImsReasonInfo)
&& mCause == that.mCause
&& equalsHandlesNulls(mEmergencyRegResult, that.mEmergencyRegResult);
@@ -307,9 +336,9 @@
@Override
public int hashCode() {
- return Objects.hash(mCallId, mNumber, mImsReasonInfo,
- mIsVideoCall, mIsEmergency, mIsExitedFromAirplaneMode, mEmergencyRegResult,
- mSlotId, mSubId, mSelectorType, mCause);
+ return Objects.hash(mCallId, mAddress, mImsReasonInfo,
+ mIsVideoCall, mIsEmergency, mIsTestEmergencyNumber, mIsExitedFromAirplaneMode,
+ mEmergencyRegResult, mSlotIndex, mSubId, mSelectorType, mCause);
}
@Override
@@ -319,30 +348,31 @@
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
- out.writeInt(mSlotId);
+ out.writeInt(mSlotIndex);
out.writeInt(mSubId);
out.writeString8(mCallId);
- out.writeString8(mNumber);
+ out.writeParcelable(mAddress, 0);
out.writeInt(mSelectorType);
out.writeBoolean(mIsVideoCall);
out.writeBoolean(mIsEmergency);
+ out.writeBoolean(mIsTestEmergencyNumber);
out.writeBoolean(mIsExitedFromAirplaneMode);
- //out.writeParcelable(mUtAttributes, 0);
out.writeParcelable(mImsReasonInfo, 0);
out.writeInt(mCause);
out.writeParcelable(mEmergencyRegResult, 0);
}
private void readFromParcel(@NonNull Parcel in) {
- mSlotId = in.readInt();
+ mSlotIndex = in.readInt();
mSubId = in.readInt();
mCallId = in.readString8();
- mNumber = in.readString8();
+ mAddress = in.readParcelable(Uri.class.getClassLoader(),
+ android.net.Uri.class);
mSelectorType = in.readInt();
mIsVideoCall = in.readBoolean();
mIsEmergency = in.readBoolean();
+ mIsTestEmergencyNumber = in.readBoolean();
mIsExitedFromAirplaneMode = in.readBoolean();
- //mUtAttributes = s.mUtAttributes;
mImsReasonInfo = in.readParcelable(ImsReasonInfo.class.getClassLoader(),
android.telephony.ims.ImsReasonInfo.class);
mCause = in.readInt();
@@ -370,16 +400,17 @@
/**
* Builder class creating a new instance.
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final class Builder {
- private final int mSlotId;
+ private final int mSlotIndex;
private final int mSubId;
private @Nullable String mCallId;
- private @Nullable String mNumber;
+ private @Nullable Uri mAddress;
private final @SelectorType int mSelectorType;
private boolean mIsVideoCall;
private boolean mIsEmergency;
+ private boolean mIsTestEmergencyNumber;
private boolean mIsExitedFromAirplaneMode;
- //private @Nullable UtAttributes mUtAttributes;
private @Nullable ImsReasonInfo mImsReasonInfo;
private @PreciseDisconnectCauses int mCause;
private @Nullable EmergencyRegResult mEmergencyRegResult;
@@ -387,9 +418,9 @@
/**
* Default constructor for Builder.
*/
- public Builder(int slotId, int subId, @SelectorType int selectorType) {
- mSlotId = slotId;
- mSubId = subId;
+ public Builder(int slotIndex, int subscriptionId, @SelectorType int selectorType) {
+ mSlotIndex = slotIndex;
+ mSubId = subscriptionId;
mSelectorType = selectorType;
}
@@ -405,35 +436,46 @@
}
/**
- * Sets the dialed number.
+ * Sets the dialed address.
*
- * @param number The dialed number.
+ * @param address The dialed address.
* @return The same instance of the builder.
*/
- public @NonNull Builder setNumber(@NonNull String number) {
- mNumber = number;
+ public @NonNull Builder setAddress(@NonNull Uri address) {
+ mAddress = address;
return this;
}
/**
* Sets whether it's a video call or not.
*
- * @param video Indicates it's a video call.
+ * @param isVideo Indicates it's a video call.
* @return The same instance of the builder.
*/
- public @NonNull Builder setVideoCall(boolean video) {
- mIsVideoCall = video;
+ public @NonNull Builder setVideoCall(boolean isVideo) {
+ mIsVideoCall = isVideo;
return this;
}
/**
* Sets whether it's an emergency service or not.
*
- * @param emergency Indicates it's emergency service.
+ * @param isEmergency Indicates it's emergency service.
* @return The same instance of the builder.
*/
- public @NonNull Builder setEmergency(boolean emergency) {
- mIsEmergency = emergency;
+ public @NonNull Builder setEmergency(boolean isEmergency) {
+ mIsEmergency = isEmergency;
+ return this;
+ }
+
+ /**
+ * Sets whether it's a test emergency number or not.
+ *
+ * @param isTest Indicates it's a test emergency number.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setTestEmergencyNumber(boolean isTest) {
+ mIsTestEmergencyNumber = isTest;
return this;
}
@@ -450,20 +492,6 @@
}
/**
- * Sets the Ut service attributes.
- * Only applicable for SELECTOR_TYPE_UT
- *
- * @param attr Ut services attributes.
- * @return The same instance of the builder.
- */
- /*
- public @NonNull Builder setUtAttributes(@NonNull UtAttributes attr);
- mUtAttributes = attr;
- return this;
- }
- */
-
- /**
* Sets an optional reason why the last PS attempt failed.
*
* @param info The reason why the last PS attempt failed.
@@ -501,9 +529,10 @@
* @return The SelectionAttributes object.
*/
public @NonNull SelectionAttributes build() {
- return new SelectionAttributes(mSlotId, mSubId, mCallId, mNumber, mSelectorType,
- mIsVideoCall, mIsEmergency, mIsExitedFromAirplaneMode, /*mUtAttributes,*/
- mImsReasonInfo, mCause, mEmergencyRegResult);
+ return new SelectionAttributes(mSlotIndex, mSubId, mCallId, mAddress,
+ mSelectorType, mIsVideoCall, mIsEmergency, mIsTestEmergencyNumber,
+ mIsExitedFromAirplaneMode, mImsReasonInfo,
+ mCause, mEmergencyRegResult);
}
}
}
@@ -546,19 +575,6 @@
}
@Override
- public @NonNull WwanSelectorCallback onWwanSelected() {
- WwanSelectorCallback callback = null;
- try {
- IWwanSelectorCallback cb = mCallback.onWwanSelected();
- callback = new WwanSelectorCallbackWrapper(cb, mExecutor);
- } catch (Exception e) {
- Rlog.e(TAG, "onWwanSelected e=" + e);
- }
-
- return callback;
- }
-
- @Override
public void onWwanSelected(Consumer<WwanSelectorCallback> consumer) {
try {
mResultCallback = new ITransportSelectorResultCallbackAdapter(consumer, mExecutor);
@@ -627,15 +643,6 @@
}
@Override
- public void cancelSelection() {
- final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
- if (domainSelector == null) return;
-
- executeMethodAsyncNoException(mExecutor,
- () -> domainSelector.cancelSelection(), TAG, "cancelSelection");
- }
-
- @Override
public void reselectDomain(@NonNull SelectionAttributes attr) {
final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
if (domainSelector == null) return;
@@ -688,14 +695,15 @@
@Override
public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
- @EmergencyScanType int scanType, @NonNull CancellationSignal signal,
+ @EmergencyScanType int scanType, boolean resetScan,
+ @NonNull CancellationSignal signal,
@NonNull Consumer<EmergencyRegResult> consumer) {
try {
if (signal != null) signal.setOnCancelListener(this);
mResultCallback = new IWwanSelectorResultCallbackAdapter(consumer, mExecutor);
mCallback.onRequestEmergencyNetworkScan(
preferredNetworks.stream().mapToInt(Integer::intValue).toArray(),
- scanType, mResultCallback);
+ scanType, resetScan, mResultCallback);
} catch (Exception e) {
Rlog.e(TAG, "onRequestEmergencyNetworkScan e=" + e);
}
@@ -738,7 +746,15 @@
private @NonNull Executor mExecutor;
/**
- * Selects a domain for the given operation.
+ * Selects a calling domain given the SelectionAttributes of the call request.
+ * <p>
+ * When the framework generates a request to place a call, {@link #onDomainSelection}
+ * will be called in order to determine the domain (CS or PS). For PS calls, the transport
+ * (WWAN or WLAN) will also need to be determined.
+ * <p>
+ * Once the domain/transport has been selected or an error has occurred,
+ * {@link TransportSelectorCallback} must be used to communicate the result back
+ * to the framework.
*
* @param attr Required to determine the domain.
* @param callback The callback instance being registered.
@@ -748,23 +764,24 @@
}
/**
- * Notifies the change in {@link ServiceState} for a specific slot.
+ * Notifies the change in {@link ServiceState} for a specific logical slot index.
*
- * @param slotId For which the state changed.
- * @param subId For which the state changed.
+ * @param slotIndex For which the state changed.
+ * @param subscriptionId For which the state changed.
* @param serviceState Updated {@link ServiceState}.
*/
- public void onServiceStateUpdated(int slotId, int subId, @NonNull ServiceState serviceState) {
+ public void onServiceStateUpdated(int slotIndex, int subscriptionId,
+ @NonNull ServiceState serviceState) {
}
/**
- * Notifies the change in {@link BarringInfo} for a specific slot.
+ * Notifies the change in {@link BarringInfo} for a specific logical slot index.
*
- * @param slotId For which the state changed.
- * @param subId For which the state changed.
+ * @param slotIndex For which the state changed.
+ * @param subscriptionId For which the state changed.
* @param info Updated {@link BarringInfo}.
*/
- public void onBarringInfoUpdated(int slotId, int subId, @NonNull BarringInfo info) {
+ public void onBarringInfoUpdated(int slotIndex, int subscriptionId, @NonNull BarringInfo info) {
}
private final IBinder mDomainSelectionServiceController =
@@ -779,16 +796,19 @@
}
@Override
- public void updateServiceState(int slotId, int subId, @NonNull ServiceState serviceState) {
+ public void updateServiceState(int slotIndex, int subscriptionId,
+ @NonNull ServiceState serviceState) {
executeMethodAsyncNoException(getCachedExecutor(),
- () -> DomainSelectionService.this.onServiceStateUpdated(slotId,
- subId, serviceState), LOG_TAG, "onServiceStateUpdated");
+ () -> DomainSelectionService.this.onServiceStateUpdated(slotIndex,
+ subscriptionId, serviceState), LOG_TAG, "onServiceStateUpdated");
}
@Override
- public void updateBarringInfo(int slotId, int subId, @NonNull BarringInfo info) {
+ public void updateBarringInfo(int slotIndex, int subscriptionId,
+ @NonNull BarringInfo info) {
executeMethodAsyncNoException(getCachedExecutor(),
- () -> DomainSelectionService.this.onBarringInfoUpdated(slotId, subId, info),
+ () -> DomainSelectionService.this.onBarringInfoUpdated(slotIndex,
+ subscriptionId, info),
LOG_TAG, "onBarringInfoUpdated");
}
};
@@ -816,7 +836,8 @@
/** @hide */
@Override
- public IBinder onBind(Intent intent) {
+ public @Nullable IBinder onBind(@Nullable Intent intent) {
+ if (intent == null) return null;
if (SERVICE_INTERFACE.equals(intent.getAction())) {
Log.i(LOG_TAG, "DomainSelectionService Bound.");
return mDomainSelectionServiceController;
@@ -825,13 +846,13 @@
}
/**
- * The DomainSelectionService will be able to define an {@link Executor} that the service
- * can use to execute the methods. It has set the default executor as Runnable::run,
+ * The Executor to use when calling callback methods from the framework.
+ * <p>
+ * By default, calls from the framework will use Binder threads to call these methods.
*
- * @return An {@link Executor} to be used.
+ * @return an {@link Executor} used to execute methods called remotely by the framework.
*/
- @SuppressLint("OnNameExpected")
- public @NonNull Executor getExecutor() {
+ public @NonNull Executor onCreateExecutor() {
return Runnable::run;
}
@@ -845,7 +866,7 @@
public @NonNull Executor getCachedExecutor() {
synchronized (mExecutorLock) {
if (mExecutor == null) {
- Executor e = getExecutor();
+ Executor e = onCreateExecutor();
mExecutor = (e != null) ? e : Runnable::run;
}
return mExecutor;
diff --git a/telephony/java/android/telephony/DomainSelector.java b/telephony/java/android/telephony/DomainSelector.java
index 0871831..5d25d3a 100644
--- a/telephony/java/android/telephony/DomainSelector.java
+++ b/telephony/java/android/telephony/DomainSelector.java
@@ -16,22 +16,23 @@
package android.telephony;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.telephony.DomainSelectionService.SelectionAttributes;
+import com.android.internal.telephony.flags.Flags;
+
/**
* Implemented as part of the {@link DomainSelectionService} to implement domain selection
- * for a specific use case.
+ * for a specific use case and receive signals from the framework to reselect a new domain
+ * when a previous domain selection fails or finish a selection when the call connects successfully.
* @hide
*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public interface DomainSelector {
/**
- * Cancel an ongoing selection operation. It is up to the DomainSelectionService
- * to clean up all ongoing operations with the framework.
- */
- void cancelSelection();
-
- /**
* Reselect a domain due to the call not setting up properly.
*
* @param attr attributes required to select the domain.
diff --git a/telephony/java/android/telephony/EmergencyRegResult.java b/telephony/java/android/telephony/EmergencyRegResult.java
index 5aed412..15579be 100644
--- a/telephony/java/android/telephony/EmergencyRegResult.java
+++ b/telephony/java/android/telephony/EmergencyRegResult.java
@@ -16,17 +16,25 @@
package android.telephony;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.internal.telephony.flags.Flags;
+
import java.util.Objects;
/**
- * Contains attributes required to determine the domain for a telephony service
+ * Contains attributes required to determine the domain for a telephony service, including
+ * the network registration state.
+ *
* @hide
*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public final class EmergencyRegResult implements Parcelable {
/**
@@ -77,7 +85,7 @@
* The ISO-3166-1 alpha-2 country code equivalent for the network's country code,
* empty string if unknown.
*/
- private @NonNull String mIso;
+ private @NonNull String mCountryIso;
/**
* Constructor
@@ -108,7 +116,7 @@
mNwProvidedEmf = emf;
mMcc = mcc;
mMnc = mnc;
- mIso = iso;
+ mCountryIso = iso;
}
/**
@@ -127,7 +135,7 @@
mNwProvidedEmf = s.mNwProvidedEmf;
mMcc = s.mMcc;
mMnc = s.mMnc;
- mIso = s.mIso;
+ mCountryIso = s.mCountryIso;
}
/**
@@ -226,8 +234,8 @@
*
* @return Country code.
*/
- public @NonNull String getIso() {
- return mIso;
+ public @NonNull String getCountryIso() {
+ return mCountryIso;
}
@Override
@@ -242,7 +250,7 @@
+ ", emf=" + mNwProvidedEmf
+ ", mcc=" + mMcc
+ ", mnc=" + mMnc
- + ", iso=" + mIso
+ + ", iso=" + mCountryIso
+ " }";
}
@@ -260,7 +268,7 @@
&& mNwProvidedEmf == that.mNwProvidedEmf
&& TextUtils.equals(mMcc, that.mMcc)
&& TextUtils.equals(mMnc, that.mMnc)
- && TextUtils.equals(mIso, that.mIso);
+ && TextUtils.equals(mCountryIso, that.mCountryIso);
}
@Override
@@ -268,7 +276,7 @@
return Objects.hash(mAccessNetworkType, mRegState, mDomain,
mIsVopsSupported, mIsEmcBearerSupported,
mNwProvidedEmc, mNwProvidedEmf,
- mMcc, mMnc, mIso);
+ mMcc, mMnc, mCountryIso);
}
@Override
@@ -287,7 +295,7 @@
out.writeInt(mNwProvidedEmf);
out.writeString8(mMcc);
out.writeString8(mMnc);
- out.writeString8(mIso);
+ out.writeString8(mCountryIso);
}
private void readFromParcel(@NonNull Parcel in) {
@@ -300,7 +308,7 @@
mNwProvidedEmf = in.readInt();
mMcc = in.readString8();
mMnc = in.readString8();
- mIso = in.readString8();
+ mCountryIso = in.readString8();
}
public static final @NonNull Creator<EmergencyRegResult> CREATOR =
diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java
index 1cfd22c..d9437ab 100644
--- a/telephony/java/android/telephony/PreciseDisconnectCause.java
+++ b/telephony/java/android/telephony/PreciseDisconnectCause.java
@@ -16,8 +16,11 @@
package android.telephony;
+import android.annotation.FlaggedApi;
import android.annotation.SystemApi;
+import com.android.internal.telephony.flags.Flags;
+
/**
* Contains precise disconnect call causes generated by the framework and the RIL.
* @hide
@@ -238,18 +241,18 @@
/**
* Dialing emergency calls is currently unavailable.
* The call should be redialed on the other subscription silently.
- * If there is no other subscription available, the call may be redialed
+ * If there are no other subscriptions available then the call may be redialed
* on this subscription again.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final int EMERGENCY_TEMP_FAILURE = 325;
/**
* Dialing emergency calls is currently unavailable.
* The call should be redialed on the other subscription silently.
- * Even if there is no other subscription available, the call should not
+ * If there are no other subscriptions available then the call should not
* be redialed on this subscription again.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public static final int EMERGENCY_PERM_FAILURE = 326;
/** Mobile station (MS) is locked until next power cycle. */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f82463b..61c7a42 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -19207,13 +19207,11 @@
/**
* Returns whether the domain selection service is supported.
*
- * <p>Requires Permission:
- * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}.
- *
* @return {@code true} if the domain selection service is supported.
* @hide
*/
- @TestApi
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public boolean isDomainSelectionSupported() {
diff --git a/telephony/java/android/telephony/TransportSelectorCallback.java b/telephony/java/android/telephony/TransportSelectorCallback.java
index 04752e4..0f5dd27 100644
--- a/telephony/java/android/telephony/TransportSelectorCallback.java
+++ b/telephony/java/android/telephony/TransportSelectorCallback.java
@@ -16,18 +16,28 @@
package android.telephony;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.telephony.Annotation.DisconnectCauses;
+import com.android.internal.telephony.flags.Flags;
+
import java.util.function.Consumer;
/**
- * A callback class used to receive the transport selection result.
+ * A callback class used by the domain selection module to notify the framework of the result of
+ * selecting a domain for a call.
* @hide
*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public interface TransportSelectorCallback {
/**
* Notify that {@link DomainSelector} instance has been created for the selection request.
+ * <p>
+ * DomainSelector callbacks run using the executor specified in
+ * {@link DomainSelectionService#onCreateExecutor}.
*
* @param selector the {@link DomainSelector} instance created.
*/
@@ -41,16 +51,13 @@
void onWlanSelected(boolean useEmergencyPdn);
/**
- * Notify that WWAN transport has been selected.
- */
- @NonNull WwanSelectorCallback onWwanSelected();
-
- /**
- * Notify that WWAN transport has been selected.
+ * Notify that WWAN transport has been selected and the next phase of selecting
+ * the PS or CS domain is starting.
*
- * @param consumer The callback to receive the result.
+ * @param consumer The callback used by the {@link DomainSelectionService} to optionally run
+ * emergency network scans and notify the framework of the WWAN transport result.
*/
- void onWwanSelected(Consumer<WwanSelectorCallback> consumer);
+ void onWwanSelected(@NonNull Consumer<WwanSelectorCallback> consumer);
/**
* Notify that selection has terminated because there is no decision that can be made
diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java
index f9c2620..ea83815 100644
--- a/telephony/java/android/telephony/WwanSelectorCallback.java
+++ b/telephony/java/android/telephony/WwanSelectorCallback.java
@@ -16,29 +16,38 @@
package android.telephony;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.CancellationSignal;
import android.telephony.DomainSelectionService.EmergencyScanType;
+import com.android.internal.telephony.flags.Flags;
+
import java.util.List;
import java.util.function.Consumer;
/**
- * A callback class used to receive the domain selection result.
+ * A callback class used to communicate with the framework to request network scans
+ * and notify the framework when a WWAN domain has been selected.
* @hide
*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
public interface WwanSelectorCallback {
/**
* Notify the framework that the {@link DomainSelectionService} has requested an emergency
* network scan as part of selection.
*
- * @param preferredNetworks the ordered list of preferred networks to scan.
- * @param scanType indicates the scan preference, such as full service or limited service.
- * @param signal notifies when the operation is canceled.
- * @param consumer the handler of the response.
+ * @param preferredNetworks The ordered list of preferred networks to scan.
+ * @param scanType Indicates the scan preference, such as full service or limited service.
+ * @param resetScan Indicates that the previous scan result shall be reset before scanning.
+ * @param signal Notifies when the operation is canceled.
+ * @param consumer The handler of the response, which will contain a one-shot result
+ * of the network scan.
*/
void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
- @EmergencyScanType int scanType,
+ @EmergencyScanType int scanType, boolean resetScan,
@NonNull CancellationSignal signal, @NonNull Consumer<EmergencyRegResult> consumer);
/**
diff --git a/telephony/java/com/android/internal/telephony/IDomainSelector.aidl b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
index d94840b..0eeadee7 100644
--- a/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
+++ b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
@@ -19,7 +19,6 @@
import android.telephony.DomainSelectionService.SelectionAttributes;
oneway interface IDomainSelector {
- void cancelSelection();
void reselectDomain(in SelectionAttributes attr);
void finishSelection();
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index a1fc064..213fbc5 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3203,6 +3203,18 @@
boolean setShouldSendDatagramToModemInDemoMode(boolean shouldSendToModemInDemoMode);
/**
+ * @return {@code true} if the DomainSelectionService is set,
+ * {@code false} otherwise.
+ */
+ boolean setDomainSelectionServiceOverride(in ComponentName componentName);
+
+ /**
+ * @return {@code true} if the DomainSelectionService override is cleared,
+ * {@code false} otherwise.
+ */
+ boolean clearDomainSelectionServiceOverride();
+
+ /**
* Enable or disable notifications sent for cellular identifier disclosure events.
*
* Disclosure events are defined as instances where a device has sent a cellular identifier
diff --git a/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
index 6777256d..26daacd 100644
--- a/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
+++ b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
@@ -20,10 +20,9 @@
import com.android.internal.telephony.ITransportSelectorResultCallback;
import com.android.internal.telephony.IWwanSelectorCallback;
-interface ITransportSelectorCallback {
- oneway void onCreated(in IDomainSelector selector);
- oneway void onWlanSelected(boolean useEmergencyPdn);
- IWwanSelectorCallback onWwanSelected();
- oneway void onWwanSelectedAsync(in ITransportSelectorResultCallback cb);
- oneway void onSelectionTerminated(int cause);
+oneway interface ITransportSelectorCallback {
+ void onCreated(in IDomainSelector selector);
+ void onWlanSelected(boolean useEmergencyPdn);
+ void onWwanSelectedAsync(in ITransportSelectorResultCallback cb);
+ void onSelectionTerminated(int cause);
}
diff --git a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
index 94e7c87..87955ac 100644
--- a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
@@ -20,7 +20,7 @@
oneway interface IWwanSelectorCallback {
void onRequestEmergencyNetworkScan(in int[] preferredNetworks,
- int scanType, in IWwanSelectorResultCallback cb);
+ int scanType, boolean resetScan, in IWwanSelectorResultCallback cb);
void onDomainSelected(int domain, boolean useEmergencyPdn);
void onCancel();
}
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index cf2d5d6..d17cd1f 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_input_framework",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
diff --git a/tests/InputMethodStressTest/Android.bp b/tests/InputMethodStressTest/Android.bp
index 58ceb3f..5ed8d8d 100644
--- a/tests/InputMethodStressTest/Android.bp
+++ b/tests/InputMethodStressTest/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_input_method_framework",
default_applicable_licenses: ["frameworks_base_license"],
}
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index 23efe54..2751141 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_framework_android_packages",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp
index ddb3649..12d4338 100644
--- a/tests/UpdatableSystemFontTest/Android.bp
+++ b/tests/UpdatableSystemFontTest/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_android_gpu",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
diff --git a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp
index ed34fa9..0f21035 100644
--- a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp
+++ b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_android_gpu",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
diff --git a/tests/UpdatableSystemFontTest/testdata/Android.bp b/tests/UpdatableSystemFontTest/testdata/Android.bp
index 0bdb3a8..38355530 100644
--- a/tests/UpdatableSystemFontTest/testdata/Android.bp
+++ b/tests/UpdatableSystemFontTest/testdata/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_android_gpu",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
diff --git a/tools/codegen/src/com/android/codegen/FileInfo.kt b/tools/codegen/src/com/android/codegen/FileInfo.kt
index a1d0389..cc3a156 100644
--- a/tools/codegen/src/com/android/codegen/FileInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FileInfo.kt
@@ -238,7 +238,7 @@
} else if (classBounds.isDataclass) {
// Insert placeholder for generated code to be inserted for the 1st time
- chunks.last = (chunks.last as Code)
+ chunks[chunks.lastIndex] = (chunks.last() as Code)
.lines
.dropLastWhile { it.isBlank() }
.run {
@@ -286,4 +286,4 @@
.let { addAll(it) }
}
}
-}
\ No newline at end of file
+}
diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt
index 9ceb204..a40bdd7 100644
--- a/tools/codegen/src/com/android/codegen/Utils.kt
+++ b/tools/codegen/src/com/android/codegen/Utils.kt
@@ -137,14 +137,4 @@
cause)
}
-var <T> MutableList<T>.last
- get() = last()
- set(value) {
- if (isEmpty()) {
- add(value)
- } else {
- this[size - 1] = value
- }
- }
-
-inline fun <T> buildList(init: MutableList<T>.() -> Unit) = mutableListOf<T>().apply(init)
\ No newline at end of file
+inline fun <T> buildList(init: MutableList<T>.() -> Unit) = mutableListOf<T>().apply(init)