Merge "Add service configinfrastructure"
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 ad6eff0..e0df22c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -266,6 +266,8 @@
private final List<RestrictingController> mRestrictiveControllers;
/** Need direct access to this for testing. */
private final StorageController mStorageController;
+ /** Needed to get estimated transfer time. */
+ private final ConnectivityController mConnectivityController;
/** Need directly for sending uid state changes */
private final DeviceIdleJobsController mDeviceIdleJobsController;
/** Needed to get next estimated launch time. */
@@ -464,6 +466,13 @@
case Constants.KEY_RUNTIME_MIN_GUARANTEE_MS:
case Constants.KEY_RUNTIME_MIN_EJ_GUARANTEE_MS:
case Constants.KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS:
+ case Constants.KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS:
+ case Constants.KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS:
+ case Constants.KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS:
+ case Constants.KEY_RUNTIME_USER_INITIATED_LIMIT_MS:
+ case Constants.KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR:
+ case Constants.KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS:
+ case Constants.KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS:
if (!runtimeUpdated) {
mConstants.updateRuntimeConstantsLocked();
runtimeUpdated = true;
@@ -555,6 +564,21 @@
private static final String KEY_RUNTIME_MIN_EJ_GUARANTEE_MS = "runtime_min_ej_guarantee_ms";
private static final String KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS =
"runtime_min_high_priority_guarantee_ms";
+ private static final String KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS =
+ "runtime_min_data_transfer_guarantee_ms";
+ private static final String KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS =
+ "runtime_data_transfer_limit_ms";
+ private static final String KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
+ "runtime_min_user_initiated_guarantee_ms";
+ private static final String KEY_RUNTIME_USER_INITIATED_LIMIT_MS =
+ "runtime_user_initiated_limit_ms";
+ private static final String
+ KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
+ "runtime_min_user_initiated_data_transfer_guarantee_buffer_factor";
+ private static final String KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
+ "runtime_min_user_initiated_data_transfer_guarantee_ms";
+ private static final String KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
+ "runtime_user_initiated_data_transfer_limit_ms";
private static final String KEY_PERSIST_IN_SPLIT_FILES = "persist_in_split_files";
@@ -584,6 +608,20 @@
public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
@VisibleForTesting
static final long DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS = 5 * MINUTE_IN_MILLIS;
+ public static final long DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS =
+ DEFAULT_RUNTIME_MIN_GUARANTEE_MS;
+ public static final long DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS =
+ DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
+ public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
+ Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS);
+ public static final long DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS =
+ Math.max(60 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS);
+ public static final float
+ DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.35f;
+ public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
+ Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS);
+ public static final long DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
+ Math.max(Long.MAX_VALUE, DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS);
static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true;
private static final boolean DEFAULT_USE_TARE_POLICY = false;
@@ -700,6 +738,49 @@
DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS;
/**
+ * The minimum amount of time we try to guarantee normal data transfer jobs will run for.
+ */
+ public long RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS =
+ DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS;
+
+ /**
+ * The maximum amount of time we will let a normal data transfer job run for. This will only
+ * apply if there are no other limits that apply to the specific data transfer job.
+ */
+ public long RUNTIME_DATA_TRANSFER_LIMIT_MS = DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS;
+
+ /**
+ * The minimum amount of time we try to guarantee normal user-initiated jobs will run for.
+ */
+ public long RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
+ DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS;
+
+ /**
+ * The maximum amount of time we will let a user-initiated job run for. This will only
+ * apply if there are no other limits that apply to the specific user-initiated job.
+ */
+ public long RUNTIME_USER_INITIATED_LIMIT_MS = DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS;
+
+ /**
+ * A factor to apply to estimated transfer durations for user-initiated data transfer jobs
+ * so that we give some extra time for unexpected situations. This will be at least 1 and
+ * so can just be multiplied with the original value to get the final value.
+ */
+ public float RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
+ DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR;
+
+ /**
+ * The minimum amount of time we try to guarantee user-initiated data transfer jobs
+ * will run for.
+ */
+ public long RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
+ DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS;
+
+ /** The maximum amount of time we will let a user-initiated data transfer job run for. */
+ public long RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
+ DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
+
+ /**
* Whether to persist jobs in split files (by UID). If false, all persisted jobs will be
* saved in a single file.
*/
@@ -801,7 +882,14 @@
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
KEY_RUNTIME_MIN_GUARANTEE_MS, KEY_RUNTIME_MIN_EJ_GUARANTEE_MS,
- KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS);
+ KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS,
+ KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
+ KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS,
+ KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
+ KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+ KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS);
// Make sure min runtime for regular jobs is at least 10 minutes.
RUNTIME_MIN_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS,
@@ -819,6 +907,49 @@
RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
properties.getLong(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS));
+ // Make sure min runtime is at least as long as regular jobs.
+ RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
+ properties.getLong(
+ KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS));
+ // Max limit should be at least the min guarantee AND the free quota.
+ RUNTIME_DATA_TRANSFER_LIMIT_MS = Math.max(RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ Math.max(RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ properties.getLong(
+ KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS,
+ DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS)));
+ // Make sure min runtime is at least as long as regular jobs.
+ RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
+ properties.getLong(
+ KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS));
+ // Max limit should be at least the min guarantee AND the free quota.
+ RUNTIME_USER_INITIATED_LIMIT_MS = Math.max(RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ Math.max(RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ properties.getLong(
+ KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
+ DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS)));
+ // The buffer factor should be at least 1 (so we don't decrease the time).
+ RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = Math.max(1,
+ properties.getFloat(
+ KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
+ DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR
+ ));
+ // Make sure min runtime is at least as long as other user-initiated jobs.
+ RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = Math.max(
+ RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ properties.getLong(
+ KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+ DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS));
+ // Data transfer requires RUN_LONG_JOBS permission, so the upper limit will be higher
+ // than other jobs.
+ // Max limit should be the min guarantee and the max of other user-initiated jobs.
+ RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = Math.max(
+ RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+ Math.max(RUNTIME_USER_INITIATED_LIMIT_MS,
+ properties.getLong(
+ KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+ DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS)));
}
private boolean updateTareSettingsLocked(boolean isTareEnabled) {
@@ -867,6 +998,20 @@
RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS).println();
pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
.println();
+ pw.print(KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS).println();
+ pw.print(KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS,
+ RUNTIME_DATA_TRANSFER_LIMIT_MS).println();
+ pw.print(KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS).println();
+ pw.print(KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
+ RUNTIME_USER_INITIATED_LIMIT_MS).println();
+ pw.print(KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
+ RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR).println();
+ pw.print(KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+ RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS).println();
+ pw.print(KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+ RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS).println();
pw.print(KEY_PERSIST_IN_SPLIT_FILES, PERSIST_IN_SPLIT_FILES).println();
@@ -1858,9 +2003,9 @@
final FlexibilityController flexibilityController =
new FlexibilityController(this, mPrefetchController);
mControllers.add(flexibilityController);
- final ConnectivityController connectivityController =
+ mConnectivityController =
new ConnectivityController(this, flexibilityController);
- mControllers.add(connectivityController);
+ mControllers.add(mConnectivityController);
mControllers.add(new TimeController(this));
final IdleController idleController = new IdleController(this, flexibilityController);
mControllers.add(idleController);
@@ -1876,16 +2021,16 @@
mDeviceIdleJobsController = new DeviceIdleJobsController(this);
mControllers.add(mDeviceIdleJobsController);
mQuotaController =
- new QuotaController(this, backgroundJobsController, connectivityController);
+ new QuotaController(this, backgroundJobsController, mConnectivityController);
mControllers.add(mQuotaController);
mControllers.add(new ComponentController(this));
mTareController =
- new TareController(this, backgroundJobsController, connectivityController);
+ new TareController(this, backgroundJobsController, mConnectivityController);
mControllers.add(mTareController);
mRestrictiveControllers = new ArrayList<>();
mRestrictiveControllers.add(batteryController);
- mRestrictiveControllers.add(connectivityController);
+ mRestrictiveControllers.add(mConnectivityController);
mRestrictiveControllers.add(idleController);
// Create restrictions
@@ -3028,7 +3173,30 @@
/** Returns the minimum amount of time we should let this job run before timing out. */
public long getMinJobExecutionGuaranteeMs(JobStatus job) {
synchronized (mLock) {
- if (job.shouldTreatAsExpeditedJob()) {
+ final boolean shouldTreatAsDataTransfer = job.getJob().isDataTransfer()
+ && checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
+ if (job.shouldTreatAsUserInitiated()) {
+ if (shouldTreatAsDataTransfer) {
+ final long estimatedTransferTimeMs =
+ mConnectivityController.getEstimatedTransferTimeMs(job);
+ if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
+ return mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS;
+ }
+ // Try to give the job at least as much time as we think the transfer will take,
+ // but cap it at the maximum limit
+ final long factoredTransferTimeMs = (long) (estimatedTransferTimeMs
+ * mConstants
+ .RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
+ return Math.min(mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+ Math.max(factoredTransferTimeMs,
+ mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
+ ));
+ }
+ return mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS;
+ } else if (shouldTreatAsDataTransfer) {
+ // For now, don't increase a bg data transfer's minimum guarantee.
+ return mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS;
+ } else if (job.shouldTreatAsExpeditedJob()) {
// Don't guarantee RESTRICTED jobs more than 5 minutes.
return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX
? mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS
@@ -3044,6 +3212,16 @@
/** Returns the maximum amount of time this job could run for. */
public long getMaxJobExecutionTimeMs(JobStatus job) {
synchronized (mLock) {
+ final boolean shouldTreatAsDataTransfer = job.getJob().isDataTransfer()
+ && checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
+ if (job.shouldTreatAsUserInitiated()) {
+ if (shouldTreatAsDataTransfer) {
+ return mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
+ }
+ return mConstants.RUNTIME_USER_INITIATED_LIMIT_MS;
+ } else if (shouldTreatAsDataTransfer) {
+ return mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS;
+ }
return Math.min(mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mConstants.USE_TARE_POLICY
? mTareController.getMaxJobExecutionTimeMsLocked(job)
@@ -3703,13 +3881,6 @@
return checkRunLongJobsPermission(uid, packageName);
}
- private boolean checkRunLongJobsPermission(int packageUid, String packageName) {
- // Returns true if both the appop and permission are granted.
- return PermissionChecker.checkPermissionForPreflight(getContext(),
- android.Manifest.permission.RUN_LONG_JOBS, PermissionChecker.PID_UNKNOWN,
- packageUid, packageName) == PermissionChecker.PERMISSION_GRANTED;
- }
-
/**
* "dumpsys" infrastructure
*/
@@ -3985,13 +4156,27 @@
}
}
+ private boolean checkRunLongJobsPermission(int packageUid, String packageName) {
+ // Returns true if both the appop and permission are granted.
+ return PermissionChecker.checkPermissionForPreflight(getTestableContext(),
+ android.Manifest.permission.RUN_LONG_JOBS, PermissionChecker.PID_UNKNOWN,
+ packageUid, packageName) == PermissionChecker.PERMISSION_GRANTED;
+ }
+
+ @VisibleForTesting
+ protected ConnectivityController getConnectivityController() {
+ return mConnectivityController;
+ }
+
// Shell command infrastructure
int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) {
try {
final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
if (uid < 0) {
- pw.print("unknown("); pw.print(pkgName); pw.println(")");
+ pw.print("unknown(");
+ pw.print(pkgName);
+ pw.println(")");
return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
}
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 16dd1672..6166921 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
@@ -42,7 +42,6 @@
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.DataUnit;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pools;
@@ -82,6 +81,8 @@
private static final boolean DEBUG = JobSchedulerService.DEBUG
|| Log.isLoggable(TAG, Log.DEBUG);
+ public static final long UNKNOWN_TIME = -1L;
+
// The networking stack has a hard limit so we can't make this configurable.
private static final int MAX_NETWORK_CALLBACKS = 125;
/**
@@ -570,9 +571,8 @@
// If we don't know the bandwidth, all we can do is hope the job finishes the minimum
// chunk in time.
if (bandwidthDown > 0) {
- // Divide by 8 to convert bits to bytes.
- final long estimatedMillis = ((minimumChunkBytes * DateUtils.SECOND_IN_MILLIS)
- / (DataUnit.KIBIBYTES.toBytes(bandwidthDown) / 8));
+ final long estimatedMillis =
+ calculateTransferTimeMs(minimumChunkBytes, bandwidthDown);
if (estimatedMillis > maxJobExecutionTimeMs) {
// If we'd never finish the minimum chunk before the timeout, we'd be insane!
Slog.w(TAG, "Minimum chunk " + minimumChunkBytes + " bytes over "
@@ -585,9 +585,8 @@
final long bandwidthUp = capabilities.getLinkUpstreamBandwidthKbps();
// If we don't know the bandwidth, all we can do is hope the job finishes in time.
if (bandwidthUp > 0) {
- // Divide by 8 to convert bits to bytes.
- final long estimatedMillis = ((minimumChunkBytes * DateUtils.SECOND_IN_MILLIS)
- / (DataUnit.KIBIBYTES.toBytes(bandwidthUp) / 8));
+ final long estimatedMillis =
+ calculateTransferTimeMs(minimumChunkBytes, bandwidthUp);
if (estimatedMillis > maxJobExecutionTimeMs) {
// If we'd never finish the minimum chunk before the timeout, we'd be insane!
Slog.w(TAG, "Minimum chunk " + minimumChunkBytes + " bytes over " + bandwidthUp
@@ -615,9 +614,7 @@
final long bandwidth = capabilities.getLinkDownstreamBandwidthKbps();
// If we don't know the bandwidth, all we can do is hope the job finishes in time.
if (bandwidth > 0) {
- // Divide by 8 to convert bits to bytes.
- final long estimatedMillis = ((downloadBytes * DateUtils.SECOND_IN_MILLIS)
- / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
+ final long estimatedMillis = calculateTransferTimeMs(downloadBytes, bandwidth);
if (estimatedMillis > maxJobExecutionTimeMs) {
// If we'd never finish before the timeout, we'd be insane!
Slog.w(TAG, "Estimated " + downloadBytes + " download bytes over " + bandwidth
@@ -633,9 +630,7 @@
final long bandwidth = capabilities.getLinkUpstreamBandwidthKbps();
// If we don't know the bandwidth, all we can do is hope the job finishes in time.
if (bandwidth > 0) {
- // Divide by 8 to convert bits to bytes.
- final long estimatedMillis = ((uploadBytes * DateUtils.SECOND_IN_MILLIS)
- / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
+ final long estimatedMillis = calculateTransferTimeMs(uploadBytes, bandwidth);
if (estimatedMillis > maxJobExecutionTimeMs) {
// If we'd never finish before the timeout, we'd be insane!
Slog.w(TAG, "Estimated " + uploadBytes + " upload bytes over " + bandwidth
@@ -649,6 +644,48 @@
return false;
}
+ /**
+ * Return the estimated amount of time this job will be transferring data,
+ * based on the current network speed.
+ */
+ public long getEstimatedTransferTimeMs(JobStatus jobStatus) {
+ final long downloadBytes = jobStatus.getEstimatedNetworkDownloadBytes();
+ final long uploadBytes = jobStatus.getEstimatedNetworkUploadBytes();
+ if (downloadBytes == JobInfo.NETWORK_BYTES_UNKNOWN
+ && uploadBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
+ return UNKNOWN_TIME;
+ }
+ if (jobStatus.network == null) {
+ // This job doesn't have a network assigned.
+ return UNKNOWN_TIME;
+ }
+ NetworkCapabilities capabilities = getNetworkCapabilities(jobStatus.network);
+ if (capabilities == null) {
+ return UNKNOWN_TIME;
+ }
+ final long estimatedDownloadTimeMs = calculateTransferTimeMs(downloadBytes,
+ capabilities.getLinkDownstreamBandwidthKbps());
+ final long estimatedUploadTimeMs = calculateTransferTimeMs(uploadBytes,
+ capabilities.getLinkUpstreamBandwidthKbps());
+ if (estimatedDownloadTimeMs == UNKNOWN_TIME) {
+ return estimatedUploadTimeMs;
+ } else if (estimatedUploadTimeMs == UNKNOWN_TIME) {
+ return estimatedDownloadTimeMs;
+ }
+ return estimatedDownloadTimeMs + estimatedUploadTimeMs;
+ }
+
+ @VisibleForTesting
+ static long calculateTransferTimeMs(long transferBytes, long bandwidthKbps) {
+ if (transferBytes == JobInfo.NETWORK_BYTES_UNKNOWN || bandwidthKbps <= 0) {
+ return UNKNOWN_TIME;
+ }
+ return (transferBytes * DateUtils.SECOND_IN_MILLIS)
+ // Multiply by 1000 to convert kilobits to bits.
+ // Divide by 8 to convert bits to bytes.
+ / (bandwidthKbps * 1000 / 8);
+ }
+
private static boolean isCongestionDelayed(JobStatus jobStatus, Network network,
NetworkCapabilities capabilities, Constants constants) {
// If network is congested, and job is less than 50% through the
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 83b6a8e..419127e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -1345,6 +1345,15 @@
}
/**
+ * @return true if the job was scheduled as a user-initiated job and it hasn't been downgraded
+ * for any reason.
+ */
+ public boolean shouldTreatAsUserInitiated() {
+ // TODO(248386641): implement
+ return false;
+ }
+
+ /**
* Return a summary that uniquely identifies the underlying job.
*/
@NonNull
diff --git a/api/Android.bp b/api/Android.bp
index e1e9fcf..318748e 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -122,6 +122,7 @@
"service-healthconnect",
"service-media-s",
"service-permission",
+ "service-rkp",
"service-sdksandbox",
],
}
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index c4d90c6..80512f7 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -1112,6 +1112,11 @@
int nextReadPos;
+ if (strlen(l) == 0) {
+ s = ++endl;
+ continue;
+ }
+
int topLineNumbers = sscanf(l, "%d %d %d %d", &width, &height, &fps, &progress);
if (topLineNumbers == 3 || topLineNumbers == 4) {
// SLOGD("> w=%d, h=%d, fps=%d, progress=%d", width, height, fps, progress);
diff --git a/core/api/current.txt b/core/api/current.txt
index 434b60d..e50b058 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7475,7 +7475,7 @@
method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName);
method public boolean getCameraDisabled(@Nullable android.content.ComponentName);
method @Deprecated @Nullable public String getCertInstallerPackage(@NonNull android.content.ComponentName) throws java.lang.SecurityException;
- method @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
+ method @Deprecated @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
method public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName);
method public boolean getCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName);
method @NonNull public java.util.Set<java.lang.String> getCrossProfilePackages(@NonNull android.content.ComponentName);
@@ -7624,7 +7624,7 @@
method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
method public void setCommonCriteriaModeEnabled(@NonNull android.content.ComponentName, boolean);
method public void setConfiguredNetworksLockdownState(@NonNull android.content.ComponentName, boolean);
- method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
+ method @Deprecated public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCrossProfilePackages(@NonNull android.content.ComponentName, @NonNull java.util.Set<java.lang.String>);
@@ -20206,7 +20206,7 @@
public final class AltitudeConverter {
ctor public AltitudeConverter();
- method @WorkerThread public void addMslAltitude(@NonNull android.content.Context, @NonNull android.location.Location) throws java.io.IOException;
+ method @WorkerThread public void addMslAltitudeToLocation(@NonNull android.content.Context, @NonNull android.location.Location) throws java.io.IOException;
}
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index eb2d2ca..2aaa8e6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -826,7 +826,6 @@
method public int describeContents();
method public int getFpsOverride();
method public float getScalingFactor();
- method @NonNull public android.app.GameModeConfiguration.Builder toBuilder();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.GameModeConfiguration> CREATOR;
field public static final int FPS_OVERRIDE_NONE = 0; // 0x0
@@ -834,6 +833,7 @@
public static final class GameModeConfiguration.Builder {
ctor public GameModeConfiguration.Builder();
+ ctor public GameModeConfiguration.Builder(@NonNull android.app.GameModeConfiguration);
method @NonNull public android.app.GameModeConfiguration build();
method @NonNull public android.app.GameModeConfiguration.Builder setFpsOverride(int);
method @NonNull public android.app.GameModeConfiguration.Builder setScalingFactor(float);
@@ -845,7 +845,7 @@
method public int getActiveGameMode();
method @NonNull public int[] getAvailableGameModes();
method @Nullable public android.app.GameModeConfiguration getGameModeConfiguration(int);
- method @NonNull public int[] getOptedInGameModes();
+ method @NonNull public int[] getOverriddenGameModes();
method public boolean isDownscalingAllowed();
method public boolean isFpsOverrideAllowed();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -860,7 +860,7 @@
method @NonNull public android.app.GameModeInfo.Builder setDownscalingAllowed(boolean);
method @NonNull public android.app.GameModeInfo.Builder setFpsOverrideAllowed(boolean);
method @NonNull public android.app.GameModeInfo.Builder setGameModeConfiguration(int, @NonNull android.app.GameModeConfiguration);
- method @NonNull public android.app.GameModeInfo.Builder setOptedInGameModes(@NonNull int[]);
+ method @NonNull public android.app.GameModeInfo.Builder setOverriddenGameModes(@NonNull int[]);
}
public abstract class InstantAppResolverService extends android.app.Service {
@@ -3772,6 +3772,7 @@
field @Deprecated public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
field public static final int PROTECTION_FLAG_KNOWN_SIGNER = 134217728; // 0x8000000
+ field public static final int PROTECTION_FLAG_MODULE = 4194304; // 0x400000
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 43d4530..3d30c0f 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -38,6 +38,7 @@
field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
+ field public static final String REMAP_MODIFIER_KEYS = "android.permission.REMAP_MODIFIER_KEYS";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String REQUEST_UNIQUE_ID_ATTESTATION = "android.permission.REQUEST_UNIQUE_ID_ATTESTATION";
field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
@@ -1294,8 +1295,11 @@
public final class InputManager {
method public void addUniqueIdAssociation(@NonNull String, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings();
method @Nullable public String getCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier);
method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice);
+ method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping();
+ method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
method public void removeUniqueIdAssociation(@NonNull String);
method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 128e3de..738e2de 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -368,6 +368,20 @@
},
}
+// Build Rust bindings for remote provisioning. Needed by keystore2.
+aidl_interface {
+ name: "android.security.rkp_aidl",
+ unstable: true,
+ srcs: [
+ "android/security/rkp/*.aidl",
+ ],
+ backend: {
+ rust: {
+ enabled: true,
+ },
+ },
+}
+
aidl_interface {
name: "android.debug_aidl",
unstable: true,
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index f62190a..c51e8ae 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -902,4 +902,9 @@
*/
public abstract void registerNetworkPolicyUidObserver(@NonNull IUidObserver observer,
int which, int cutpoint, @NonNull String callingPackage);
+
+ /**
+ * Return all client package names of a service.
+ */
+ public abstract ArraySet<String> getClientPackages(String servicePackageName);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 31cbe28..a4c9f8c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1020,6 +1020,12 @@
int flags;
}
+ // A list of receivers and an index into the receiver to be processed next.
+ static final class ReceiverList {
+ List<ReceiverInfo> receivers;
+ int index;
+ }
+
private class ApplicationThread extends IApplicationThread.Stub {
private static final String DB_CONNECTION_INFO_HEADER = " %8s %8s %14s %5s %5s %5s %s";
private static final String DB_CONNECTION_INFO_FORMAT = " %8s %8s %14s %5d %5d %5d %s";
@@ -1036,6 +1042,21 @@
sendMessage(H.RECEIVER, r);
}
+ public final void scheduleReceiverList(List<ReceiverInfo> info) throws RemoteException {
+ for (int i = 0; i < info.size(); i++) {
+ ReceiverInfo r = info.get(i);
+ if (r.registered) {
+ scheduleRegisteredReceiver(r.receiver, r.intent,
+ r.resultCode, r.data, r.extras, r.ordered, r.sticky,
+ r.sendingUser, r.processState);
+ } else {
+ scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
+ r.resultCode, r.data, r.extras, r.sync,
+ r.sendingUser, r.processState);
+ }
+ }
+ }
+
public final void scheduleCreateBackupAgent(ApplicationInfo app,
int backupMode, int userId, @BackupDestination int backupDestination) {
CreateBackupAgentData d = new CreateBackupAgentData();
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index 2f51b17..c6fa064 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -287,6 +287,7 @@
* <p>
* The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
*
+ * @param packageName The package name of the game to update
* @param gameModeConfig The configuration to use for game mode interventions
* @hide
*/
diff --git a/core/java/android/app/GameModeConfiguration.java b/core/java/android/app/GameModeConfiguration.java
index b081e82..d8be814 100644
--- a/core/java/android/app/GameModeConfiguration.java
+++ b/core/java/android/app/GameModeConfiguration.java
@@ -62,10 +62,16 @@
*/
@SystemApi
public static final class Builder {
- /** Constructs a new Builder for a game mode’s configuration */
+ /** Constructs a new Builder for a game mode’s configuration. */
public Builder() {
}
+ /** Constructs a new builder by copying from an existing game mode configuration. */
+ public Builder(@NonNull GameModeConfiguration configuration) {
+ mFpsOverride = configuration.mFpsOverride;
+ mScalingFactor = configuration.mScalingFactor;
+ }
+
/**
* Sets the scaling factor used for game resolution downscaling.
* <br>
@@ -156,16 +162,6 @@
return mFpsOverride;
}
- /**
- * Converts and returns the game mode config as a new builder.
- */
- @NonNull
- public GameModeConfiguration.Builder toBuilder() {
- return new GameModeConfiguration.Builder()
- .setFpsOverride(mFpsOverride)
- .setScalingFactor(mScalingFactor);
- }
-
@Override
public boolean equals(Object obj) {
if (obj == this) {
diff --git a/core/java/android/app/GameModeInfo.java b/core/java/android/app/GameModeInfo.java
index 31255c2..7dcb3909 100644
--- a/core/java/android/app/GameModeInfo.java
+++ b/core/java/android/app/GameModeInfo.java
@@ -87,12 +87,12 @@
}
/**
- * Sets the opted-in game modes.
+ * Sets the overridden game modes.
*/
@NonNull
- public GameModeInfo.Builder setOptedInGameModes(
- @NonNull @GameManager.GameMode int[] optedInGameModes) {
- mOptedInGameModes = optedInGameModes;
+ public GameModeInfo.Builder setOverriddenGameModes(
+ @NonNull @GameManager.GameMode int[] overriddenGameModes) {
+ mOverriddenGameModes = overriddenGameModes;
return this;
}
@@ -140,12 +140,12 @@
*/
@NonNull
public GameModeInfo build() {
- return new GameModeInfo(mActiveGameMode, mAvailableGameModes, mOptedInGameModes,
+ return new GameModeInfo(mActiveGameMode, mAvailableGameModes, mOverriddenGameModes,
mIsDownscalingAllowed, mIsFpsOverrideAllowed, mConfigMap);
}
private @GameManager.GameMode int[] mAvailableGameModes = new int[]{};
- private @GameManager.GameMode int[] mOptedInGameModes = new int[]{};
+ private @GameManager.GameMode int[] mOverriddenGameModes = new int[]{};
private @GameManager.GameMode int mActiveGameMode;
private boolean mIsDownscalingAllowed;
private boolean mIsFpsOverrideAllowed;
@@ -164,11 +164,11 @@
private GameModeInfo(@GameManager.GameMode int activeGameMode,
@NonNull @GameManager.GameMode int[] availableGameModes,
- @NonNull @GameManager.GameMode int[] optedInGameModes, boolean isDownscalingAllowed,
+ @NonNull @GameManager.GameMode int[] overriddenGameModes, boolean isDownscalingAllowed,
boolean isFpsOverrideAllowed, @NonNull Map<Integer, GameModeConfiguration> configMap) {
mActiveGameMode = activeGameMode;
mAvailableGameModes = Arrays.copyOf(availableGameModes, availableGameModes.length);
- mOptedInGameModes = Arrays.copyOf(optedInGameModes, optedInGameModes.length);
+ mOverriddenGameModes = Arrays.copyOf(overriddenGameModes, overriddenGameModes.length);
mIsDownscalingAllowed = isDownscalingAllowed;
mIsFpsOverrideAllowed = isFpsOverrideAllowed;
mConfigMap = configMap;
@@ -179,7 +179,7 @@
public GameModeInfo(Parcel in) {
mActiveGameMode = in.readInt();
mAvailableGameModes = in.createIntArray();
- mOptedInGameModes = in.createIntArray();
+ mOverriddenGameModes = in.createIntArray();
mIsDownscalingAllowed = in.readBoolean();
mIsFpsOverrideAllowed = in.readBoolean();
mConfigMap = new ArrayMap<>();
@@ -198,8 +198,8 @@
* Gets the collection of {@link GameManager.GameMode} that can be applied to the game.
* <p>
* Available games include all game modes that are either supported by the OEM in device
- * config, or opted in by the game developers in game mode config XML, plus the default enabled
- * modes for any game including {@link GameManager#GAME_MODE_STANDARD} and
+ * config, or overridden by the game developers in game mode config XML, plus the default
+ * enabled modes for any game including {@link GameManager#GAME_MODE_STANDARD} and
* {@link GameManager#GAME_MODE_CUSTOM}.
* <p>
* Also see {@link GameModeInfo}.
@@ -210,19 +210,19 @@
}
/**
- * Gets the collection of {@link GameManager.GameMode} that are opted in by the game.
+ * Gets the collection of {@link GameManager.GameMode} that are overridden by the game.
* <p>
* Also see {@link GameModeInfo}.
*/
@NonNull
- public @GameManager.GameMode int[] getOptedInGameModes() {
- return Arrays.copyOf(mOptedInGameModes, mOptedInGameModes.length);
+ public @GameManager.GameMode int[] getOverriddenGameModes() {
+ return Arrays.copyOf(mOverriddenGameModes, mOverriddenGameModes.length);
}
/**
* Gets the current game mode configuration of a game mode.
* <p>
- * The game mode can be null if it's opted in by the game itself, or not configured in device
+ * The game mode can be null if it's overridden by the game itself, or not configured in device
* config nor set by the user as custom game mode configuration.
*/
public @Nullable GameModeConfiguration getGameModeConfiguration(
@@ -250,7 +250,7 @@
private final @GameManager.GameMode int[] mAvailableGameModes;
- private final @GameManager.GameMode int[] mOptedInGameModes;
+ private final @GameManager.GameMode int[] mOverriddenGameModes;
private final @GameManager.GameMode int mActiveGameMode;
private final boolean mIsDownscalingAllowed;
private final boolean mIsFpsOverrideAllowed;
@@ -265,7 +265,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mActiveGameMode);
dest.writeIntArray(mAvailableGameModes);
- dest.writeIntArray(mOptedInGameModes);
+ dest.writeIntArray(mOverriddenGameModes);
dest.writeBoolean(mIsDownscalingAllowed);
dest.writeBoolean(mIsFpsOverrideAllowed);
dest.writeMap(mConfigMap);
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 595c7f7..3984fee 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -20,6 +20,7 @@
import android.app.IInstrumentationWatcher;
import android.app.IUiAutomationConnection;
import android.app.ProfilerInfo;
+import android.app.ReceiverInfo;
import android.app.ResultInfo;
import android.app.servertransaction.ClientTransaction;
import android.content.AutofillOptions;
@@ -66,6 +67,9 @@
in CompatibilityInfo compatInfo,
int resultCode, in String data, in Bundle extras, boolean sync,
int sendingUser, int processState);
+
+ void scheduleReceiverList(in List<ReceiverInfo> info);
+
@UnsupportedAppUsage
void scheduleCreateService(IBinder token, in ServiceInfo info,
in CompatibilityInfo compatInfo, int processState);
diff --git a/core/java/android/app/ReceiverInfo.aidl b/core/java/android/app/ReceiverInfo.aidl
new file mode 100644
index 0000000..d90eee7
--- /dev/null
+++ b/core/java/android/app/ReceiverInfo.aidl
@@ -0,0 +1,60 @@
+/**
+ * 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 android.app;
+
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.os.Bundle;
+
+/**
+ * Collect the information needed for manifest and registered receivers into a single structure
+ * that can be the element of a list. All fields are already parcelable.
+ * @hide
+ */
+parcelable ReceiverInfo {
+ /**
+ * Fields common to registered and manifest receivers.
+ */
+ Intent intent;
+ String data;
+ Bundle extras;
+ int sendingUser;
+ int processState;
+ int resultCode;
+
+ /**
+ * True if this instance represents a registered receiver and false if this instance
+ * represents a manifest receiver.
+ */
+ boolean registered;
+
+ /**
+ * Fields used only for registered receivers.
+ */
+ IIntentReceiver receiver;
+ boolean ordered;
+ boolean sticky;
+
+ /**
+ * Fields used only for manifest receivers.
+ */
+ ActivityInfo activityInfo;
+ CompatibilityInfo compatInfo;
+ boolean sync;
+}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 32217610..7e6386e 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -41,6 +41,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DisplayMetrics;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -51,7 +52,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.IndentingPrintWriter;
import java.io.IOException;
import java.io.PrintWriter;
@@ -173,6 +173,7 @@
/**
* The ApkAssets that are being referenced in the wild that we can reuse.
+ * Used as a lock for itself as well.
*/
private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
@@ -286,42 +287,43 @@
* try as hard as possible to release any open FDs.
*/
public void invalidatePath(String path) {
+ final List<ResourcesImpl> implsToFlush = new ArrayList<>();
synchronized (mLock) {
- int count = 0;
-
for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
final ResourcesKey key = mResourceImpls.keyAt(i);
if (key.isPathReferenced(path)) {
- ResourcesImpl impl = mResourceImpls.removeAt(i).get();
- if (impl != null) {
- impl.flushLayoutCache();
+ ResourcesImpl resImpl = mResourceImpls.removeAt(i).get();
+ if (resImpl != null) {
+ implsToFlush.add(resImpl);
}
- count++;
}
}
-
- Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path);
-
+ }
+ for (int i = 0; i < implsToFlush.size(); i++) {
+ implsToFlush.get(i).flushLayoutCache();
+ }
+ final List<ApkAssets> assetsToClose = new ArrayList<>();
+ synchronized (mCachedApkAssets) {
for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) {
final ApkKey key = mCachedApkAssets.keyAt(i);
if (key.path.equals(path)) {
final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.removeAt(i);
- if (apkAssetsRef == null) {
- continue;
- }
- final ApkAssets apkAssets = apkAssetsRef.get();
+ final ApkAssets apkAssets = apkAssetsRef != null ? apkAssetsRef.get() : null;
if (apkAssets != null) {
- apkAssets.close();
+ assetsToClose.add(apkAssets);
}
}
}
}
+ for (int i = 0; i < assetsToClose.size(); i++) {
+ assetsToClose.get(i).close();
+ }
+ Log.i(TAG,
+ "Invalidated " + implsToFlush.size() + " asset managers that referenced " + path);
}
public Configuration getConfiguration() {
- synchronized (mLock) {
- return mResConfiguration;
- }
+ return mResConfiguration;
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -406,14 +408,12 @@
* @param resources The {@link Resources} backing the display adjustments.
*/
public Display getAdjustedDisplay(final int displayId, Resources resources) {
- synchronized (mLock) {
- final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
- if (dm == null) {
- // may be null early in system startup
- return null;
- }
- return dm.getCompatibleDisplay(displayId, resources);
+ final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
+ if (dm == null) {
+ // may be null early in system startup
+ return null;
}
+ return dm.getCompatibleDisplay(displayId, resources);
}
/**
@@ -451,7 +451,7 @@
// Optimistically check if this ApkAssets exists somewhere else.
final WeakReference<ApkAssets> apkAssetsRef;
- synchronized (mLock) {
+ synchronized (mCachedApkAssets) {
apkAssetsRef = mCachedApkAssets.get(key);
}
if (apkAssetsRef != null) {
@@ -474,7 +474,7 @@
apkAssets = ApkAssets.loadFromPath(key.path, flags);
}
- synchronized (mLock) {
+ synchronized (mCachedApkAssets) {
mCachedApkAssets.put(key, new WeakReference<>(apkAssets));
}
@@ -586,28 +586,33 @@
* @hide
*/
public void dump(String prefix, PrintWriter printWriter) {
+ final int references;
+ final int resImpls;
synchronized (mLock) {
- IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
- for (int i = 0; i < prefix.length() / 2; i++) {
- pw.increaseIndent();
- }
-
- pw.println("ResourcesManager:");
- pw.increaseIndent();
- pw.print("total apks: ");
- pw.println(countLiveReferences(mCachedApkAssets.values()));
-
- pw.print("resources: ");
-
- int references = countLiveReferences(mResourceReferences);
+ int refs = countLiveReferences(mResourceReferences);
for (ActivityResources activityResources : mActivityResourceReferences.values()) {
- references += activityResources.countLiveReferences();
+ refs += activityResources.countLiveReferences();
}
- pw.println(references);
-
- pw.print("resource impls: ");
- pw.println(countLiveReferences(mResourceImpls.values()));
+ references = refs;
+ resImpls = countLiveReferences(mResourceImpls.values());
}
+ final int liveAssets;
+ synchronized (mCachedApkAssets) {
+ liveAssets = countLiveReferences(mCachedApkAssets.values());
+ }
+
+ final var pw = new IndentingPrintWriter(printWriter, " ");
+ for (int i = 0; i < prefix.length() / 2; i++) {
+ pw.increaseIndent();
+ }
+ pw.println("ResourcesManager:");
+ pw.increaseIndent();
+ pw.print("total apks: ");
+ pw.println(liveAssets);
+ pw.print("resources: ");
+ pw.println(references);
+ pw.print("resource impls: ");
+ pw.println(resImpls);
}
private Configuration generateConfig(@NonNull ResourcesKey key) {
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index a035375..f63f406 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -234,7 +234,10 @@
/**
* Session flag for {@link #registerSessionListener} indicating the listener
- * is interested in sessions on the keygaurd
+ * is interested in sessions on the keygaurd.
+ * Keyguard Session Boundaries:
+ * START_SESSION: device starts going to sleep OR the keyguard is newly shown
+ * END_SESSION: device starts going to sleep OR keyguard is no longer showing
* @hide
*/
public static final int SESSION_KEYGUARD = 1 << 0;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ecea1bb..0784405 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -14449,7 +14449,9 @@
* @throws SecurityException if {@code admin} is not a profile owner
*
* @see #getCrossProfileCalendarPackages(ComponentName)
+ * @deprecated Use {@link #setCrossProfilePackages(ComponentName, Set)}.
*/
+ @Deprecated
public void setCrossProfileCalendarPackages(@NonNull ComponentName admin,
@Nullable Set<String> packageNames) {
throwIfParentInstance("setCrossProfileCalendarPackages");
@@ -14475,7 +14477,9 @@
* @throws SecurityException if {@code admin} is not a profile owner
*
* @see #setCrossProfileCalendarPackages(ComponentName, Set)
+ * @deprecated Use {@link #setCrossProfilePackages(ComponentName, Set)}.
*/
+ @Deprecated
public @Nullable Set<String> getCrossProfileCalendarPackages(@NonNull ComponentName admin) {
throwIfParentInstance("getCrossProfileCalendarPackages");
if (mService != null) {
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index b6f0916..537b8d4 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -269,4 +269,9 @@
* {@link #supportsResetOp(int)} is true.
*/
public abstract void resetOp(int op, String packageName, @UserIdInt int userId);
+
+ /**
+ * Returns whether new "turn off work" behavior is enabled via feature flag.
+ */
+ public abstract boolean isKeepProfilesRunningEnabled();
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9f9fd3c..df5a1ed 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5937,6 +5937,14 @@
public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
/**
+ * Binder service for remote key provisioning.
+ *
+ * @see android.frameworks.rkp.IRemoteProvisioning
+ * @hide
+ */
+ public static final String REMOTE_PROVISIONING_SERVICE = "remote_provisioning";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a
* {@link android.hardware.lights.LightsManager} for controlling device lights.
*
diff --git a/core/java/android/content/om/TEST_MAPPING b/core/java/android/content/om/TEST_MAPPING
index 6185cf6..a9aaf1a 100644
--- a/core/java/android/content/om/TEST_MAPPING
+++ b/core/java/android/content/om/TEST_MAPPING
@@ -12,6 +12,9 @@
"name": "OverlayDeviceTests"
},
{
+ "name": "SelfTargetingOverlayDeviceTests"
+ },
+ {
"name": "OverlayHostTests"
},
{
@@ -35,6 +38,9 @@
},
{
"include-filter": "android.content.om.cts"
+ },
+ {
+ "include-filter": "android.content.res.loader.cts"
}
]
}
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 61fc6f6..4fa80d7 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -250,6 +250,16 @@
/**
* Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>module</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_MODULE = 0x400000;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
* to the <code>companion</code> value of
* {@link android.R.attr#protectionLevel}.
*
@@ -320,6 +330,7 @@
PROTECTION_FLAG_RECENTS,
PROTECTION_FLAG_ROLE,
PROTECTION_FLAG_KNOWN_SIGNER,
+ PROTECTION_FLAG_MODULE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProtectionFlags {}
@@ -593,6 +604,9 @@
if ((level & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0) {
protLevel.append("|knownSigner");
}
+ if ((level & PermissionInfo.PROTECTION_FLAG_MODULE) != 0) {
+ protLevel.append(("|module"));
+ }
return protLevel.toString();
}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index b26c0a2..eef0f42 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -123,6 +123,22 @@
String[] getKeyboardLayoutListForInputDevice(in InputDeviceIdentifier identifier, int userId,
in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
+ // Modifier key remapping APIs.
+ @EnforcePermission("REMAP_MODIFIER_KEYS")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.REMAP_MODIFIER_KEYS)")
+ void remapModifierKey(int fromKey, int toKey);
+
+ @EnforcePermission("REMAP_MODIFIER_KEYS")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.REMAP_MODIFIER_KEYS)")
+ void clearAllModifierKeyRemappings();
+
+ @EnforcePermission("REMAP_MODIFIER_KEYS")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.REMAP_MODIFIER_KEYS)")
+ Map getModifierKeyRemapping();
+
// Registers an input devices changed listener.
void registerInputDevicesChangedListener(IInputDevicesChangedListener listener);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index cea3fa1..3735417 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -78,6 +78,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -253,6 +254,31 @@
})
public @interface SwitchState {}
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "REMAPPABLE_MODIFIER_KEY_" }, value = {
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_CTRL_LEFT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_CTRL_RIGHT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_META_LEFT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_META_RIGHT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_ALT_LEFT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_ALT_RIGHT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_SHIFT_LEFT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_SHIFT_RIGHT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_CAPS_LOCK,
+ })
+ public @interface RemappableModifierKey {
+ int REMAPPABLE_MODIFIER_KEY_CTRL_LEFT = KeyEvent.KEYCODE_CTRL_LEFT;
+ int REMAPPABLE_MODIFIER_KEY_CTRL_RIGHT = KeyEvent.KEYCODE_CTRL_RIGHT;
+ int REMAPPABLE_MODIFIER_KEY_META_LEFT = KeyEvent.KEYCODE_META_LEFT;
+ int REMAPPABLE_MODIFIER_KEY_META_RIGHT = KeyEvent.KEYCODE_META_RIGHT;
+ int REMAPPABLE_MODIFIER_KEY_ALT_LEFT = KeyEvent.KEYCODE_ALT_LEFT;
+ int REMAPPABLE_MODIFIER_KEY_ALT_RIGHT = KeyEvent.KEYCODE_ALT_RIGHT;
+ int REMAPPABLE_MODIFIER_KEY_SHIFT_LEFT = KeyEvent.KEYCODE_SHIFT_LEFT;
+ int REMAPPABLE_MODIFIER_KEY_SHIFT_RIGHT = KeyEvent.KEYCODE_SHIFT_RIGHT;
+ int REMAPPABLE_MODIFIER_KEY_CAPS_LOCK = KeyEvent.KEYCODE_CAPS_LOCK;
+ }
+
/**
* Switch State: Unknown.
*
@@ -854,6 +880,60 @@
}
/**
+ * Remaps modifier keys. Remapping a modifier key to itself will clear any previous remappings
+ * for that key.
+ *
+ * @param fromKey The modifier key getting remapped.
+ * @param toKey The modifier key that it is remapped to.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+ public void remapModifierKey(@RemappableModifierKey int fromKey,
+ @RemappableModifierKey int toKey) {
+ try {
+ mIm.remapModifierKey(fromKey, toKey);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clears all existing modifier key remappings
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+ public void clearAllModifierKeyRemappings() {
+ try {
+ mIm.clearAllModifierKeyRemappings();
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Provides the current modifier key remapping
+ *
+ * @return a {fromKey, toKey} map that contains the existing modifier key remappings..
+ * {@link RemappableModifierKey}
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ @RequiresPermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+ public Map<Integer, Integer> getModifierKeyRemapping() {
+ try {
+ return mIm.getModifierKeyRemapping();
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets the TouchCalibration applied to the specified input device's coordinates.
*
* @param inputDeviceDescriptor The input device descriptor.
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 5c5af2a..e9a3254 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -71,6 +71,9 @@
# Tracing
per-file Trace.java = file:/TRACE_OWNERS
+# PatternMatcher
+per-file PatternMatcher* = file:/PACKAGE_MANAGER_OWNERS
+
# PermissionEnforcer
per-file PermissionEnforcer.java = tweek@google.com, brufino@google.com
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 03846db..eeac00b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9370,6 +9370,14 @@
public static final int DOCK_SETUP_PAUSED = 2;
/**
+ * Indicates that the user has been prompted to start dock setup.
+ * One of the possible states for {@link #DOCK_SETUP_STATE}.
+ *
+ * @hide
+ */
+ public static final int DOCK_SETUP_PROMPTED = 3;
+
+ /**
* Indicates that the user has completed dock setup.
* One of the possible states for {@link #DOCK_SETUP_STATE}.
*
@@ -9383,6 +9391,7 @@
DOCK_SETUP_NOT_STARTED,
DOCK_SETUP_STARTED,
DOCK_SETUP_PAUSED,
+ DOCK_SETUP_PROMPTED,
DOCK_SETUP_COMPLETED
})
public @interface DockSetupState {
diff --git a/core/java/android/security/rkp/IGetKeyCallback.aidl b/core/java/android/security/rkp/IGetKeyCallback.aidl
new file mode 100644
index 0000000..85ceae62
--- /dev/null
+++ b/core/java/android/security/rkp/IGetKeyCallback.aidl
@@ -0,0 +1,49 @@
+/*
+ * 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 android.security.rkp;
+
+import android.security.rkp.RemotelyProvisionedKey;
+
+/**
+ * Callback interface for receiving remotely provisioned keys from a
+ * {@link IRegistration}.
+ *
+ * @hide
+ */
+oneway interface IGetKeyCallback {
+ /**
+ * Called in response to {@link IRegistration.getKey}, indicating
+ * a remotely-provisioned key is available.
+ *
+ * @param key The key that was received from the remote provisioning service.
+ */
+ void onSuccess(in RemotelyProvisionedKey key);
+
+ /**
+ * Called when the key request has been successfully cancelled.
+ * @see IRegistration.cancelGetKey
+ */
+ void onCancel();
+
+ /**
+ * Called when an error has occurred while trying to get a remotely provisioned key.
+ *
+ * @param error A description of what failed, suitable for logging.
+ */
+ void onError(String error);
+}
+
diff --git a/core/java/android/security/rkp/IGetRegistrationCallback.aidl b/core/java/android/security/rkp/IGetRegistrationCallback.aidl
new file mode 100644
index 0000000..e375a6f
--- /dev/null
+++ b/core/java/android/security/rkp/IGetRegistrationCallback.aidl
@@ -0,0 +1,49 @@
+/*
+ * 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 android.security.rkp;
+
+import android.security.rkp.IRegistration;
+
+/**
+ * Callback interface for receiving a remote provisioning registration.
+ * {@link IRegistration}.
+ *
+ * @hide
+ */
+oneway interface IGetRegistrationCallback {
+ /**
+ * Called in response to {@link IRemoteProvisioning.getRegistration}.
+ *
+ * @param registration an IRegistration that is used to fetch remotely
+ * provisioned keys for the given IRemotelyProvisionedComponent.
+ */
+ void onSuccess(in IRegistration registration);
+
+ /**
+ * Called when the get registration request has been successfully cancelled.
+ * @see IRemoteProvisioning.cancelGetRegistration
+ */
+ void onCancel();
+
+ /**
+ * Called when an error has occurred while trying to get a registration.
+ *
+ * @param error A description of what failed, suitable for logging.
+ */
+ void onError(String error);
+}
+
diff --git a/core/java/android/security/rkp/IRegistration.aidl b/core/java/android/security/rkp/IRegistration.aidl
new file mode 100644
index 0000000..6522a45
--- /dev/null
+++ b/core/java/android/security/rkp/IRegistration.aidl
@@ -0,0 +1,85 @@
+/*
+ * 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 android.security.rkp;
+
+import android.security.rkp.IGetKeyCallback;
+
+/**
+ * This interface is associated with the registration of an
+ * IRemotelyProvisionedComponent. Each component has a unique set of keys
+ * and certificates that are provisioned to the device for attestation. An
+ * IRegistration binder is created by calling
+ * {@link IRemoteProvisioning#getRegistration()}.
+ *
+ * This interface is used to query for available keys and certificates for the
+ * registered component.
+ *
+ * @hide
+ */
+oneway interface IRegistration {
+ /**
+ * Fetch a remotely provisioned key for the given keyId. Keys are unique
+ * per caller/keyId/registration tuple. This ensures that no two
+ * applications are able to correlate keys to uniquely identify a
+ * device/user. Callers receive their key via {@code callback}.
+ *
+ * If a key is available, this call immediately invokes {@code callback}.
+ *
+ * If no keys are immediately available, then this function contacts the
+ * remote provisioning server to provision a key. After provisioning is
+ * completed, the key is passed to {@code callback}.
+ *
+ * @param keyId This is a client-chosen key identifier, used to
+ * differentiate between keys for varying client-specific use-cases. For
+ * example, keystore2 passes the UID of the applications that call it as
+ * the keyId value here, so that each of keystore2's clients gets a unique
+ * key.
+ * @param callback Receives the result of the call. A callback must only
+ * be used with one {@code getKey} call at a time.
+ */
+ void getKey(int keyId, IGetKeyCallback callback);
+
+ /**
+ * Cancel an active request for a remotely provisioned key, as initiated via
+ * {@link getKey}. Upon cancellation, {@code callback.onCancel} will be invoked.
+ */
+ void cancelGetKey(IGetKeyCallback callback);
+
+ /**
+ * Replace an obsolete key blob with an upgraded key blob.
+ * In certain cases, such as security patch level upgrade, keys become "old".
+ * In these cases, the component which supports operations with the remotely
+ * provisioned key blobs must support upgrading the blobs to make them "new"
+ * and usable on the updated system.
+ *
+ * For an example of a remotely provisioned component that has an upgrade
+ * mechanism, see the documentation for IKeyMintDevice.upgradeKey.
+ *
+ * Once a key has been upgraded, the IRegistration where the key is stored
+ * needs to be told about the new blob. After calling storeUpgradedKey,
+ * getKey will return the new key blob instead of the old one.
+ *
+ * Note that this function does NOT extend the lifetime of key blobs. The
+ * certificate for the key is unchanged, and the key will still expire at
+ * the same time it would have if storeUpgradedKey had never been called.
+ *
+ * @param oldKeyBlob The old key blob to be replaced by {@code newKeyBlob}.
+ *
+ * @param newKeyblob The new blob to replace {@code oldKeyBlob}.
+ */
+ void storeUpgradedKey(in byte[] oldKeyBlob, in byte[] newKeyBlob);
+}
diff --git a/core/java/android/security/rkp/IRemoteProvisioning.aidl b/core/java/android/security/rkp/IRemoteProvisioning.aidl
new file mode 100644
index 0000000..23d8159
--- /dev/null
+++ b/core/java/android/security/rkp/IRemoteProvisioning.aidl
@@ -0,0 +1,68 @@
+/*
+ * 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 android.security.rkp;
+
+import android.security.rkp.IRegistration;
+import android.security.rkp.IGetRegistrationCallback;
+
+/**
+ * {@link IRemoteProvisioning} is the interface provided to use the remote key
+ * provisioning functionality from the Remote Key Provisioning Daemon (RKPD).
+ * This would be the first service that RKPD clients would interact with. The
+ * intent is for the clients to get the {@link IRegistration} object from this
+ * interface and use it for actual remote provisioning work.
+ *
+ * @hide
+ */
+oneway interface IRemoteProvisioning {
+ /**
+ * Takes a remotely provisioned component service name and gets a
+ * registration bound to that service and the caller's UID.
+ *
+ * @param irpcName The name of the {@code IRemotelyProvisionedComponent}
+ * for which remotely provisioned keys should be managed.
+ * @param callback Receives the result of the call. A callback must only
+ * be used with one {@code getRegistration} call at a time.
+ *
+ * Notes:
+ * - This function will attempt to get the service named by irpcName. This
+ * implies that a lazy/dynamic aidl service will be instantiated, and this
+ * function blocks until the service is up. Upon return, any binder tokens
+ * are dropped, allowing the lazy/dynamic service to shutdown.
+ * - The created registration object is unique per caller. If two different
+ * UIDs call getRegistration with the same irpcName, they will receive
+ * different registrations. This prevents two different applications from
+ * being able to see the same keys.
+ * - This function is idempotent per calling UID. Additional calls to
+ * getRegistration with the same parameters, from the same caller, will have
+ * no side effects.
+ * - A callback may only be associated with one getRegistration call at a time.
+ * If the callback is used multiple times, this API will return an error.
+ *
+ * @see IRegistration#getKey()
+ * @see IRemotelyProvisionedComponent
+ *
+ */
+ void getRegistration(String irpcName, IGetRegistrationCallback callback);
+
+ /**
+ * Cancel any active {@link getRegistration} call associated with the given
+ * callback. If no getRegistration call is currently active, this function is
+ * a noop.
+ */
+ void cancelGetRegistration(IGetRegistrationCallback callback);
+}
diff --git a/core/java/android/security/rkp/OWNERS b/core/java/android/security/rkp/OWNERS
new file mode 100644
index 0000000..fd43089
--- /dev/null
+++ b/core/java/android/security/rkp/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 1084908
+
+jbires@google.com
+sethmo@google.com
+vikramgaur@google.com
diff --git a/core/java/android/security/rkp/RemotelyProvisionedKey.aidl b/core/java/android/security/rkp/RemotelyProvisionedKey.aidl
new file mode 100644
index 0000000..207f18f
--- /dev/null
+++ b/core/java/android/security/rkp/RemotelyProvisionedKey.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.security.rkp;
+
+/**
+ * A {@link RemotelyProvisionedKey} holds an attestation key and the
+ * corresponding remotely provisioned certificate chain.
+ *
+ * @hide
+ */
+@RustDerive(Eq=true, PartialEq=true)
+parcelable RemotelyProvisionedKey {
+ /**
+ * The remotely-provisioned key that may be used to sign attestations. The
+ * format of this key is opaque, and need only be understood by the
+ * IRemotelyProvisionedComponent that generated it.
+ *
+ * Any private key material contained within this blob must be encrypted.
+ *
+ * @see IRemotelyProvisionedComponent
+ */
+ byte[] keyBlob;
+
+ /**
+ * Sequence of DER-encoded X.509 certificates that make up the attestation
+ * key's certificate chain. This is the binary encoding for a chain that is
+ * supported by Java's CertificateFactory.generateCertificates API.
+ */
+ byte[] encodedCertChain;
+}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index bfb489e..727011c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -471,16 +471,6 @@
private boolean mAppVisibilityChanged;
int mOrigWindowType = -1;
- /** Whether the window had focus during the most recent traversal. */
- boolean mHadWindowFocus;
-
- /**
- * Whether the window lost focus during a previous traversal and has not
- * yet gained it back. Used to determine whether a WINDOW_STATE_CHANGE
- * accessibility events should be sent during traversal.
- */
- boolean mLostWindowFocus;
-
// Set to true if the owner of this window is in the stopped state,
// so the window should no longer be active.
@UnsupportedAppUsage
@@ -3630,20 +3620,8 @@
}
final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
- final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
- final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
- if (regainedFocus) {
- mLostWindowFocus = false;
- } else if (!hasWindowFocus && mHadWindowFocus) {
- mLostWindowFocus = true;
- }
-
- if (changedVisibility || regainedFocus) {
- // Toasts are presented as notifications - don't present them as windows as well
- boolean isToast = mWindowAttributes.type == TYPE_TOAST;
- if (!isToast) {
- host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- }
+ if (changedVisibility) {
+ maybeFireAccessibilityWindowStateChangedEvent();
}
mFirst = false;
@@ -3651,8 +3629,8 @@
mNewSurfaceNeeded = false;
mActivityRelaunched = false;
mViewVisibility = viewVisibility;
- mHadWindowFocus = hasWindowFocus;
+ final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
@@ -3895,6 +3873,8 @@
~WindowManager.LayoutParams
.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+ maybeFireAccessibilityWindowStateChangedEvent();
+
// Refocusing a window that has a focused view should fire a
// focus event for the view since the global focused view changed.
fireAccessibilityFocusEventIfHasFocusedNode();
@@ -3922,6 +3902,14 @@
ensureTouchModeLocally(inTouchMode);
}
+ private void maybeFireAccessibilityWindowStateChangedEvent() {
+ // Toasts are presented as notifications - don't present them as windows as well.
+ boolean isToast = mWindowAttributes != null && (mWindowAttributes.type == TYPE_TOAST);
+ if (!isToast && mView != null) {
+ mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+ }
+
private void fireAccessibilityFocusEventIfHasFocusedNode() {
if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
return;
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 43be031..e0b0110 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -226,9 +226,7 @@
Slog.d(TAG, "Accessibility shortcut activated");
final ContentResolver cr = mContext.getContentResolver();
final int userId = ActivityManager.getCurrentUser();
- final int dialogAlreadyShown = Settings.Secure.getIntForUser(
- cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStatus.NOT_SHOWN,
- userId);
+
// Play a notification vibration
Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
if ((vibrator != null) && vibrator.hasVibrator()) {
@@ -239,7 +237,7 @@
vibrator.vibrate(vibePattern, -1, VIBRATION_ATTRIBUTES);
}
- if (dialogAlreadyShown == DialogStatus.NOT_SHOWN) {
+ if (shouldShowDialog()) {
// The first time, we show a warning rather than toggle the service to give the user a
// chance to turn off this feature before stuff gets enabled.
mAlertDialog = createShortcutWarningDialog(userId);
@@ -269,6 +267,20 @@
}
}
+ /** Whether the warning dialog should be shown instead of performing the shortcut. */
+ private boolean shouldShowDialog() {
+ if (hasFeatureLeanback()) {
+ // Never show the dialog on TV, instead always perform the shortcut directly.
+ return false;
+ }
+ final ContentResolver cr = mContext.getContentResolver();
+ final int userId = ActivityManager.getCurrentUser();
+ final int dialogAlreadyShown = Settings.Secure.getIntForUser(cr,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStatus.NOT_SHOWN,
+ userId);
+ return dialogAlreadyShown == DialogStatus.NOT_SHOWN;
+ }
+
/**
* Show toast to alert the user that the accessibility shortcut turned on or off an
* accessibility service.
diff --git a/core/java/com/android/internal/content/om/TEST_MAPPING b/core/java/com/android/internal/content/om/TEST_MAPPING
index 4cb595b..98dadce7 100644
--- a/core/java/com/android/internal/content/om/TEST_MAPPING
+++ b/core/java/com/android/internal/content/om/TEST_MAPPING
@@ -7,6 +7,28 @@
"include-filter": "com.android.internal.content."
}
]
+ },
+ {
+ "name": "SelfTargetingOverlayDeviceTests"
+ }
+ ],
+ "presubmit-large": [
+ {
+ "name": "CtsContentTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.content.om.cts"
+ },
+ {
+ "include-filter": "android.content.res.loader.cts"
+ }
+ ]
}
]
-}
\ No newline at end of file
+}
diff --git a/core/jni/TEST_MAPPING b/core/jni/TEST_MAPPING
index 004c30e..2844856 100644
--- a/core/jni/TEST_MAPPING
+++ b/core/jni/TEST_MAPPING
@@ -11,6 +11,29 @@
}
],
"file_patterns": ["CharsetUtils|FastData"]
+ },
+ {
+ "name": "SelfTargetingOverlayDeviceTests",
+ "file_patterns": ["Overlay"]
+ }
+ ],
+ "presubmit-large": [
+ {
+ "name": "CtsContentTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.content.om.cts"
+ },
+ {
+ "include-filter": "android.content.res.loader.cts"
+ }
+ ]
}
]
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index fb451dd..dc24b0f9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4629,6 +4629,8 @@
<p>Protection level: appop
-->
<permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
+ android:label="@string/permlab_schedule_exact_alarm"
+ android:description="@string/permdesc_schedule_exact_alarm"
android:protectionLevel="normal|appop"/>
<!-- Allows apps to use exact alarms just like with {@link
@@ -4654,6 +4656,8 @@
lower standby bucket.
-->
<permission android:name="android.permission.USE_EXACT_ALARM"
+ android:label="@string/permlab_use_exact_alarm"
+ android:description="@string/permdesc_use_exact_alarm"
android:protectionLevel="normal"/>
<!-- Allows an application to query tablet mode state and monitor changes
@@ -6772,6 +6776,14 @@
@hide -->
<permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART"
android:protectionLevel="signature" />
+
+ <!-- Allows low-level access to re-mapping modifier keys.
+ <p>Not for use by third-party applications.
+ @hide
+ @TestApi -->
+ <permission android:name="android.permission.REMAP_MODIFIER_KEYS"
+ android:protectionLevel="signature" />
+
<uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" />
<!-- Allows financed device kiosk apps to perform actions on the Device Lock service
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c8a65a7..d4644c5 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -294,6 +294,9 @@
<!-- Additional flag from base permission type: this permission can be automatically
granted to the system app predictor -->
<flag name="appPredictor" value="0x200000" />
+ <!-- Additional flag from base permission type: this permission can also be granted if the
+ requesting application is included in the mainline module}. -->
+ <flag name="module" value="0x400000" />
<!-- Additional flag from base permission type: this permission can be automatically
granted to the system companion device manager service -->
<flag name="companion" value="0x800000" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 39012ae..18a5c72 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1133,6 +1133,16 @@
<string name="permdesc_useDataInBackground">This app can use data in the background. This may increase data usage.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_schedule_exact_alarm">Schedule precisely timed actions</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_schedule_exact_alarm">This app can schedule work to happen at a desired time in the future. This also means that the app can run when you\u2019re not actively using the device.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_use_exact_alarm">Schedule alarms or event reminders</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_use_exact_alarm">This app can schedule actions like alarms and reminders to notify you at a desired time in the future.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_persistentActivity">make app always run</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_persistentActivity" product="tablet">Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down the tablet.</string>
diff --git a/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java b/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java
index 7462bcf..b3e74d3 100644
--- a/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java
+++ b/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java
@@ -77,10 +77,10 @@
}
@Test
- public void testToBuilder() {
+ public void testBuilderConstructor() {
GameModeConfiguration config = new GameModeConfiguration
.Builder().setFpsOverride(40).setScalingFactor(0.5f).build();
- GameModeConfiguration newConfig = config.toBuilder().build();
+ GameModeConfiguration newConfig = new GameModeConfiguration.Builder(config).build();
assertEquals(config, newConfig);
}
diff --git a/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java b/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java
index ecd9b6b8..5fa6084 100644
--- a/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java
+++ b/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java
@@ -43,7 +43,7 @@
int[] availableGameModes =
new int[]{GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_PERFORMANCE,
GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_CUSTOM};
- int[] optedInGameModes = new int[]{GameManager.GAME_MODE_PERFORMANCE};
+ int[] overriddenGameModes = new int[]{GameManager.GAME_MODE_PERFORMANCE};
GameModeConfiguration batteryConfig = new GameModeConfiguration
.Builder().setFpsOverride(40).setScalingFactor(0.5f).build();
GameModeConfiguration performanceConfig = new GameModeConfiguration
@@ -51,7 +51,7 @@
GameModeInfo gameModeInfo = new GameModeInfo.Builder()
.setActiveGameMode(activeGameMode)
.setAvailableGameModes(availableGameModes)
- .setOptedInGameModes(optedInGameModes)
+ .setOverriddenGameModes(overriddenGameModes)
.setDownscalingAllowed(true)
.setFpsOverrideAllowed(false)
.setGameModeConfiguration(GameManager.GAME_MODE_BATTERY, batteryConfig)
@@ -59,7 +59,7 @@
.build();
assertArrayEquals(availableGameModes, gameModeInfo.getAvailableGameModes());
- assertArrayEquals(optedInGameModes, gameModeInfo.getOptedInGameModes());
+ assertArrayEquals(overriddenGameModes, gameModeInfo.getOverriddenGameModes());
assertEquals(activeGameMode, gameModeInfo.getActiveGameMode());
assertTrue(gameModeInfo.isDownscalingAllowed());
assertFalse(gameModeInfo.isFpsOverrideAllowed());
@@ -75,8 +75,8 @@
assertEquals(gameModeInfo.getActiveGameMode(), newGameModeInfo.getActiveGameMode());
assertArrayEquals(gameModeInfo.getAvailableGameModes(),
newGameModeInfo.getAvailableGameModes());
- assertArrayEquals(gameModeInfo.getOptedInGameModes(),
- newGameModeInfo.getOptedInGameModes());
+ assertArrayEquals(gameModeInfo.getOverriddenGameModes(),
+ newGameModeInfo.getOverriddenGameModes());
assertTrue(newGameModeInfo.isDownscalingAllowed());
assertFalse(newGameModeInfo.isFpsOverrideAllowed());
assertEquals(performanceConfig,
diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp
index 109aac3..7c732d7 100644
--- a/libs/hwui/jni/Mesh.cpp
+++ b/libs/hwui/jni/Mesh.cpp
@@ -37,6 +37,16 @@
return indexBuffer;
}
+// TODO(b/260252882): undefine SK_LEGACY_MESH_MAKE and remove this.
+template <typename T>
+SkMesh get_mesh_from_result(T&& result) {
+#ifdef SK_LEGACY_MESH_MAKE
+ return result;
+#else
+ return result.mesh;
+#endif
+}
+
static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
jboolean isDirect, jint vertexCount, jint vertexOffset, jint left, jint top,
jint right, jint bottom) {
@@ -44,8 +54,8 @@
sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect);
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto mesh = SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
- vertexOffset, nullptr, skRect);
+ auto mesh = get_mesh_from_result(SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer,
+ vertexCount, vertexOffset, nullptr, skRect));
auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
return reinterpret_cast<jlong>(meshPtr.release());
}
@@ -60,9 +70,9 @@
sk_sp<SkMesh::IndexBuffer> skIndexBuffer =
genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect);
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto mesh = SkMesh::MakeIndexed(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
- vertexOffset, skIndexBuffer, indexCount, indexOffset, nullptr,
- skRect);
+ auto mesh = get_mesh_from_result(SkMesh::MakeIndexed(
+ skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
+ skIndexBuffer, indexCount, indexOffset, nullptr, skRect));
auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
return reinterpret_cast<jlong>(meshPtr.release());
}
@@ -71,14 +81,15 @@
auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
auto mesh = wrapper->mesh;
if (indexed) {
- wrapper->mesh = SkMesh::MakeIndexed(
+ wrapper->mesh = get_mesh_from_result(SkMesh::MakeIndexed(
sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
mesh.vertexCount(), mesh.vertexOffset(), sk_ref_sp(mesh.indexBuffer()),
- mesh.indexCount(), mesh.indexOffset(), wrapper->builder.fUniforms, mesh.bounds());
+ mesh.indexCount(), mesh.indexOffset(), wrapper->builder.fUniforms, mesh.bounds()));
} else {
- wrapper->mesh = SkMesh::Make(
- sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
- mesh.vertexCount(), mesh.vertexOffset(), wrapper->builder.fUniforms, mesh.bounds());
+ wrapper->mesh = get_mesh_from_result(
+ SkMesh::Make(sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
+ mesh.vertexCount(), mesh.vertexOffset(), wrapper->builder.fUniforms,
+ mesh.bounds()));
}
}
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
index 506128e..d46b4d2 100644
--- a/location/java/android/location/altitude/AltitudeConverter.java
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -151,20 +151,17 @@
* altitude accuracy if the {@code location} has a finite and non-negative vertical accuracy;
* otherwise, does not add a corresponding accuracy.
*
- * <p>Must be called off the main thread as data may be loaded from raw assets. Throws an
- * {@link IOException} if an I/O error occurs when loading data.
+ * <p>Must be called off the main thread as data may be loaded from raw assets.
*
- * <p>Throws an {@link IllegalArgumentException} if the {@code location} has an invalid
- * latitude, longitude, or altitude above WGS84. Specifically:
- *
- * <ul>
- * <li>The latitude must be between -90 and 90, both inclusive.
- * <li>The longitude must be between -180 and 180, both inclusive.
- * <li>The altitude above WGS84 must be finite.
- * </ul>
+ * @throws IOException if an I/O error occurs when loading data from raw assets.
+ * @throws IllegalArgumentException if the {@code location} has an invalid latitude, longitude,
+ * or altitude above WGS84. Specifically, the latitude must be
+ * between -90 and 90 (both inclusive), the longitude must be
+ * between -180 and 180 (both inclusive), and the altitude
+ * above WGS84 must be finite.
*/
@WorkerThread
- public void addMslAltitude(@NonNull Context context, @NonNull Location location)
+ public void addMslAltitudeToLocation(@NonNull Context context, @NonNull Location location)
throws IOException {
validate(location);
MapParamsProto params = GeoidHeightMap.getParams(context);
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 7786f61..aea6bcb 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -76,13 +76,7 @@
private static MediaRouter2Manager sInstance;
private final MediaSessionManager mMediaSessionManager;
-
- final String mPackageName;
-
- private final Context mContext;
-
private final Client mClient;
-
private final IMediaRouterService mMediaRouterService;
private final AtomicInteger mScanRequestCount = new AtomicInteger(/* initialValue= */ 0);
final Handler mHandler;
@@ -120,16 +114,14 @@
}
private MediaRouter2Manager(Context context) {
- mContext = context.getApplicationContext();
mMediaRouterService = IMediaRouterService.Stub.asInterface(
ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
mMediaSessionManager = (MediaSessionManager) context
.getSystemService(Context.MEDIA_SESSION_SERVICE);
- mPackageName = mContext.getPackageName();
mHandler = new Handler(context.getMainLooper());
mClient = new Client();
try {
- mMediaRouterService.registerManager(mClient, mPackageName);
+ mMediaRouterService.registerManager(mClient, context.getPackageName());
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/media/tests/AudioPolicyTest/AndroidManifest.xml b/media/tests/AudioPolicyTest/AndroidManifest.xml
index f696735..5c911b1 100644
--- a/media/tests/AudioPolicyTest/AndroidManifest.xml
+++ b/media/tests/AudioPolicyTest/AndroidManifest.xml
@@ -24,13 +24,22 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:label="@string/app_name" android:name="AudioPolicyTestActivity"
+ <activity android:label="@string/app_name" android:name="AudioVolumeTestActivity"
android:screenOrientation="landscape" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:label="@string/app_name" android:name="AudioPolicyDeathTestActivity"
+ android:screenOrientation="landscape"
+ android:process=":AudioPolicyDeathTestActivityProcess"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTest.java
new file mode 100644
index 0000000..841804b
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.audiopolicytest;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AudioPolicyDeathTest {
+ private static final String TAG = "AudioPolicyDeathTest";
+
+ private static final int SAMPLE_RATE = 48000;
+ private static final int PLAYBACK_TIME_MS = 2000;
+
+ private static final IntentFilter AUDIO_NOISY_INTENT_FILTER =
+ new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+
+ private class MyBroadcastReceiver extends BroadcastReceiver {
+ private boolean mReceived = false;
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
+ synchronized (this) {
+ mReceived = true;
+ notify();
+ }
+ }
+ }
+
+ public synchronized boolean received() {
+ return mReceived;
+ }
+ }
+ private final MyBroadcastReceiver mReceiver = new MyBroadcastReceiver();
+
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = getApplicationContext();
+ assertEquals(PackageManager.PERMISSION_GRANTED,
+ mContext.checkSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING));
+ }
+
+ //-----------------------------------------------------------------
+ // Tests that an AUDIO_BECOMING_NOISY intent is broadcast when an app having registered
+ // a dynamic audio policy that intercepts an active media playback dies
+ //-----------------------------------------------------------------
+ @Test
+ public void testPolicyClientDeathSendBecomingNoisyIntent() {
+ mContext.registerReceiver(mReceiver, AUDIO_NOISY_INTENT_FILTER);
+
+ // Launch process registering a dynamic auido policy and dying after PLAYBACK_TIME_MS/2 ms
+ Intent intent = new Intent(mContext, AudioPolicyDeathTestActivity.class);
+ intent.putExtra("captureDurationMs", PLAYBACK_TIME_MS / 2);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivity(intent);
+
+ AudioTrack track = createAudioTrack();
+ track.play();
+ synchronized (mReceiver) {
+ long startTimeMs = System.currentTimeMillis();
+ long elapsedTimeMs = 0;
+ while (elapsedTimeMs < PLAYBACK_TIME_MS && !mReceiver.received()) {
+ try {
+ mReceiver.wait(PLAYBACK_TIME_MS - elapsedTimeMs);
+ } catch (InterruptedException e) {
+ Log.w(TAG, "wait interrupted");
+ }
+ elapsedTimeMs = System.currentTimeMillis() - startTimeMs;
+ }
+ }
+
+ track.stop();
+ track.release();
+
+ assertTrue(mReceiver.received());
+ }
+
+ private AudioTrack createAudioTrack() {
+ AudioFormat format = new AudioFormat.Builder()
+ .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setSampleRate(SAMPLE_RATE)
+ .build();
+
+ short[] data = new short[PLAYBACK_TIME_MS * SAMPLE_RATE * format.getChannelCount() / 1000];
+ AudioAttributes attributes =
+ new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
+
+ AudioTrack track = new AudioTrack(attributes, format, data.length,
+ AudioTrack.MODE_STATIC, AudioManager.AUDIO_SESSION_ID_GENERATE);
+ track.write(data, 0, data.length);
+
+ return track;
+ }
+}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTestActivity.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTestActivity.java
new file mode 100644
index 0000000..957e719
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTestActivity.java
@@ -0,0 +1,132 @@
+/*
+ * 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.audiopolicytest;
+
+import android.app.Activity;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.AudioPolicy;
+import android.os.Bundle;
+import android.os.Looper;
+import android.util.Log;
+
+// This activity will register a dynamic audio policy to intercept media playback and launch
+// a thread that will capture audio from the policy mix and crash after the time indicated by
+// intent extra "captureDurationMs" has elapsed
+public class AudioPolicyDeathTestActivity extends Activity {
+ private static final String TAG = "AudioPolicyDeathTestActivity";
+
+ private static final int SAMPLE_RATE = 48000;
+ private static final int RECORD_TIME_MS = 1000;
+
+ private AudioManager mAudioManager = null;
+ private AudioPolicy mAudioPolicy = null;
+
+ public AudioPolicyDeathTestActivity() {
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mAudioManager = getApplicationContext().getSystemService(AudioManager.class);
+
+ AudioAttributes attributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA).build();
+ AudioMixingRule.Builder audioMixingRuleBuilder = new AudioMixingRule.Builder()
+ .addRule(attributes, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
+
+ AudioFormat audioFormat = new AudioFormat.Builder()
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+ .setSampleRate(SAMPLE_RATE)
+ .build();
+
+ AudioMix audioMix = new AudioMix.Builder(audioMixingRuleBuilder.build())
+ .setFormat(audioFormat)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK)
+ .build();
+
+ AudioPolicy.Builder audioPolicyBuilder = new AudioPolicy.Builder(getApplicationContext());
+ audioPolicyBuilder.addMix(audioMix)
+ .setLooper(Looper.getMainLooper());
+ mAudioPolicy = audioPolicyBuilder.build();
+
+ int result = mAudioManager.registerAudioPolicy(mAudioPolicy);
+ if (result != AudioManager.SUCCESS) {
+ Log.w(TAG, "registerAudioPolicy failed, status: " + result);
+ return;
+ }
+ AudioRecord audioRecord = mAudioPolicy.createAudioRecordSink(audioMix);
+ if (audioRecord == null) {
+ Log.w(TAG, "AudioRecord creation failed");
+ return;
+ }
+
+ int captureDurationMs = getIntent().getIntExtra("captureDurationMs", RECORD_TIME_MS);
+ AudioCapturingThread thread = new AudioCapturingThread(audioRecord, captureDurationMs);
+ thread.start();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mAudioManager != null && mAudioPolicy != null) {
+ mAudioManager.unregisterAudioPolicy(mAudioPolicy);
+ }
+ }
+
+ // A thread that captures audio from the supplied AudioRecord and crashes after the supplied
+ // duration has elapsed
+ private static class AudioCapturingThread extends Thread {
+ private final AudioRecord mAudioRecord;
+ private final int mDurationMs;
+
+ AudioCapturingThread(AudioRecord record, int durationMs) {
+ super();
+ mAudioRecord = record;
+ mDurationMs = durationMs;
+ }
+
+ @Override
+ @SuppressWarnings("ConstantOverflow")
+ public void run() {
+ int samplesLeft = mDurationMs * SAMPLE_RATE * mAudioRecord.getChannelCount() / 1000;
+ short[] readBuffer = new short[samplesLeft / 10];
+ mAudioRecord.startRecording();
+ long startTimeMs = System.currentTimeMillis();
+ long elapsedTimeMs = 0;
+ do {
+ int read = readBuffer.length < samplesLeft ? readBuffer.length : samplesLeft;
+ read = mAudioRecord.read(readBuffer, 0, read);
+ elapsedTimeMs = System.currentTimeMillis() - startTimeMs;
+ if (read < 0) {
+ Log.w(TAG, "read error: " + read);
+ break;
+ }
+ samplesLeft -= read;
+ } while (elapsedTimeMs < mDurationMs && samplesLeft > 0);
+
+ // force process to crash
+ int i = 1 / 0;
+ }
+ }
+}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTestActivity.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeTestActivity.java
similarity index 91%
rename from media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTestActivity.java
rename to media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeTestActivity.java
index e31c01a..8f61815 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTestActivity.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeTestActivity.java
@@ -19,9 +19,9 @@
import android.app.Activity;
import android.os.Bundle;
-public class AudioPolicyTestActivity extends Activity {
+public class AudioVolumeTestActivity extends Activity {
- public AudioPolicyTestActivity() {
+ public AudioVolumeTestActivity() {
}
/** Called when the activity is first created. */
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java
index fc3b198..c6ec7a6 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java
@@ -100,7 +100,7 @@
@Before
public void setUp() throws Exception {
- ActivityScenario.launch(AudioPolicyTestActivity.class);
+ ActivityScenario.launch(AudioVolumeTestActivity.class);
mContext = getApplicationContext();
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 37d6b42..d32d659 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -38,7 +38,6 @@
<provider
android:name="com.android.settingslib.spa.search.SpaSearchProvider"
android:authorities="com.android.spa.gallery.search.provider"
- android:enabled="true"
android:exported="false">
</provider>
@@ -67,7 +66,6 @@
<provider
android:name="com.android.settingslib.spa.debug.DebugProvider"
android:authorities="com.android.spa.gallery.debug.provider"
- android:enabled="true"
android:exported="false">
</provider>
diff --git a/packages/SettingsLib/Spa/screenshot/Android.bp b/packages/SettingsLib/Spa/screenshot/Android.bp
new file mode 100644
index 0000000..4e6b646
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/Android.bp
@@ -0,0 +1,41 @@
+//
+// 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 {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "SpaScreenshotTests",
+ test_suites: ["device-tests"],
+
+ asset_dirs: ["assets"],
+
+ srcs: ["src/**/*.kt"],
+
+ certificate: "platform",
+
+ static_libs: [
+ "SpaLib",
+ "SpaLibTestUtils",
+ "androidx.compose.runtime_runtime",
+ "androidx.test.ext.junit",
+ "androidx.test.runner",
+ "mockito-target-minus-junit4",
+ "platform-screenshot-diff-core",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/Spa/screenshot/AndroidManifest.xml b/packages/SettingsLib/Spa/screenshot/AndroidManifest.xml
new file mode 100644
index 0000000..d59a154
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.spa.screenshot">
+
+ <uses-sdk android:minSdkVersion="21"/>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name=".DebugActivity" android:exported="true" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Screenshot tests for SpaLib"
+ android:targetPackage="com.android.settingslib.spa.screenshot">
+ </instrumentation>
+</manifest>
diff --git a/packages/SettingsLib/Spa/screenshot/AndroidTest.xml b/packages/SettingsLib/Spa/screenshot/AndroidTest.xml
new file mode 100644
index 0000000..e0c08e8
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/AndroidTest.xml
@@ -0,0 +1,36 @@
+<!--
+ 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.
+ -->
+
+<configuration description="Runs screendiff tests.">
+ <option name="test-suite-tag" value="apct-instrumentation" />
+ <option name="test-suite-tag" value="apct" />
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="optimized-property-setting" value="true" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="SpaScreenshotTests.apk" />
+ </target_preparer>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys"
+ value="/data/user/0/com.android.settingslib.spa.screenshot/files/settings_screenshots" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.settingslib.spa.screenshot" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.png
new file mode 100644
index 0000000..6086e2d
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.png
new file mode 100644
index 0000000..aa6c5b7
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png
new file mode 100644
index 0000000..cac990c
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png
new file mode 100644
index 0000000..f6298c0
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.png
new file mode 100644
index 0000000..9391eeb
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png
new file mode 100644
index 0000000..94e2843
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.png
new file mode 100644
index 0000000..b1d03c3
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.png
new file mode 100644
index 0000000..95f19da
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt
new file mode 100644
index 0000000..814d4a1
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.settingslib.spa.screenshot
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.os.Build
+import android.view.View
+import platform.test.screenshot.matchers.MSSIMMatcher
+import platform.test.screenshot.matchers.PixelPerfectMatcher
+
+/** Draw this [View] into a [Bitmap]. */
+// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their
+// tests.
+fun View.drawIntoBitmap(): Bitmap {
+ val bitmap =
+ Bitmap.createBitmap(
+ measuredWidth,
+ measuredHeight,
+ Bitmap.Config.ARGB_8888,
+ )
+ val canvas = Canvas(bitmap)
+ draw(canvas)
+ return bitmap
+}
+
+/**
+ * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
+ * screenshot *unit* tests.
+ */
+val UnitTestBitmapMatcher =
+ if (Build.CPU_ABI == "x86_64") {
+ // Different CPU architectures can sometimes end up rendering differently, so we can't do
+ // pixel-perfect matching on different architectures using the same golden. Given that our
+ // presubmits are run on cf_x86_64_phone, our goldens should be perfectly matched on the
+ // x86_64 architecture and use the Structural Similarity Index on others.
+ // TODO(b/237511747): Run our screenshot presubmit tests on arm64 instead so that we can
+ // do pixel perfect matching both at presubmit time and at development time with actual
+ // devices.
+ PixelPerfectMatcher()
+ } else {
+ MSSIMMatcher()
+ }
+
+/**
+ * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
+ * screenshot *unit* tests.
+ *
+ * We use the Structural Similarity Index for integration tests because they usually contain
+ * additional information and noise that shouldn't break the test.
+ */
+val IntegrationTestBitmapMatcher = MSSIMMatcher()
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt
new file mode 100644
index 0000000..d7f42b3
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.settingslib.spa.screenshot
+
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.DisplaySpec
+
+/**
+ * The emulations specs for all 8 permutations of:
+ * - phone or tablet.
+ * - dark of light mode.
+ * - portrait or landscape.
+ */
+val DeviceEmulationSpec.Companion.PhoneAndTabletFull
+ get() = PhoneAndTabletFullSpec
+
+private val PhoneAndTabletFullSpec =
+ DeviceEmulationSpec.forDisplays(Displays.Phone, Displays.Tablet)
+
+/**
+ * The emulations specs of:
+ * - phone + light mode + portrait.
+ * - phone + light mode + landscape.
+ * - tablet + dark mode + portrait.
+ *
+ * This allows to test the most important permutations of a screen/layout with only 3
+ * configurations.
+ */
+val DeviceEmulationSpec.Companion.PhoneAndTabletMinimal
+ get() = PhoneAndTabletMinimalSpec
+
+private val PhoneAndTabletMinimalSpec =
+ DeviceEmulationSpec.forDisplays(Displays.Phone, isDarkTheme = false) +
+ DeviceEmulationSpec.forDisplays(Displays.Tablet, isDarkTheme = true, isLandscape = false)
+
+object Displays {
+ val Phone =
+ DisplaySpec(
+ "phone",
+ width = 1440,
+ height = 3120,
+ densityDpi = 560,
+ )
+
+ val Tablet =
+ DisplaySpec(
+ "tablet",
+ width = 2560,
+ height = 1600,
+ densityDpi = 320,
+ )
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
new file mode 100644
index 0000000..25bc098
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.settingslib.spa.screenshot
+
+import androidx.test.platform.app.InstrumentationRegistry
+import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.PathConfig
+
+/** A [GoldenImagePathManager] that should be used for all Settings screenshot tests. */
+class SettingsGoldenImagePathManager(
+ pathConfig: PathConfig,
+ assetsPathRelativeToBuildRoot: String
+) :
+ GoldenImagePathManager(
+ appContext = InstrumentationRegistry.getInstrumentation().context,
+ assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
+ deviceLocalPath =
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/settings_screenshots",
+ pathConfig = pathConfig,
+ ) {
+ override fun toString(): String {
+ // This string is appended to all actual/expected screenshots on the device, so make sure
+ // it is a static value.
+ return "SettingsGoldenImagePathManager"
+ }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
new file mode 100644
index 0000000..7a7cf31
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.settingslib.spa.screenshot
+
+import androidx.activity.ComponentActivity
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.ViewRootForTest
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onRoot
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import platform.test.screenshot.DeviceEmulationRule
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.MaterialYouColorsRule
+import platform.test.screenshot.ScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+/** A rule for Settings screenshot diff tests. */
+class SettingsScreenshotTestRule(
+ emulationSpec: DeviceEmulationSpec,
+ assetsPathRelativeToBuildRoot: String
+) : TestRule {
+ private val colorsRule = MaterialYouColorsRule()
+ private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
+ private val screenshotRule =
+ ScreenshotTestRule(
+ SettingsGoldenImagePathManager(
+ getEmulatedDevicePathConfig(emulationSpec),
+ assetsPathRelativeToBuildRoot
+ )
+ )
+ private val composeRule = createAndroidComposeRule<ComponentActivity>()
+ private val delegateRule =
+ RuleChain.outerRule(colorsRule)
+ .around(deviceEmulationRule)
+ .around(screenshotRule)
+ .around(composeRule)
+ private val matcher = UnitTestBitmapMatcher
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return delegateRule.apply(base, description)
+ }
+
+ /**
+ * Compare [content] with the golden image identified by [goldenIdentifier] in the context of
+ * [testSpec].
+ */
+ fun screenshotTest(
+ goldenIdentifier: String,
+ content: @Composable () -> Unit,
+ ) {
+ // Make sure that the activity draws full screen and fits the whole display.
+ val activity = composeRule.activity
+ activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) }
+
+ // Set the content using the AndroidComposeRule to make sure that the Activity is set up
+ // correctly.
+ composeRule.setContent {
+ SettingsTheme {
+ Surface(
+ color = MaterialTheme.colorScheme.background,
+ ) {
+ content()
+ }
+ }
+ }
+ composeRule.waitForIdle()
+
+ val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
+ screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
+ }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt
new file mode 100644
index 0000000..9631826
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.settingslib.spa.screenshot
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Autorenew
+import androidx.compose.material.icons.outlined.DisabledByDefault
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.SettingsIcon
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for ExampleFeature. */
+@RunWith(Parameterized::class)
+class PreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletFull
+ private const val TITLE = "Title"
+ private const val SUMMARY = "Summary"
+ private const val LONG_SUMMARY =
+ "Long long long long long long long long long long long long long long long summary"
+ }
+
+ @get:Rule
+ val screenshotRule =
+ SettingsScreenshotTestRule(
+ emulationSpec,
+ "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+ )
+
+ @Test
+ fun testPreference() {
+ screenshotRule.screenshotTest("preference") {
+ RegularScaffold(title = "Preference") {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ })
+
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val summary = SUMMARY.toState()
+ })
+
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val summary = LONG_SUMMARY.toState()
+ })
+
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val summary = SUMMARY.toState()
+ override val enabled = false.toState()
+ override val icon = @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.DisabledByDefault)
+ }
+ })
+
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val summary = SUMMARY.toState()
+ override val icon = @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.Autorenew)
+ }
+ })
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
index 5873635..6ecf9c3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
@@ -40,7 +40,7 @@
}
fun SettingsPage.debugArguments(): String {
- val normArguments = parameter.normalize(arguments)
+ val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
if (normArguments == null || normArguments.isEmpty) return "[No arguments]"
return normArguments.toString().removeRange(0, 6)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
index 59ec985..838c0cf 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
@@ -27,11 +27,7 @@
import android.database.MatrixCursor
import android.net.Uri
import android.util.Log
-import com.android.settingslib.spa.framework.common.ColumnEnum
-import com.android.settingslib.spa.framework.common.QueryEnum
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.addUri
-import com.android.settingslib.spa.framework.common.getColumns
import com.android.settingslib.spa.framework.util.KEY_DESTINATION
import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
import com.android.settingslib.spa.framework.util.KEY_SESSION_SOURCE_NAME
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
similarity index 60%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
index 61b46be..bb9a134 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.framework.common
+package com.android.settingslib.spa.debug
import android.content.UriMatcher
-import androidx.annotation.VisibleForTesting
/**
* Enum to define all column names in provider.
@@ -39,12 +38,6 @@
ENTRY_INTENT_URI("entryIntent"),
ENTRY_HIERARCHY_PATH("entryPath"),
ENTRY_START_ADB("entryStartAdb"),
-
- // Columns related to search
- SEARCH_TITLE("searchTitle"),
- SEARCH_KEYWORD("searchKw"),
- SEARCH_PATH("searchPath"),
- SEARCH_STATUS_DISABLED("searchDisabled"),
}
/**
@@ -89,54 +82,16 @@
ColumnEnum.ENTRY_HIERARCHY_PATH,
)
),
-
- SEARCH_STATIC_DATA_QUERY(
- "search_static", 301,
- listOf(
- ColumnEnum.ENTRY_ID,
- ColumnEnum.ENTRY_INTENT_URI,
- ColumnEnum.SEARCH_TITLE,
- ColumnEnum.SEARCH_KEYWORD,
- ColumnEnum.SEARCH_PATH,
- )
- ),
- SEARCH_DYNAMIC_DATA_QUERY(
- "search_dynamic", 302,
- listOf(
- ColumnEnum.ENTRY_ID,
- ColumnEnum.ENTRY_INTENT_URI,
- ColumnEnum.SEARCH_TITLE,
- ColumnEnum.SEARCH_KEYWORD,
- ColumnEnum.SEARCH_PATH,
- )
- ),
- SEARCH_IMMUTABLE_STATUS_DATA_QUERY(
- "search_immutable_status", 303,
- listOf(
- ColumnEnum.ENTRY_ID,
- ColumnEnum.SEARCH_STATUS_DISABLED,
- )
- ),
- SEARCH_MUTABLE_STATUS_DATA_QUERY(
- "search_mutable_status", 304,
- listOf(
- ColumnEnum.ENTRY_ID,
- ColumnEnum.SEARCH_STATUS_DISABLED,
- )
- ),
}
-@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-fun QueryEnum.getColumns(): Array<String> {
+internal fun QueryEnum.getColumns(): Array<String> {
return columnNames.map { it.id }.toTypedArray()
}
-@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-fun QueryEnum.getIndex(name: ColumnEnum): Int {
+internal fun QueryEnum.getIndex(name: ColumnEnum): Int {
return columnNames.indexOf(name)
}
-@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
+internal fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
uriMatcher.addURI(authority, queryPath, queryMatchCode)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 702c075..4985b44 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -35,6 +35,8 @@
get() = null
val isHighlighted: Boolean
get() = false
+ val arguments: Bundle?
+ get() = null
}
val LocalEntryDataProvider =
@@ -121,11 +123,11 @@
}
private fun fullArgument(runtimeArguments: Bundle? = null): Bundle {
- val arguments = Bundle()
- if (owner.arguments != null) arguments.putAll(owner.arguments)
- // Put runtime args later, which can override page args.
- if (runtimeArguments != null) arguments.putAll(runtimeArguments)
- return arguments
+ return Bundle().apply {
+ if (owner.arguments != null) putAll(owner.arguments)
+ // Put runtime args later, which can override page args.
+ if (runtimeArguments != null) putAll(runtimeArguments)
+ }
}
fun getStatusData(runtimeArguments: Bundle? = null): EntryStatusData? {
@@ -142,19 +144,21 @@
@Composable
fun UiLayout(runtimeArguments: Bundle? = null) {
- CompositionLocalProvider(provideLocalEntryData()) {
- uiLayoutImpl(fullArgument(runtimeArguments))
+ val arguments = remember { fullArgument(runtimeArguments) }
+ CompositionLocalProvider(provideLocalEntryData(arguments)) {
+ uiLayoutImpl(arguments)
}
}
@Composable
- fun provideLocalEntryData(): ProvidedValue<EntryData> {
+ fun provideLocalEntryData(arguments: Bundle): ProvidedValue<EntryData> {
val controller = LocalNavController.current
return LocalEntryDataProvider provides remember {
object : EntryData {
override val pageId = containerPage().id
override val entryId = id
override val isHighlighted = controller.highlightEntryId == id
+ override val arguments = arguments
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 7a39b73..2bfa2a4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -69,7 +69,7 @@
parameter: List<NamedNavArgument> = emptyList(),
arguments: Bundle? = null
): String {
- val normArguments = parameter.normalize(arguments)
+ val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
return "$name:${normArguments?.toString()}".toHashId()
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
index 1c88187..8ff4368 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
@@ -28,9 +28,12 @@
@Composable
fun logEntryEvent(): (event: LogEvent, extraData: Bundle) -> Unit {
val entryId = LocalEntryDataProvider.current.entryId ?: return { _, _ -> }
+ val arguments = LocalEntryDataProvider.current.arguments
return { event, extraData ->
SpaEnvironmentFactory.instance.logger.event(
- entryId, event, category = LogCategory.VIEW, extraData = extraData
+ entryId, event, category = LogCategory.VIEW, extraData = extraData.apply {
+ if (arguments != null) putAll(arguments)
+ }
)
}
}
@@ -40,7 +43,7 @@
if (onClick == null) return null
val logEvent = logEntryEvent()
return {
- logEvent(LogEvent.ENTRY_CLICK, Bundle.EMPTY)
+ logEvent(LogEvent.ENTRY_CLICK, bundleOf())
onClick()
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
index a881254..271443e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
@@ -48,7 +48,10 @@
extraData = bundleOf(
LOG_DATA_DISPLAY_NAME to page.displayName,
LOG_DATA_SESSION_NAME to navController.sessionSourceName,
- )
+ ).apply {
+ val normArguments = parameter.normalize(arguments)
+ if (normArguments != null) putAll(normArguments)
+ }
)
}
if (event == Lifecycle.Event.ON_START) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
index f10d3b0..be303f0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
@@ -47,12 +47,15 @@
return argsArray.joinToString("") { arg -> "/$arg" }
}
-fun List<NamedNavArgument>.normalize(arguments: Bundle? = null): Bundle? {
+fun List<NamedNavArgument>.normalize(
+ arguments: Bundle? = null,
+ eraseRuntimeValues: Boolean = false
+): Bundle? {
if (this.isEmpty()) return null
val normArgs = Bundle()
for (navArg in this) {
// Erase value of runtime parameters.
- if (navArg.isRuntimeParam()) {
+ if (navArg.isRuntimeParam() && eraseRuntimeValues) {
normArgs.putString(navArg.name, null)
continue
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
index b65b91f..3d7d565 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
@@ -24,6 +24,7 @@
import androidx.slice.SliceManager
import androidx.slice.builders.ListBuilder
import androidx.slice.builders.SliceAction
+import androidx.slice.core.R
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.slice.createBroadcastPendingIntent
import com.android.settingslib.spa.slice.createBrowsePendingIntent
@@ -52,10 +53,7 @@
private fun createSliceAction(context: Context, intent: PendingIntent): SliceAction {
return SliceAction.create(
intent,
- IconCompat.createWithResource(
- context,
- com.google.android.material.R.drawable.navigation_empty_icon
- ),
+ IconCompat.createWithResource(context, R.drawable.notification_action_background),
ListBuilder.ICON_IMAGE,
"Enter app"
)
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
index 48ebd8d..0aa846c 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
@@ -88,7 +88,7 @@
val emptyParam = navArguments.normalize()
assertThat(emptyParam).isNotNull()
assertThat(emptyParam.toString()).isEqualTo(
- "Bundle[{rt_param=null, unset_string_param=null, unset_int_param=null}]"
+ "Bundle[{unset_rt_param=null, unset_string_param=null, unset_int_param=null}]"
)
val setPartialParam = navArguments.normalize(
@@ -99,7 +99,7 @@
)
assertThat(setPartialParam).isNotNull()
assertThat(setPartialParam.toString()).isEqualTo(
- "Bundle[{rt_param=null, string_param=myStr, unset_int_param=null}]"
+ "Bundle[{rt_param=rtStr, string_param=myStr, unset_int_param=null}]"
)
val setAllParam = navArguments.normalize(
@@ -107,7 +107,8 @@
"string_param" to "myStr",
"int_param" to 10,
"rt_param" to "rtStr",
- )
+ ),
+ eraseRuntimeValues = true
)
assertThat(setAllParam).isNotNull()
assertThat(setAllParam.toString()).isEqualTo(
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
index 7e51fea..ce9b791 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
@@ -27,7 +27,7 @@
parameter: List<NamedNavArgument> = emptyList(),
arguments: Bundle? = null
): String {
- val normArguments = parameter.normalize(arguments)
+ val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
return "$name:${normArguments?.toString()}".toHashId()
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index d3ba5e6..973d0de 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -774,6 +774,9 @@
<!-- Permissions required for CTS test - CtsAppFgsTestCases -->
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
+ <!-- Permission required for CTS test - CtsHardwareTestCases -->
+ <uses-permission android:name="android.permission.REMAP_MODIFIER_KEYS" />
+
<!-- Permissions required for CTS test - CtsAppFgsTestCases -->
<uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED" />
<uses-permission android:name="android.permission.health.READ_BASAL_BODY_TEMPERATURE" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
index 316ad39..411fea5 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
@@ -18,8 +18,6 @@
<TextView
android:id="@+id/digit_text"
style="@style/Widget.TextView.NumPadKey.Digit"
- android:autoSizeMaxTextSize="32sp"
- android:autoSizeTextType="uniform"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 6d0cc5e..738b37c 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -34,7 +34,6 @@
import platform.test.screenshot.DeviceEmulationRule
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.MaterialYouColorsRule
-import platform.test.screenshot.PathConfig
import platform.test.screenshot.ScreenshotTestRule
import platform.test.screenshot.getEmulatedDevicePathConfig
import platform.test.screenshot.matchers.BitmapMatcher
@@ -43,7 +42,6 @@
class ViewScreenshotTestRule(
emulationSpec: DeviceEmulationSpec,
private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
- pathConfig: PathConfig = getEmulatedDevicePathConfig(emulationSpec),
assetsPathRelativeToBuildRoot: String
) : TestRule {
private val colorsRule = MaterialYouColorsRule()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index d172690..d85292a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -38,6 +38,7 @@
const val ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG"
const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS"
const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS"
+ const val ACTION_SYSUI_STARTED = "com.android.systemui.STARTED"
const val EXTRA_ID = "id"
const val EXTRA_VALUE = "value"
const val EXTRA_FLAGS = "flags"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
index 142642a..802b9b6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
@@ -42,6 +42,7 @@
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
+import javax.inject.Provider
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin
@@ -64,7 +65,7 @@
private val fingerprintManager: FingerprintManager?,
private val handler: Handler,
private val biometricExecutor: Executor,
- private val alternateTouchProvider: Optional<AlternateUdfpsTouchProvider>,
+ private val alternateTouchProvider: Optional<Provider<AlternateUdfpsTouchProvider>>,
@Main private val fgExecutor: DelayableExecutor,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val authController: AuthController,
@@ -126,6 +127,7 @@
if (!processedMotionEvent && goodOverlap) {
biometricExecutor.execute {
alternateTouchProvider
+ .map(Provider<AlternateUdfpsTouchProvider>::get)
.get()
.onPointerDown(
requestId,
@@ -142,7 +144,10 @@
view.configureDisplay {
biometricExecutor.execute {
- alternateTouchProvider.get().onUiReady()
+ alternateTouchProvider
+ .map(Provider<AlternateUdfpsTouchProvider>::get)
+ .get()
+ .onUiReady()
}
}
@@ -158,7 +163,10 @@
MotionEvent.ACTION_CANCEL -> {
if (processedMotionEvent && alternateTouchProvider.isPresent) {
biometricExecutor.execute {
- alternateTouchProvider.get().onPointerUp(requestId)
+ alternateTouchProvider
+ .map(Provider<AlternateUdfpsTouchProvider>::get)
+ .get()
+ .onPointerUp(requestId)
}
fgExecutor.execute {
if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
@@ -241,7 +249,10 @@
if (overlayView != null && isShowing && alternateTouchProvider.isPresent) {
if (processedMotionEvent) {
biometricExecutor.execute {
- alternateTouchProvider.get().onPointerUp(requestId)
+ alternateTouchProvider
+ .map(Provider<AlternateUdfpsTouchProvider>::get)
+ .get()
+ .onPointerUp(requestId)
}
fgExecutor.execute {
if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index fb37def..63c2065 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -301,7 +301,9 @@
} else {
mView.showDefaultTextPreview();
}
- maybeShowRemoteCopy(clipData);
+ if (!isRemote) {
+ maybeShowRemoteCopy(clipData);
+ }
animateIn();
mView.announceForAccessibility(accessibilityAnnouncement);
if (isRemote) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 81df4ed..267e036 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -87,7 +87,7 @@
new ServerFlagReader.ChangeListener() {
@Override
public void onChange() {
- mRestarter.restart();
+ mRestarter.restartSystemUI();
}
};
@@ -327,9 +327,7 @@
Log.i(TAG, "SystemUI Restart Suppressed");
return;
}
- Log.i(TAG, "Restarting SystemUI");
- // SysUI starts back when up exited. Is there a better way to do this?
- System.exit(0);
+ mRestarter.restartSystemUI();
}
private void restartAndroid(boolean requestSuppress) {
@@ -337,7 +335,7 @@
Log.i(TAG, "Android Restart Suppressed");
return;
}
- mRestarter.restart();
+ mRestarter.restartAndroid();
}
void setBooleanFlagInternal(Flag<?> flag, boolean value) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
index 3d9f627..069e612 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
@@ -28,6 +28,8 @@
private val systemExitRestarter: SystemExitRestarter,
) : Restarter {
+ private var androidRestartRequested = false
+
val observer =
object : WakefulnessLifecycle.Observer {
override fun onFinishedGoingToSleep() {
@@ -36,8 +38,18 @@
}
}
- override fun restart() {
- Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting on next screen off.")
+ override fun restartSystemUI() {
+ Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting on next screen off.")
+ scheduleRestart()
+ }
+
+ override fun restartAndroid() {
+ Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting on next screen off.")
+ androidRestartRequested = true
+ scheduleRestart()
+ }
+
+ fun scheduleRestart() {
if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
restartNow()
} else {
@@ -46,6 +58,10 @@
}
private fun restartNow() {
- systemExitRestarter.restart()
+ if (androidRestartRequested) {
+ systemExitRestarter.restartAndroid()
+ } else {
+ systemExitRestarter.restartSystemUI()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index 7189f00..b94d781 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -16,7 +16,9 @@
package com.android.systemui.flags
+import android.content.Intent
import com.android.systemui.CoreStartable
+import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.commandline.CommandRegistry
import dagger.Binds
@@ -31,7 +33,8 @@
dumpManager: DumpManager,
private val commandRegistry: CommandRegistry,
private val flagCommand: FlagCommand,
- private val featureFlags: FeatureFlagsDebug
+ private val featureFlags: FeatureFlagsDebug,
+ private val broadcastSender: BroadcastSender
) : CoreStartable {
init {
@@ -43,6 +46,8 @@
override fun start() {
featureFlags.init()
commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand }
+ val intent = Intent(FlagManager.ACTION_SYSUI_STARTED)
+ broadcastSender.sendBroadcast(intent)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 3c83682..8bddacc 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -61,7 +61,7 @@
new ServerFlagReader.ChangeListener() {
@Override
public void onChange() {
- mRestarter.restart();
+ mRestarter.restartSystemUI();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
index a3f0f66..7ff3876 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
@@ -34,35 +34,48 @@
@Background private val bgExecutor: DelayableExecutor,
private val systemExitRestarter: SystemExitRestarter
) : Restarter {
- var shouldRestart = false
+ var listenersAdded = false
var pendingRestart: Runnable? = null
+ var androidRestartRequested = false
val observer =
object : WakefulnessLifecycle.Observer {
override fun onFinishedGoingToSleep() {
- maybeScheduleRestart()
+ scheduleRestart()
}
}
val batteryCallback =
object : BatteryController.BatteryStateChangeCallback {
override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
- maybeScheduleRestart()
+ scheduleRestart()
}
}
- override fun restart() {
- Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting when plugged in and idle.")
- if (!shouldRestart) {
- // Don't bother scheduling twice.
- shouldRestart = true
- wakefulnessLifecycle.addObserver(observer)
- batteryController.addCallback(batteryCallback)
- maybeScheduleRestart()
- }
+ override fun restartSystemUI() {
+ Log.d(
+ FeatureFlagsDebug.TAG,
+ "SystemUI Restart requested. Restarting when plugged in and idle."
+ )
+ scheduleRestart()
}
- private fun maybeScheduleRestart() {
+ override fun restartAndroid() {
+ Log.d(
+ FeatureFlagsDebug.TAG,
+ "Android Restart requested. Restarting when plugged in and idle."
+ )
+ androidRestartRequested = true
+ scheduleRestart()
+ }
+
+ private fun scheduleRestart() {
+ // Don't bother adding listeners twice.
+ if (!listenersAdded) {
+ listenersAdded = true
+ wakefulnessLifecycle.addObserver(observer)
+ batteryController.addCallback(batteryCallback)
+ }
if (
wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn
) {
@@ -77,6 +90,10 @@
private fun restartNow() {
Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
- systemExitRestarter.restart()
+ if (androidRestartRequested) {
+ systemExitRestarter.restartAndroid()
+ } else {
+ systemExitRestarter.restartSystemUI()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
index 8f095a2..ce8b821 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
@@ -16,5 +16,7 @@
package com.android.systemui.flags
interface Restarter {
- fun restart()
-}
\ No newline at end of file
+ fun restartSystemUI()
+
+ fun restartAndroid()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
index f1b1be4..89daa64 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
@@ -16,10 +16,19 @@
package com.android.systemui.flags
+import com.android.internal.statusbar.IStatusBarService
import javax.inject.Inject
-class SystemExitRestarter @Inject constructor() : Restarter {
- override fun restart() {
+class SystemExitRestarter
+@Inject
+constructor(
+ private val barService: IStatusBarService,
+) : Restarter {
+ override fun restartAndroid() {
+ barService.restart()
+ }
+
+ override fun restartSystemUI() {
System.exit(0)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index c4eac1c..c0d6cc9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -824,7 +824,11 @@
surfaceBehindEntryAnimator.cancel()
surfaceBehindAlpha = 1f
setSurfaceBehindAppearAmount(1f)
- launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
+ try {
+ launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Remote exception in notifyFinishedKeyguardExitAnimation", e)
+ }
// That target is no longer valid since the animation finished, null it out.
surfaceBehindRemoteAnimationTargets = null
diff --git a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
index c7e4c5e..b98a92f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
@@ -49,7 +49,9 @@
@SysUISingleton
public class SessionTracker implements CoreStartable {
private static final String TAG = "SessionTracker";
- private static final boolean DEBUG = false;
+
+ // To enable logs: `adb shell setprop log.tag.SessionTracker DEBUG` & restart sysui
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// At most 20 bits: ~1m possibilities, ~0.5% probability of collision in 100 values
private final InstanceIdSequence mInstanceIdGenerator = new InstanceIdSequence(1 << 20);
@@ -81,8 +83,8 @@
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
- mKeyguardSessionStarted = mKeyguardStateController.isShowing();
- if (mKeyguardSessionStarted) {
+ if (mKeyguardStateController.isShowing()) {
+ mKeyguardSessionStarted = true;
startSession(SESSION_KEYGUARD);
}
}
@@ -136,12 +138,11 @@
new KeyguardUpdateMonitorCallback() {
@Override
public void onStartedGoingToSleep(int why) {
- // we need to register to the KeyguardUpdateMonitor lifecycle b/c it gets called
- // before the WakefulnessLifecycle
if (mKeyguardSessionStarted) {
- return;
+ endSession(SESSION_KEYGUARD);
}
+ // Start a new session whenever the device goes to sleep
mKeyguardSessionStarted = true;
startSession(SESSION_KEYGUARD);
}
@@ -154,6 +155,9 @@
boolean wasSessionStarted = mKeyguardSessionStarted;
boolean keyguardShowing = mKeyguardStateController.isShowing();
if (keyguardShowing && !wasSessionStarted) {
+ // the keyguard can start showing without the device going to sleep (ie: lockdown
+ // from the power button), so we start a new keyguard session when the keyguard is
+ // newly shown in addition to when the device starts going to sleep
mKeyguardSessionStarted = true;
startSession(SESSION_KEYGUARD);
} else if (!keyguardShowing && wasSessionStarted) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 3012bb4..2dd339d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -422,6 +422,7 @@
appUid = appUid
)
mediaEntries.put(packageName, resumeData)
+ logSingleVsMultipleMediaAdded(appUid, packageName, instanceId)
logger.logResumeMediaAdded(appUid, packageName, instanceId)
}
backgroundExecutor.execute {
@@ -812,6 +813,7 @@
val appUid = appInfo?.uid ?: Process.INVALID_UID
if (logEvent) {
+ logSingleVsMultipleMediaAdded(appUid, sbn.packageName, instanceId)
logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
} else if (playbackLocation != currentEntry?.playbackLocation) {
logger.logPlaybackLocationChange(appUid, sbn.packageName, instanceId, playbackLocation)
@@ -855,6 +857,20 @@
}
}
+ private fun logSingleVsMultipleMediaAdded(
+ appUid: Int,
+ packageName: String,
+ instanceId: InstanceId
+ ) {
+ if (mediaEntries.size == 1) {
+ logger.logSingleMediaPlayerInCarousel(appUid, packageName, instanceId)
+ } else if (mediaEntries.size == 2) {
+ // Since this method is only called when there is a new media session added.
+ // logging needed once there is more than one media session in carousel.
+ logger.logMultipleMediaPlayersInCarousel(appUid, packageName, instanceId)
+ }
+ }
+
private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
try {
return context.packageManager.getApplicationInfo(packageName, 0)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
index 3ad8c21..ea943be 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
@@ -213,6 +213,24 @@
instanceId
)
}
+
+ fun logSingleMediaPlayerInCarousel(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(
+ MediaUiEvent.MEDIA_CAROUSEL_SINGLE_PLAYER,
+ uid,
+ packageName,
+ instanceId
+ )
+ }
+
+ fun logMultipleMediaPlayersInCarousel(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(
+ MediaUiEvent.MEDIA_CAROUSEL_MULTIPLE_PLAYERS,
+ uid,
+ packageName,
+ instanceId
+ )
+ }
}
enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
@@ -269,7 +287,11 @@
@UiEvent(doc = "User tapped on a media recommendation card")
MEDIA_RECOMMENDATION_CARD_TAP(1045),
@UiEvent(doc = "User opened the broadcast dialog from a media control")
- MEDIA_OPEN_BROADCAST_DIALOG(1079);
+ MEDIA_OPEN_BROADCAST_DIALOG(1079),
+ @UiEvent(doc = "The media carousel contains one media player card")
+ MEDIA_CAROUSEL_SINGLE_PLAYER(1244),
+ @UiEvent(doc = "The media carousel contains multiple media player cards")
+ MEDIA_CAROUSEL_MULTIPLE_PLAYERS(1245);
override fun getId() = metricId
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 9542a02..28dd986 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -333,7 +333,14 @@
mCellularInfo.mAirplaneModeEnabled = icon.visible;
mWifiInfo.mAirplaneModeEnabled = icon.visible;
if (!mSignalCallback.mEthernetInfo.mConnected) {
- if (mWifiInfo.mEnabled && (mWifiInfo.mWifiSignalIconId > 0)
+ // Always use mWifiInfo to refresh the Internet Tile if airplane mode is enabled,
+ // because Internet Tile will show different information depending on whether WiFi
+ // is enabled or not.
+ if (mWifiInfo.mAirplaneModeEnabled) {
+ refreshState(mWifiInfo);
+ // If airplane mode is disabled, we will use mWifiInfo to refresh the Internet Tile
+ // if WiFi is currently connected to avoid any icon flickering.
+ } else if (mWifiInfo.mEnabled && (mWifiInfo.mWifiSignalIconId > 0)
&& (mWifiInfo.mSsid != null)) {
refreshState(mWifiInfo);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 6bd9502..8bb2d46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -102,6 +102,8 @@
private var showSensitiveContentForManagedUser = false
private var managedUserHandle: UserHandle? = null
+ // TODO(b/202758428): refactor so that we can test color updates via region samping, similar to
+ // how we test color updates when theme changes (See testThemeChangeUpdatesTextColor).
private val updateFun: UpdateColorCallback = { updateTextColorFromRegionSampler() }
// TODO: Move logic into SmartspaceView
@@ -109,16 +111,19 @@
override fun onViewAttachedToWindow(v: View) {
smartspaceViews.add(v as SmartspaceView)
- var regionSampler = RegionSampler(
- v,
- uiExecutor,
- bgExecutor,
- regionSamplingEnabled,
- updateFun
- )
- initializeTextColors(regionSampler)
- regionSampler.startRegionSampler()
- regionSamplers.put(v, regionSampler)
+ if (regionSamplingEnabled) {
+ var regionSampler = RegionSampler(
+ v,
+ uiExecutor,
+ bgExecutor,
+ regionSamplingEnabled,
+ updateFun
+ )
+ initializeTextColors(regionSampler)
+ regionSampler.startRegionSampler()
+ regionSamplers.put(v, regionSampler)
+ }
+
connectSession()
updateTextColorFromWallpaper()
@@ -128,9 +133,11 @@
override fun onViewDetachedFromWindow(v: View) {
smartspaceViews.remove(v as SmartspaceView)
- var regionSampler = regionSamplers.getValue(v)
- regionSampler.stopRegionSampler()
- regionSamplers.remove(v)
+ if (regionSamplingEnabled) {
+ var regionSampler = regionSamplers.getValue(v)
+ regionSampler.stopRegionSampler()
+ regionSamplers.remove(v)
+ }
if (smartspaceViews.isEmpty()) {
disconnect()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
index 1e7b1f2..ed16721 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
@@ -48,22 +48,22 @@
@Test
fun testRestart_ImmediateWhenAsleep() {
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- restarter.restart()
- verify(systemExitRestarter).restart()
+ restarter.restartSystemUI()
+ verify(systemExitRestarter).restartSystemUI()
}
@Test
fun testRestart_WaitsForSceenOff() {
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
- restarter.restart()
- verify(systemExitRestarter, never()).restart()
+ restarter.restartSystemUI()
+ verify(systemExitRestarter, never()).restartSystemUI()
val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
verify(wakefulnessLifecycle).addObserver(captor.capture())
captor.value.onFinishedGoingToSleep()
- verify(systemExitRestarter).restart()
+ verify(systemExitRestarter).restartSystemUI()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
index 68ca48d..7d807e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
@@ -63,7 +63,7 @@
whenever(batteryController.isPluggedIn).thenReturn(true)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restart()
+ restarter.restartSystemUI()
assertThat(executor.numPending()).isEqualTo(1)
}
@@ -72,11 +72,11 @@
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
whenever(batteryController.isPluggedIn).thenReturn(true)
- restarter.restart()
- verify(systemExitRestarter, never()).restart()
+ restarter.restartSystemUI()
+ verify(systemExitRestarter, never()).restartSystemUI()
executor.advanceClockToLast()
executor.runAllReady()
- verify(systemExitRestarter).restart()
+ verify(systemExitRestarter).restartSystemUI()
}
@Test
@@ -85,7 +85,7 @@
whenever(batteryController.isPluggedIn).thenReturn(true)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restart()
+ restarter.restartSystemUI()
assertThat(executor.numPending()).isEqualTo(0)
}
@@ -95,7 +95,7 @@
whenever(batteryController.isPluggedIn).thenReturn(false)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restart()
+ restarter.restartSystemUI()
assertThat(executor.numPending()).isEqualTo(0)
}
@@ -105,8 +105,8 @@
whenever(batteryController.isPluggedIn).thenReturn(true)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restart()
- restarter.restart()
+ restarter.restartSystemUI()
+ restarter.restartSystemUI()
assertThat(executor.numPending()).isEqualTo(1)
}
@@ -115,7 +115,7 @@
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
whenever(batteryController.isPluggedIn).thenReturn(true)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restart()
+ restarter.restartSystemUI()
val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
verify(wakefulnessLifecycle).addObserver(captor.capture())
@@ -131,7 +131,7 @@
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
whenever(batteryController.isPluggedIn).thenReturn(false)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restart()
+ restarter.restartSystemUI()
val captor =
ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
index dc5522e..aa54a1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
@@ -23,8 +23,10 @@
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
+import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -171,6 +173,34 @@
}
@Test
+ public void testKeyguardSessionOnDeviceStartsSleepingTwiceInARow_startsNewKeyguardSession()
+ throws RemoteException {
+ // GIVEN session tracker started w/o any sessions
+ mSessionTracker.start();
+ captureKeyguardUpdateMonitorCallback();
+
+ // WHEN device starts going to sleep
+ mKeyguardUpdateMonitorCallback.onStartedGoingToSleep(0);
+
+ // THEN the keyguard session has a session id
+ final InstanceId firstSessionId = mSessionTracker.getSessionId(SESSION_KEYGUARD);
+ assertNotNull(firstSessionId);
+
+ // WHEN device starts going to sleep a second time
+ mKeyguardUpdateMonitorCallback.onStartedGoingToSleep(0);
+
+ // THEN there's a new keyguard session with a unique session id
+ final InstanceId secondSessionId = mSessionTracker.getSessionId(SESSION_KEYGUARD);
+ assertNotNull(secondSessionId);
+ assertNotEquals(firstSessionId, secondSessionId);
+
+ // THEN session start event gets sent to status bar service twice (once per going to
+ // sleep signal)
+ verify(mStatusBarService, times(2)).onSessionStarted(
+ eq(SESSION_KEYGUARD), any(InstanceId.class));
+ }
+
+ @Test
public void testKeyguardSessionOnKeyguardShowingChange() throws RemoteException {
// GIVEN session tracker started w/o any sessions
mSessionTracker.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index d91baa5..80c39cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -23,6 +23,7 @@
import android.os.Handler;
+import android.service.quicksettings.Tile;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -38,6 +39,7 @@
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.statusbar.connectivity.AccessPointController;
+import com.android.systemui.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.NetworkController;
import org.junit.Before;
@@ -113,4 +115,24 @@
.isNotEqualTo(mContext.getString(R.string.quick_settings_networks_available));
assertThat(mTile.getLastTileState()).isEqualTo(-1);
}
+
+ @Test
+ public void setIsAirplaneMode_APM_enabled_wifi_disabled() {
+ IconState state = new IconState(true, 0, "");
+ mTile.mSignalCallback.setIsAirplaneMode(state);
+ mTestableLooper.processAllMessages();
+ assertThat(mTile.getState().state).isEqualTo(Tile.STATE_INACTIVE);
+ assertThat(mTile.getState().secondaryLabel)
+ .isEqualTo(mContext.getString(R.string.status_bar_airplane));
+ }
+
+ @Test
+ public void setIsAirplaneMode_APM_enabled_wifi_enabled() {
+ IconState state = new IconState(false, 0, "");
+ mTile.mSignalCallback.setIsAirplaneMode(state);
+ mTestableLooper.processAllMessages();
+ assertThat(mTile.getState().state).isEqualTo(Tile.STATE_ACTIVE);
+ assertThat(mTile.getState().secondaryLabel)
+ .isNotEqualTo(mContext.getString(R.string.status_bar_airplane));
+ }
}
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 471f8d3..14a319b 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
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -11,7 +11,7 @@
* 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
+ * limitations under the License.
*/
package com.android.systemui.statusbar.phone;
@@ -105,7 +105,6 @@
@Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory;
@Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
@Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
- @Mock private KeyguardBouncer mPrimaryBouncer;
@Mock private StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
@Mock private KeyguardMessageArea mKeyguardMessageArea;
@Mock private ShadeController mShadeController;
@@ -133,16 +132,14 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mKeyguardBouncerFactory.create(
- any(ViewGroup.class),
- any(KeyguardBouncer.PrimaryBouncerExpansionCallback.class)))
- .thenReturn(mPrimaryBouncer);
when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer);
when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
.thenReturn(mKeyguardMessageAreaController);
when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
+ when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(true);
+
mStatusBarKeyguardViewManager =
new StatusBarKeyguardViewManager(
getContext(),
@@ -184,7 +181,7 @@
mStatusBarKeyguardViewManager.show(null);
ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
ArgumentCaptor.forClass(KeyguardBouncer.PrimaryBouncerExpansionCallback.class);
- verify(mKeyguardBouncerFactory).create(any(ViewGroup.class),
+ verify(mPrimaryBouncerCallbackInteractor).addBouncerExpansionCallback(
callbackArgumentCaptor.capture());
mBouncerExpansionCallback = callbackArgumentCaptor.getValue();
}
@@ -195,87 +192,87 @@
Runnable cancelAction = () -> {};
mStatusBarKeyguardViewManager.dismissWithAction(
action, cancelAction, false /* afterKeyguardGone */);
- verify(mPrimaryBouncer).showWithDismissAction(eq(action), eq(cancelAction));
+ verify(mPrimaryBouncerInteractor).setDismissAction(eq(action), eq(cancelAction));
+ verify(mPrimaryBouncerInteractor).show(eq(true));
}
@Test
public void showBouncer_onlyWhenShowing() {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
- verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
- verify(mPrimaryBouncer, never()).show(anyBoolean());
+ verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
}
@Test
public void showBouncer_notWhenBouncerAlreadyShowing() {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
- when(mPrimaryBouncer.isSecure()).thenReturn(true);
+ when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+ KeyguardSecurityModel.SecurityMode.Password);
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
- verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
- verify(mPrimaryBouncer, never()).show(anyBoolean());
+ verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
}
@Test
public void showBouncer_showsTheBouncer() {
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
- verify(mPrimaryBouncer).show(anyBoolean(), eq(true));
+ verify(mPrimaryBouncerInteractor).show(eq(true));
}
@Test
public void onPanelExpansionChanged_neverShowsDuringHintAnimation() {
when(mNotificationPanelView.isUnlockHintRunning()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
}
@Test
public void onPanelExpansionChanged_propagatesToBouncerOnlyIfShowing() {
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(eq(0.5f));
- when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(
expansionEvent(/* fraction= */ 0.6f, /* expanded= */ false, /* tracking= */ true));
- verify(mPrimaryBouncer).setExpansion(eq(0.6f));
+ verify(mPrimaryBouncerInteractor).setPanelExpansion(eq(0.6f));
}
@Test
public void onPanelExpansionChanged_duplicateEventsAreIgnored() {
- when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer).setExpansion(eq(0.5f));
+ verify(mPrimaryBouncerInteractor).setPanelExpansion(eq(0.5f));
- reset(mPrimaryBouncer);
+ reset(mPrimaryBouncerInteractor);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(eq(0.5f));
}
@Test
public void onPanelExpansionChanged_hideBouncer_afterKeyguardHidden() {
mStatusBarKeyguardViewManager.hide(0, 0);
- when(mPrimaryBouncer.inTransit()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
+ verify(mPrimaryBouncerInteractor).setPanelExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
}
@Test
public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
mKeyguardStateController.setCanDismissLockScreen(false);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer).show(eq(false), eq(false));
+ verify(mPrimaryBouncerInteractor).show(eq(false));
// But not when it's already visible
- reset(mPrimaryBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ reset(mPrimaryBouncerInteractor);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
+ verify(mPrimaryBouncerInteractor, never()).show(eq(false));
// Or animating away
- reset(mPrimaryBouncer);
- when(mPrimaryBouncer.isAnimatingAway()).thenReturn(true);
+ reset(mPrimaryBouncerInteractor);
+ when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
+ verify(mPrimaryBouncerInteractor, never()).show(eq(false));
}
@Test
@@ -287,7 +284,7 @@
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
/* expanded= */ true,
/* tracking= */ false));
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
}
@Test
@@ -304,7 +301,7 @@
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
/* expanded= */ true,
/* tracking= */ false));
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
}
@Test
@@ -315,7 +312,7 @@
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
/* expanded= */ true,
/* tracking= */ false));
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
}
@Test
@@ -332,7 +329,7 @@
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
/* expanded= */ true,
/* tracking= */ false));
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
}
@Test
@@ -343,7 +340,7 @@
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
/* expanded= */ true,
/* tracking= */ false));
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
}
@Test
@@ -351,7 +348,7 @@
mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
verify(mCentralSurfaces).animateKeyguardUnoccluding();
- when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
clearInvocations(mCentralSurfaces);
mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
verify(mCentralSurfaces, never()).animateKeyguardUnoccluding();
@@ -402,7 +399,7 @@
mStatusBarKeyguardViewManager.dismissWithAction(
action, cancelAction, true /* afterKeyguardGone */);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
mStatusBarKeyguardViewManager.hideBouncer(true);
mStatusBarKeyguardViewManager.hide(0, 30);
verify(action, never()).onDismiss();
@@ -416,7 +413,7 @@
mStatusBarKeyguardViewManager.dismissWithAction(
action, cancelAction, true /* afterKeyguardGone */);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
mStatusBarKeyguardViewManager.hideBouncer(true);
verify(action, never()).onDismiss();
@@ -438,7 +435,7 @@
@Test
public void testShowing_whenAlternateAuthShowing() {
mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
assertTrue(
"Is showing not accurate when alternative auth showing",
@@ -448,7 +445,7 @@
@Test
public void testWillBeShowing_whenAlternateAuthShowing() {
mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
assertTrue(
"Is or will be showing not accurate when alternative auth showing",
@@ -459,7 +456,7 @@
public void testHideAlternateBouncer_onShowBouncer() {
// GIVEN alt auth is showing
mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
reset(mAlternateBouncer);
@@ -472,8 +469,8 @@
@Test
public void testBouncerIsOrWillBeShowing_whenBouncerIsInTransit() {
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
- when(mPrimaryBouncer.inTransit()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
assertTrue(
"Is or will be showing should be true when bouncer is in transit",
@@ -484,7 +481,7 @@
public void testShowAltAuth_unlockingWithBiometricNotAllowed() {
// GIVEN alt auth exists, unlocking with biometric isn't allowed
mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
.thenReturn(false);
@@ -493,7 +490,7 @@
mStatusBarKeyguardViewManager.showBouncer(scrimmed);
// THEN regular bouncer is shown
- verify(mPrimaryBouncer).show(anyBoolean(), eq(scrimmed));
+ verify(mPrimaryBouncerInteractor).show(eq(scrimmed));
verify(mAlternateBouncer, never()).showAlternateBouncer();
}
@@ -501,7 +498,7 @@
public void testShowAlternateBouncer_unlockingWithBiometricAllowed() {
// GIVEN alt auth exists, unlocking with biometric is allowed
mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
// WHEN showGenericBouncer is called
@@ -509,30 +506,28 @@
// THEN alt auth bouncer is shown
verify(mAlternateBouncer).showAlternateBouncer();
- verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+ verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
}
@Test
public void testUpdateResources_delegatesToBouncer() {
mStatusBarKeyguardViewManager.updateResources();
- verify(mPrimaryBouncer).updateResources();
+ verify(mPrimaryBouncerInteractor).updateResources();
}
@Test
public void updateKeyguardPosition_delegatesToBouncer() {
mStatusBarKeyguardViewManager.updateKeyguardPosition(1.0f);
- verify(mPrimaryBouncer).updateKeyguardPosition(1.0f);
+ verify(mPrimaryBouncerInteractor).setKeyguardPosition(1.0f);
}
@Test
public void testIsBouncerInTransit() {
- when(mPrimaryBouncer.inTransit()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isTrue();
- when(mPrimaryBouncer.inTransit()).thenReturn(false);
- Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
- mPrimaryBouncer = null;
+ when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(false);
Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
}
@@ -564,7 +559,7 @@
eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
mOnBackInvokedCallback.capture());
- when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
/* invoke the back callback directly */
mOnBackInvokedCallback.getValue().onBackInvoked();
@@ -594,13 +589,6 @@
}
@Test
- public void flag_off_DoesNotCallBouncerInteractor() {
- when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
- mStatusBarKeyguardViewManager.hideBouncer(false);
- verify(mPrimaryBouncerInteractor, never()).hide();
- }
-
- @Test
public void hideAlternateBouncer_beforeCentralSurfacesRegistered() {
mStatusBarKeyguardViewManager =
new StatusBarKeyguardViewManager(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java
new file mode 100644
index 0000000..96fba39
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright (C) 2018 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.phone;
+
+import static com.android.systemui.flags.Flags.MODERN_BOUNCER;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+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.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewRootImpl;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
+import android.window.WindowOnBackInvokedDispatcher;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardMessageArea;
+import com.android.keyguard.KeyguardMessageAreaController;
+import com.android.keyguard.KeyguardSecurityModel;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.data.BouncerView;
+import com.android.systemui.keyguard.data.BouncerViewDelegate;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+/**
+ * StatusBarKeyguardViewManager Test with deprecated KeyguardBouncer.java.
+ * TODO: Delete when deleting {@link KeyguardBouncer}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase {
+ private static final ShadeExpansionChangeEvent EXPANSION_EVENT =
+ expansionEvent(/* fraction= */ 0.5f, /* expanded= */ false, /* tracking= */ true);
+
+ @Mock private ViewMediatorCallback mViewMediatorCallback;
+ @Mock private LockPatternUtils mLockPatternUtils;
+ @Mock private CentralSurfaces mCentralSurfaces;
+ @Mock private ViewGroup mContainer;
+ @Mock private NotificationPanelViewController mNotificationPanelView;
+ @Mock private BiometricUnlockController mBiometricUnlockController;
+ @Mock private SysuiStatusBarStateController mStatusBarStateController;
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock private View mNotificationContainer;
+ @Mock private KeyguardBypassController mBypassController;
+ @Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory;
+ @Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
+ @Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
+ @Mock private KeyguardBouncer mPrimaryBouncer;
+ @Mock private StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
+ @Mock private KeyguardMessageArea mKeyguardMessageArea;
+ @Mock private ShadeController mShadeController;
+ @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
+ @Mock private DreamOverlayStateController mDreamOverlayStateController;
+ @Mock private LatencyTracker mLatencyTracker;
+ @Mock private FeatureFlags mFeatureFlags;
+ @Mock private KeyguardSecurityModel mKeyguardSecurityModel;
+ @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
+ @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ @Mock private BouncerView mBouncerView;
+ @Mock private BouncerViewDelegate mBouncerViewDelegate;
+
+ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback;
+ private FakeKeyguardStateController mKeyguardStateController =
+ spy(new FakeKeyguardStateController());
+
+ @Mock private ViewRootImpl mViewRootImpl;
+ @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
+ @Captor
+ private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
+
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mKeyguardBouncerFactory.create(
+ any(ViewGroup.class),
+ any(KeyguardBouncer.PrimaryBouncerExpansionCallback.class)))
+ .thenReturn(mPrimaryBouncer);
+ when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer);
+ when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
+ when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
+ .thenReturn(mKeyguardMessageAreaController);
+ when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
+
+ mStatusBarKeyguardViewManager =
+ new StatusBarKeyguardViewManager(
+ getContext(),
+ mViewMediatorCallback,
+ mLockPatternUtils,
+ mStatusBarStateController,
+ mock(ConfigurationController.class),
+ mKeyguardUpdateMonitor,
+ mDreamOverlayStateController,
+ mock(NavigationModeController.class),
+ mock(DockManager.class),
+ mock(NotificationShadeWindowController.class),
+ mKeyguardStateController,
+ mock(NotificationMediaManager.class),
+ mKeyguardBouncerFactory,
+ mKeyguardMessageAreaFactory,
+ Optional.of(mSysUiUnfoldComponent),
+ () -> mShadeController,
+ mLatencyTracker,
+ mKeyguardSecurityModel,
+ mFeatureFlags,
+ mPrimaryBouncerCallbackInteractor,
+ mPrimaryBouncerInteractor,
+ mBouncerView) {
+ @Override
+ public ViewRootImpl getViewRootImpl() {
+ return mViewRootImpl;
+ }
+ };
+ when(mViewRootImpl.getOnBackInvokedDispatcher())
+ .thenReturn(mOnBackInvokedDispatcher);
+ mStatusBarKeyguardViewManager.registerCentralSurfaces(
+ mCentralSurfaces,
+ mNotificationPanelView,
+ new ShadeExpansionStateManager(),
+ mBiometricUnlockController,
+ mNotificationContainer,
+ mBypassController);
+ mStatusBarKeyguardViewManager.show(null);
+ ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardBouncer.PrimaryBouncerExpansionCallback.class);
+ verify(mKeyguardBouncerFactory).create(any(ViewGroup.class),
+ callbackArgumentCaptor.capture());
+ mBouncerExpansionCallback = callbackArgumentCaptor.getValue();
+ }
+
+ @Test
+ public void dismissWithAction_AfterKeyguardGoneSetToFalse() {
+ OnDismissAction action = () -> false;
+ Runnable cancelAction = () -> {};
+ mStatusBarKeyguardViewManager.dismissWithAction(
+ action, cancelAction, false /* afterKeyguardGone */);
+ verify(mPrimaryBouncer).showWithDismissAction(eq(action), eq(cancelAction));
+ }
+
+ @Test
+ public void showBouncer_onlyWhenShowing() {
+ mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+ verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+ verify(mPrimaryBouncer, never()).show(anyBoolean());
+ }
+
+ @Test
+ public void showBouncer_notWhenBouncerAlreadyShowing() {
+ mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
+ when(mPrimaryBouncer.isSecure()).thenReturn(true);
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+ verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+ verify(mPrimaryBouncer, never()).show(anyBoolean());
+ }
+
+ @Test
+ public void showBouncer_showsTheBouncer() {
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+ verify(mPrimaryBouncer).show(anyBoolean(), eq(true));
+ }
+
+ @Test
+ public void onPanelExpansionChanged_neverShowsDuringHintAnimation() {
+ when(mNotificationPanelView.isUnlockHintRunning()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
+ public void onPanelExpansionChanged_propagatesToBouncerOnlyIfShowing() {
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
+
+ when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(/* fraction= */ 0.6f, /* expanded= */ false, /* tracking= */ true));
+ verify(mPrimaryBouncer).setExpansion(eq(0.6f));
+ }
+
+ @Test
+ public void onPanelExpansionChanged_duplicateEventsAreIgnored() {
+ when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer).setExpansion(eq(0.5f));
+
+ reset(mPrimaryBouncer);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
+ }
+
+ @Test
+ public void onPanelExpansionChanged_hideBouncer_afterKeyguardHidden() {
+ mStatusBarKeyguardViewManager.hide(0, 0);
+ when(mPrimaryBouncer.inTransit()).thenReturn(true);
+
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
+ }
+
+ @Test
+ public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
+ mKeyguardStateController.setCanDismissLockScreen(false);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer).show(eq(false), eq(false));
+
+ // But not when it's already visible
+ reset(mPrimaryBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
+
+ // Or animating away
+ reset(mPrimaryBouncer);
+ when(mPrimaryBouncer.isAnimatingAway()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
+ }
+
+ @Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() {
+ when(mBiometricUnlockController.getMode())
+ .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenDismissBouncer() {
+ // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
+ // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
+ // which would mistakenly cause the bouncer to show briefly before its visibility
+ // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
+ // bouncer if the bouncer is dismissing as a result of a biometric unlock.
+ when(mBiometricUnlockController.getMode())
+ .thenReturn(BiometricUnlockController.MODE_DISMISS_BOUNCER);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
+ // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
+ // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
+ // which would mistakenly cause the bouncer to show briefly before its visibility
+ // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
+ // bouncer if the bouncer is dismissing as a result of a biometric unlock.
+ when(mBiometricUnlockController.getMode())
+ .thenReturn(BiometricUnlockController.MODE_SHOW_BOUNCER);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenShadeLocked() {
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
+ public void setOccluded_animatesPanelExpansion_onlyIfBouncerHidden() {
+ mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
+ verify(mCentralSurfaces).animateKeyguardUnoccluding();
+
+ when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ clearInvocations(mCentralSurfaces);
+ mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
+ verify(mCentralSurfaces, never()).animateKeyguardUnoccluding();
+ }
+
+ @Test
+ public void setOccluded_onKeyguardOccludedChangedCalled() {
+ clearInvocations(mKeyguardStateController);
+ clearInvocations(mKeyguardUpdateMonitor);
+
+ mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
+ verify(mKeyguardStateController).notifyKeyguardState(true, false);
+
+ clearInvocations(mKeyguardUpdateMonitor);
+ clearInvocations(mKeyguardStateController);
+
+ mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
+ verify(mKeyguardStateController).notifyKeyguardState(true, true);
+
+ clearInvocations(mKeyguardUpdateMonitor);
+ clearInvocations(mKeyguardStateController);
+
+ mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
+ verify(mKeyguardStateController).notifyKeyguardState(true, false);
+ }
+
+ @Test
+ public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() {
+ mStatusBarKeyguardViewManager.show(null);
+
+ mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
+ verify(mKeyguardStateController).notifyKeyguardState(true, true);
+ }
+
+ @Test
+ public void setOccluded_isLaunchingActivityOverLockscreen_onKeyguardOccludedChangedCalled() {
+ when(mCentralSurfaces.isLaunchingActivityOverLockscreen()).thenReturn(true);
+ mStatusBarKeyguardViewManager.show(null);
+
+ mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
+ verify(mKeyguardStateController).notifyKeyguardState(true, true);
+ }
+
+ @Test
+ public void testHiding_cancelsGoneRunnable() {
+ OnDismissAction action = mock(OnDismissAction.class);
+ Runnable cancelAction = mock(Runnable.class);
+ mStatusBarKeyguardViewManager.dismissWithAction(
+ action, cancelAction, true /* afterKeyguardGone */);
+
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ mStatusBarKeyguardViewManager.hideBouncer(true);
+ mStatusBarKeyguardViewManager.hide(0, 30);
+ verify(action, never()).onDismiss();
+ verify(cancelAction).run();
+ }
+
+ @Test
+ public void testHidingBouncer_cancelsGoneRunnable() {
+ OnDismissAction action = mock(OnDismissAction.class);
+ Runnable cancelAction = mock(Runnable.class);
+ mStatusBarKeyguardViewManager.dismissWithAction(
+ action, cancelAction, true /* afterKeyguardGone */);
+
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ mStatusBarKeyguardViewManager.hideBouncer(true);
+
+ verify(action, never()).onDismiss();
+ verify(cancelAction).run();
+ }
+
+ @Test
+ public void testHiding_doesntCancelWhenShowing() {
+ OnDismissAction action = mock(OnDismissAction.class);
+ Runnable cancelAction = mock(Runnable.class);
+ mStatusBarKeyguardViewManager.dismissWithAction(
+ action, cancelAction, true /* afterKeyguardGone */);
+
+ mStatusBarKeyguardViewManager.hide(0, 30);
+ verify(action).onDismiss();
+ verify(cancelAction, never()).run();
+ }
+
+ @Test
+ public void testShowing_whenAlternateAuthShowing() {
+ mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+ assertTrue(
+ "Is showing not accurate when alternative auth showing",
+ mStatusBarKeyguardViewManager.isBouncerShowing());
+ }
+
+ @Test
+ public void testWillBeShowing_whenAlternateAuthShowing() {
+ mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+ assertTrue(
+ "Is or will be showing not accurate when alternative auth showing",
+ mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
+ }
+
+ @Test
+ public void testHideAlternateBouncer_onShowBouncer() {
+ // GIVEN alt auth is showing
+ mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+ reset(mAlternateBouncer);
+
+ // WHEN showBouncer is called
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true);
+
+ // THEN alt bouncer should be hidden
+ verify(mAlternateBouncer).hideAlternateBouncer();
+ }
+
+ @Test
+ public void testBouncerIsOrWillBeShowing_whenBouncerIsInTransit() {
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncer.inTransit()).thenReturn(true);
+
+ assertTrue(
+ "Is or will be showing should be true when bouncer is in transit",
+ mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
+ }
+
+ @Test
+ public void testShowAltAuth_unlockingWithBiometricNotAllowed() {
+ // GIVEN alt auth exists, unlocking with biometric isn't allowed
+ mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(false);
+
+ // WHEN showGenericBouncer is called
+ final boolean scrimmed = true;
+ mStatusBarKeyguardViewManager.showBouncer(scrimmed);
+
+ // THEN regular bouncer is shown
+ verify(mPrimaryBouncer).show(anyBoolean(), eq(scrimmed));
+ verify(mAlternateBouncer, never()).showAlternateBouncer();
+ }
+
+ @Test
+ public void testShowAlternateBouncer_unlockingWithBiometricAllowed() {
+ // GIVEN alt auth exists, unlocking with biometric is allowed
+ mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+
+ // WHEN showGenericBouncer is called
+ mStatusBarKeyguardViewManager.showBouncer(true);
+
+ // THEN alt auth bouncer is shown
+ verify(mAlternateBouncer).showAlternateBouncer();
+ verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+ }
+
+ @Test
+ public void testUpdateResources_delegatesToBouncer() {
+ mStatusBarKeyguardViewManager.updateResources();
+
+ verify(mPrimaryBouncer).updateResources();
+ }
+
+ @Test
+ public void updateKeyguardPosition_delegatesToBouncer() {
+ mStatusBarKeyguardViewManager.updateKeyguardPosition(1.0f);
+
+ verify(mPrimaryBouncer).updateKeyguardPosition(1.0f);
+ }
+
+ @Test
+ public void testIsBouncerInTransit() {
+ when(mPrimaryBouncer.inTransit()).thenReturn(true);
+ Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isTrue();
+ when(mPrimaryBouncer.inTransit()).thenReturn(false);
+ Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
+ mPrimaryBouncer = null;
+ Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
+ }
+
+ private static ShadeExpansionChangeEvent expansionEvent(
+ float fraction, boolean expanded, boolean tracking) {
+ return new ShadeExpansionChangeEvent(
+ fraction, expanded, tracking, /* dragDownPxAmount= */ 0f);
+ }
+
+ @Test
+ public void testPredictiveBackCallback_registration() {
+ /* verify that a predictive back callback is registered when the bouncer becomes visible */
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+ verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+ eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+ mOnBackInvokedCallback.capture());
+
+ /* verify that the same callback is unregistered when the bouncer becomes invisible */
+ mBouncerExpansionCallback.onVisibilityChanged(false);
+ verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(
+ eq(mOnBackInvokedCallback.getValue()));
+ }
+
+ @Test
+ public void testPredictiveBackCallback_invocationHidesBouncer() {
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+ /* capture the predictive back callback during registration */
+ verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+ eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+ mOnBackInvokedCallback.capture());
+
+ when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
+ /* invoke the back callback directly */
+ mOnBackInvokedCallback.getValue().onBackInvoked();
+
+ /* verify that the bouncer will be hidden as a result of the invocation */
+ verify(mCentralSurfaces).setBouncerShowing(eq(false));
+ }
+
+ @Test
+ public void testReportBouncerOnDreamWhenVisible() {
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+ verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+ Mockito.clearInvocations(mCentralSurfaces);
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+ verify(mCentralSurfaces).setBouncerShowingOverDream(true);
+ }
+
+ @Test
+ public void testReportBouncerOnDreamWhenNotVisible() {
+ mBouncerExpansionCallback.onVisibilityChanged(false);
+ verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+ Mockito.clearInvocations(mCentralSurfaces);
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+ mBouncerExpansionCallback.onVisibilityChanged(false);
+ verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+ }
+
+ @Test
+ public void flag_off_DoesNotCallBouncerInteractor() {
+ when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
+ mStatusBarKeyguardViewManager.hideBouncer(false);
+ verify(mPrimaryBouncerInteractor, never()).hide();
+ }
+
+ @Test
+ public void hideAlternateBouncer_beforeCentralSurfacesRegistered() {
+ mStatusBarKeyguardViewManager =
+ new StatusBarKeyguardViewManager(
+ getContext(),
+ mViewMediatorCallback,
+ mLockPatternUtils,
+ mStatusBarStateController,
+ mock(ConfigurationController.class),
+ mKeyguardUpdateMonitor,
+ mDreamOverlayStateController,
+ mock(NavigationModeController.class),
+ mock(DockManager.class),
+ mock(NotificationShadeWindowController.class),
+ mKeyguardStateController,
+ mock(NotificationMediaManager.class),
+ mKeyguardBouncerFactory,
+ mKeyguardMessageAreaFactory,
+ Optional.of(mSysUiUnfoldComponent),
+ () -> mShadeController,
+ mLatencyTracker,
+ mKeyguardSecurityModel,
+ mFeatureFlags,
+ mPrimaryBouncerCallbackInteractor,
+ mPrimaryBouncerInteractor,
+ mBouncerView) {
+ @Override
+ public ViewRootImpl getViewRootImpl() {
+ return mViewRootImpl;
+ }
+ };
+
+ // the following call before registering centralSurfaces should NOT throw a NPE:
+ mStatusBarKeyguardViewManager.hideAlternateBouncer(true);
+ }
+}
diff --git a/services/Android.bp b/services/Android.bp
index f6570e9..3f3ba06 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -187,6 +187,7 @@
"framework-tethering.stubs.module_lib",
"service-art.stubs.system_server",
"service-permission.stubs.system_server",
+ "service-rkp.stubs.system_server",
"service-sdksandbox.stubs.system_server",
],
diff --git a/services/api/current.txt b/services/api/current.txt
index 18b1df3..da5b1fc 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -57,7 +57,6 @@
public static interface PackageManagerLocal.FilteredSnapshot extends java.lang.AutoCloseable {
method public void close();
- method public void forAllPackageStates(@NonNull java.util.function.Consumer<com.android.server.pm.pkg.PackageState>);
method @Nullable public com.android.server.pm.pkg.PackageState getPackageState(@NonNull String);
method @NonNull public java.util.Map<java.lang.String,com.android.server.pm.pkg.PackageState> getPackageStates();
}
@@ -130,13 +129,6 @@
}
-package com.android.server.pm.snapshot {
-
- public interface PackageDataSnapshot {
- }
-
-}
-
package com.android.server.role {
public interface RoleServicePlatformHelper {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index b6f0237..1e1d610 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -132,6 +132,7 @@
"framework-tethering.stubs.module_lib",
"service-art.stubs.system_server",
"service-permission.stubs.system_server",
+ "service-rkp.stubs.system_server",
"service-sdksandbox.stubs.system_server",
],
plugins: ["ImmutabilityAnnotationProcessor"],
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 6cd7ce8..6f0971c 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -476,6 +476,7 @@
}
private void printPackageMeasurements(PackageInfo packageInfo,
+ boolean useSha256,
final PrintWriter pw) {
Map<Integer, byte[]> contentDigests = computeApkContentDigest(
packageInfo.applicationInfo.sourceDir);
@@ -485,6 +486,14 @@
return;
}
+ if (useSha256) {
+ byte[] fileBuff = PackageUtils.createLargeFileBuffer();
+ String hexEncodedSha256Digest =
+ PackageUtils.computeSha256DigestForLargeFile(
+ packageInfo.applicationInfo.sourceDir, fileBuff);
+ pw.print(hexEncodedSha256Digest + ",");
+ }
+
for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
Integer algorithmId = entry.getKey();
byte[] contentDigest = entry.getValue();
@@ -497,6 +506,7 @@
}
private void printPackageInstallationInfo(PackageInfo packageInfo,
+ boolean useSha256,
final PrintWriter pw) {
pw.println("--- Package Installation Info ---");
pw.println("Current install location: "
@@ -507,11 +517,13 @@
pw.println("|--> Pre-installed package install location: "
+ origPackageFilepath);
- // TODO(b/259347186): revive this with the proper cmd options.
- /*
- String digest = PackageUtils.computeSha256DigestForLargeFile(
- origPackageFilepath, PackageUtils.createLargeFileBuffer());
- */
+ if (useSha256) {
+ String sha256Digest = PackageUtils.computeSha256DigestForLargeFile(
+ origPackageFilepath, PackageUtils.createLargeFileBuffer());
+ pw.println("|--> Pre-installed package SHA-256 digest: "
+ + sha256Digest);
+ }
+
Map<Integer, byte[]> contentDigests = computeApkContentDigest(
origPackageFilepath);
@@ -531,6 +543,8 @@
}
pw.println("First install time (ms): " + packageInfo.firstInstallTime);
pw.println("Last update time (ms): " + packageInfo.lastUpdateTime);
+ // TODO(b/261493591): Determination of whether a package is preinstalled can be
+ // made more robust
boolean isPreloaded = (packageInfo.firstInstallTime
== packageInfo.lastUpdateTime);
pw.println("Is preloaded: " + isPreloaded);
@@ -560,6 +574,7 @@
pw.println("ERROR: Package's signingInfo is null.");
return;
}
+ // TODO(b/261501773): Handle printing of lineage of rotated keys.
pw.println("--- Package Signer Info ---");
pw.println("Has multiple signers: " + signerInfo.hasMultipleSigners());
Signature[] packageSigners = signerInfo.getApkContentsSigners();
@@ -669,15 +684,35 @@
}
+ private void printHeadersHelper(@NonNull String packageType,
+ boolean useSha256,
+ @NonNull final PrintWriter pw) {
+ pw.print(packageType + " Info [Format: package_name,package_version,");
+ if (useSha256) {
+ pw.print("package_sha256_digest,");
+ }
+ pw.print("content_digest_algorithm:content_digest]:\n");
+ }
+
private int printAllApexs() {
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
+ boolean useSha256 = false;
+ boolean printHeaders = true;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
+ case "--verbose":
verbose = true;
break;
+ case "-o":
+ case "--old":
+ useSha256 = true;
+ break;
+ case "--no-headers":
+ printHeaders = false;
+ break;
default:
pw.println("ERROR: Unknown option: " + opt);
return 1;
@@ -690,23 +725,17 @@
return -1;
}
- if (!verbose) {
- pw.println("APEX Info [Format: package_name,package_version,"
- // TODO(b/259347186): revive via special cmd line option
- //+ "package_sha256_digest,"
- + "content_digest_algorithm:content_digest]:");
+ if (!verbose && printHeaders) {
+ printHeadersHelper("APEX", useSha256, pw);
}
for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
- if (verbose) {
- pw.println("APEX Info [Format: package_name,package_version,"
- // TODO(b/259347186): revive via special cmd line option
- //+ "package_sha256_digest,"
- + "content_digest_algorithm:content_digest]:");
+ if (verbose && printHeaders) {
+ printHeadersHelper("APEX", useSha256, pw);
}
String packageName = packageInfo.packageName;
pw.print(packageName + ","
+ packageInfo.getLongVersionCode() + ",");
- printPackageMeasurements(packageInfo, pw);
+ printPackageMeasurements(packageInfo, useSha256, pw);
if (verbose) {
ModuleInfo moduleInfo;
@@ -718,7 +747,7 @@
pw.println("Is a module: false");
}
- printPackageInstallationInfo(packageInfo, pw);
+ printPackageInstallationInfo(packageInfo, useSha256, pw);
printPackageSignerDetails(packageInfo.signingInfo, pw);
pw.println("");
}
@@ -729,12 +758,22 @@
private int printAllModules() {
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
+ boolean useSha256 = false;
+ boolean printHeaders = true;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
+ case "--verbose":
verbose = true;
break;
+ case "-o":
+ case "--old":
+ useSha256 = true;
+ break;
+ case "--no-headers":
+ printHeaders = false;
+ break;
default:
pw.println("ERROR: Unknown option: " + opt);
return 1;
@@ -747,32 +786,25 @@
return -1;
}
- if (!verbose) {
- pw.println("Module Info [Format: package_name,package_version,"
- // TODO(b/259347186): revive via special cmd line option
- //+ "package_sha256_digest,"
- + "content_digest_algorithm:content_digest]:");
+ if (!verbose && printHeaders) {
+ printHeadersHelper("Module", useSha256, pw);
}
for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
String packageName = module.getPackageName();
- if (verbose) {
- pw.println("Module Info [Format: package_name,package_version,"
- // TODO(b/259347186): revive via special cmd line option
- //+ "package_sha256_digest,"
- + "content_digest_algorithm:content_digest]:");
+ if (verbose && printHeaders) {
+ printHeadersHelper("Module", useSha256, pw);
}
try {
PackageInfo packageInfo = pm.getPackageInfo(packageName,
PackageManager.MATCH_APEX
| PackageManager.GET_SIGNING_CERTIFICATES);
- //pw.print("package:");
pw.print(packageInfo.packageName + ",");
pw.print(packageInfo.getLongVersionCode() + ",");
- printPackageMeasurements(packageInfo, pw);
+ printPackageMeasurements(packageInfo, useSha256, pw);
if (verbose) {
printModuleDetails(module, pw);
- printPackageInstallationInfo(packageInfo, pw);
+ printPackageInstallationInfo(packageInfo, useSha256, pw);
printPackageSignerDetails(packageInfo.signingInfo, pw);
pw.println("");
}
@@ -793,41 +825,45 @@
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
boolean printLibraries = false;
+ boolean useSha256 = false;
+ boolean printHeaders = true;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
+ case "--verbose":
verbose = true;
break;
case "-l":
printLibraries = true;
break;
+ case "-o":
+ case "--old":
+ useSha256 = true;
+ break;
+ case "--no-headers":
+ printHeaders = false;
+ break;
default:
pw.println("ERROR: Unknown option: " + opt);
return 1;
}
}
- if (!verbose) {
- pw.println("MBA Info [Format: package_name,package_version,"
- // TODO(b/259347186): revive via special cmd line option
- //+ "package_sha256_digest,"
- + "content_digest_algorithm:content_digest]:");
+ if (!verbose && printHeaders) {
+ printHeadersHelper("MBA", useSha256, pw);
}
for (PackageInfo packageInfo : getNewlyInstalledMbas()) {
- if (verbose) {
- pw.println("MBA Info [Format: package_name,package_version,"
- // TODO(b/259347186): revive via special cmd line option
- //+ "package_sha256_digest,"
- + "content_digest_algorithm:content_digest]:");
+ if (verbose && printHeaders) {
+ printHeadersHelper("MBA", useSha256, pw);
}
pw.print(packageInfo.packageName + ",");
pw.print(packageInfo.getLongVersionCode() + ",");
- printPackageMeasurements(packageInfo, pw);
+ printPackageMeasurements(packageInfo, useSha256, pw);
if (verbose) {
printAppDetails(packageInfo, printLibraries, pw);
- printPackageInstallationInfo(packageInfo, pw);
+ printPackageInstallationInfo(packageInfo, useSha256, pw);
printPackageSignerDetails(packageInfo.signingInfo, pw);
pw.println("");
}
@@ -894,27 +930,39 @@
private void printHelpMenu() {
final PrintWriter pw = getOutPrintWriter();
pw.println("Transparency manager (transparency) commands:");
- pw.println(" help");
- pw.println(" Print this help text.");
+ pw.println(" help");
+ pw.println(" Print this help text.");
pw.println("");
- pw.println(" get image_info [-a]");
- pw.println(" Print information about loaded image (firmware). Options:");
- pw.println(" -a: lists all other identifiable partitions.");
+ pw.println(" get image_info [-a]");
+ pw.println(" Print information about loaded image (firmware). Options:");
+ pw.println(" -a: lists all other identifiable partitions.");
pw.println("");
- pw.println(" get apex_info [-v]");
- pw.println(" Print information about installed APEXs on device.");
- pw.println(" -v: lists more verbose information about each APEX.");
+ pw.println(" get apex_info [-o] [-v] [--no-headers]");
+ pw.println(" Print information about installed APEXs on device.");
+ pw.println(" -o: also uses the old digest scheme (SHA256) to compute "
+ + "APEX hashes. WARNING: This can be a very slow and CPU-intensive "
+ + "computation.");
+ pw.println(" -v: lists more verbose information about each APEX.");
+ pw.println(" --no-headers: does not print the header if specified");
pw.println("");
- pw.println(" get module_info [-v]");
- pw.println(" Print information about installed modules on device.");
- pw.println(" -v: lists more verbose information about each module.");
+ pw.println(" get module_info [-o] [-v] [--no-headers]");
+ pw.println(" Print information about installed modules on device.");
+ pw.println(" -o: also uses the old digest scheme (SHA256) to compute "
+ + "module hashes. WARNING: This can be a very slow and "
+ + "CPU-intensive computation.");
+ pw.println(" -v: lists more verbose information about each module.");
+ pw.println(" --no-headers: does not print the header if specified");
pw.println("");
- pw.println(" get mba_info [-v] [-l]");
- pw.println(" Print information about installed mobile bundle apps "
+ pw.println(" get mba_info [-o] [-v] [-l] [--no-headers]");
+ pw.println(" Print information about installed mobile bundle apps "
+ "(MBAs on device).");
- pw.println(" -v: lists more verbose information about each app.");
- pw.println(" -l: lists shared library info. This will only be "
- + "listed with -v");
+ pw.println(" -o: also uses the old digest scheme (SHA256) to compute "
+ + "MBA hashes. WARNING: This can be a very slow and CPU-intensive "
+ + "computation.");
+ pw.println(" -v: lists more verbose information about each app.");
+ pw.println(" -l: lists shared library info. (This option only works "
+ + "when -v option is also specified)");
+ pw.println(" --no-headers: does not print the header if specified");
pw.println("");
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 6b6351f..f88f99b 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -672,6 +672,7 @@
private static final int H_COMPLETE_UNLOCK_USER = 14;
private static final int H_VOLUME_STATE_CHANGED = 15;
private static final int H_CLOUD_MEDIA_PROVIDER_CHANGED = 16;
+ private static final int H_SECURE_KEYGUARD_STATE_CHANGED = 17;
class StorageManagerServiceHandler extends Handler {
public StorageManagerServiceHandler(Looper looper) {
@@ -819,6 +820,14 @@
}
break;
}
+ case H_SECURE_KEYGUARD_STATE_CHANGED: {
+ try {
+ mVold.onSecureKeyguardStateChanged((boolean) msg.obj);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ break;
+ }
}
}
}
@@ -1242,12 +1251,12 @@
public void onKeyguardStateChanged(boolean isShowing) {
// Push down current secure keyguard status so that we ignore malicious
// USB devices while locked.
- mSecureKeyguardShowing = isShowing
+ boolean isSecureKeyguardShowing = isShowing
&& mContext.getSystemService(KeyguardManager.class).isDeviceSecure(mCurrentUserId);
- try {
- mVold.onSecureKeyguardStateChanged(mSecureKeyguardShowing);
- } catch (Exception e) {
- Slog.wtf(TAG, e);
+ if (mSecureKeyguardShowing != isSecureKeyguardShowing) {
+ mSecureKeyguardShowing = isSecureKeyguardShowing;
+ mHandler.obtainMessage(H_SECURE_KEYGUARD_STATE_CHANGED, mSecureKeyguardShowing)
+ .sendToTarget();
}
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 0d672bd..35b3db8 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2981,6 +2981,9 @@
void onShortFgsTimeout(ServiceRecord sr) {
synchronized (mAm) {
if (!sr.shouldTriggerShortFgsTimeout()) {
+ if (DEBUG_SHORT_SERVICE) {
+ Slog.d(TAG_SERVICE, "[STALE] Short FGS timed out: " + sr);
+ }
return;
}
Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr);
@@ -3021,6 +3024,9 @@
tr.mLatencyTracker.waitingOnAMSLockEnded();
if (!sr.shouldTriggerShortFgsAnr()) {
+ if (DEBUG_SHORT_SERVICE) {
+ Slog.d(TAG_SERVICE, "[STALE] Short FGS ANR'ed: " + sr);
+ }
return;
}
@@ -7713,4 +7719,35 @@
Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist");
}
}
+
+ private static void getClientPackages(ServiceRecord sr, ArraySet<String> output) {
+ var connections = sr.getConnections();
+ for (int conni = connections.size() - 1; conni >= 0; conni--) {
+ var connl = connections.valueAt(conni);
+ for (int i = 0, size = connl.size(); i < size; i++) {
+ var conn = connl.get(i);
+ if (conn.binding.client != null) {
+ output.add(conn.binding.client.info.packageName);
+ }
+ }
+ }
+ }
+
+ /**
+ * Return all client package names of a service.
+ */
+ ArraySet<String> getClientPackagesLocked(@NonNull String servicePackageName) {
+ var results = new ArraySet<String>();
+ int[] users = mAm.mUserController.getUsers();
+ for (int ui = 0; ui < users.length; ui++) {
+ ArrayMap<ComponentName, ServiceRecord> alls = getServicesLocked(users[ui]);
+ for (int i = 0, size = alls.size(); i < size; i++) {
+ ServiceRecord sr = alls.valueAt(i);
+ if (sr.name.getPackageName().equals(servicePackageName)) {
+ getClientPackages(sr, results);
+ }
+ }
+ }
+ return results;
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index efe14f4..b8a982a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18191,6 +18191,13 @@
mServices.stopForegroundServiceDelegateLocked(connection);
}
}
+
+ @Override
+ public ArraySet<String> getClientPackages(String servicePackageName) {
+ synchronized (ActivityManagerService.this) {
+ return mServices.getClientPackagesLocked(servicePackageName);
+ }
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
@@ -18322,9 +18329,11 @@
}
}
- public void waitForBroadcastBarrier(@Nullable PrintWriter pw) {
+ public void waitForBroadcastBarrier(@Nullable PrintWriter pw, boolean flushBroadcastLoopers) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
- BroadcastLoopers.waitForBarrier(pw);
+ if (flushBroadcastLoopers) {
+ BroadcastLoopers.waitForBarrier(pw);
+ }
for (BroadcastQueue queue : mBroadcastQueues) {
queue.waitForBarrier(pw);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 0b94798..be18c08 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -3131,7 +3131,17 @@
}
int runWaitForBroadcastBarrier(PrintWriter pw) throws RemoteException {
- mInternal.waitForBroadcastBarrier(pw);
+ boolean flushBroadcastLoopers = false;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--flush-broadcast-loopers")) {
+ flushBroadcastLoopers = true;
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ mInternal.waitForBroadcastBarrier(pw, flushBroadcastLoopers);
return 0;
}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 672392d..15d2fa3 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -170,6 +170,8 @@
private int mCountInstrumented;
private int mCountManifest;
+ private boolean mPrioritizeEarliest;
+
private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE;
private @Reason int mRunnableAtReason = REASON_EMPTY;
private boolean mRunnableAtInvalidated;
@@ -605,10 +607,11 @@
final BroadcastRecord nextLPRecord = (BroadcastRecord) nextLPArgs.arg1;
final int nextLPRecordIndex = nextLPArgs.argi1;
final BroadcastRecord nextHPRecord = (BroadcastRecord) highPriorityQueue.peekFirst().arg1;
- final boolean isLPQueueEligible =
- consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit
- && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime
- && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex);
+ final boolean shouldConsiderLPQueue = (mPrioritizeEarliest
+ || consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit);
+ final boolean isLPQueueEligible = shouldConsiderLPQueue
+ && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime
+ && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex);
return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue;
}
@@ -617,6 +620,17 @@
}
/**
+ * When {@code prioritizeEarliest} is set to {@code true}, then earliest enqueued
+ * broadcasts would be prioritized for dispatching, even if there are urgent broadcasts
+ * waiting. This is typically used in case there are callers waiting for "barrier" to be
+ * reached.
+ */
+ @VisibleForTesting
+ void setPrioritizeEarliest(boolean prioritizeEarliest) {
+ mPrioritizeEarliest = prioritizeEarliest;
+ }
+
+ /**
* Returns null if there are no pending broadcasts
*/
@Nullable SomeArgs peekNextBroadcast() {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index e9340e9..6ba1b1f 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -91,6 +91,7 @@
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -1224,6 +1225,17 @@
return didSomething;
}
+ private void forEachQueue(@NonNull Consumer<BroadcastProcessQueue> consumer) {
+ for (int i = 0; i < mProcessQueues.size(); ++i) {
+ BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
+ while (leaf != null) {
+ consumer.accept(leaf);
+ updateRunnableList(leaf);
+ leaf = leaf.processNameNext;
+ }
+ }
+ }
+
@Override
public void start(@NonNull ContentResolver resolver) {
mFgConstants.startObserving(mHandler, resolver);
@@ -1282,12 +1294,19 @@
final CountDownLatch latch = new CountDownLatch(1);
synchronized (mService) {
mWaitingFor.add(Pair.create(condition, latch));
+ forEachQueue(q -> q.setPrioritizeEarliest(true));
}
enqueueUpdateRunningList();
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
+ } finally {
+ synchronized (mService) {
+ if (mWaitingFor.isEmpty()) {
+ forEachQueue(q -> q.setPrioritizeEarliest(false));
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/am/SameProcessApplicationThread.java b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
index a3c0111..62fd6e9 100644
--- a/services/core/java/com/android/server/am/SameProcessApplicationThread.java
+++ b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.app.IApplicationThread;
+import android.app.ReceiverInfo;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -26,6 +27,7 @@
import android.os.Handler;
import android.os.RemoteException;
+import java.util.List;
import java.util.Objects;
/**
@@ -70,4 +72,20 @@
}
});
}
+
+ @Override
+ public void scheduleReceiverList(List<ReceiverInfo> info) {
+ for (int i = 0; i < info.size(); i++) {
+ ReceiverInfo r = info.get(i);
+ if (r.registered) {
+ scheduleRegisteredReceiver(r.receiver, r.intent,
+ r.resultCode, r.data, r.extras, r.ordered, r.sticky,
+ r.sendingUser, r.processState);
+ } else {
+ scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
+ r.resultCode, r.data, r.extras, r.sync,
+ r.sendingUser, r.processState);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index ef195aa..8ac10b8 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -1416,7 +1416,7 @@
|| !mShortFgsInfo.isCurrent()) {
return false;
}
- return mShortFgsInfo.getTimeoutTime() < SystemClock.uptimeMillis();
+ return mShortFgsInfo.getTimeoutTime() <= SystemClock.uptimeMillis();
}
/**
@@ -1431,7 +1431,7 @@
|| !mShortFgsInfo.isCurrent()) {
return false;
}
- return mShortFgsInfo.getAnrTime() < SystemClock.uptimeMillis();
+ return mShortFgsInfo.getAnrTime() <= SystemClock.uptimeMillis();
}
private boolean isAppAlive() {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index cda18b0..46d3ff1 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -524,8 +524,8 @@
private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs = new ArrayMap<>();
// if adding new properties or make any of the below overridable, the method
// copyAndApplyOverride should be updated accordingly
- private boolean mPerfModeOptedIn = false;
- private boolean mBatteryModeOptedIn = false;
+ private boolean mPerfModeOverridden = false;
+ private boolean mBatteryModeOverridden = false;
private boolean mAllowDownscale = true;
private boolean mAllowAngle = true;
private boolean mAllowFpsOverride = true;
@@ -542,8 +542,8 @@
PackageManager.GET_META_DATA, userId);
if (!parseInterventionFromXml(packageManager, ai, packageName)
&& ai.metaData != null) {
- mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
- mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
+ mPerfModeOverridden = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
+ mBatteryModeOverridden = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
}
@@ -595,9 +595,9 @@
} else {
final TypedArray array = resources.obtainAttributes(attributeSet,
com.android.internal.R.styleable.GameModeConfig);
- mPerfModeOptedIn = array.getBoolean(
+ mPerfModeOverridden = array.getBoolean(
GameModeConfig_supportsPerformanceGameMode, false);
- mBatteryModeOptedIn = array.getBoolean(
+ mBatteryModeOverridden = array.getBoolean(
GameModeConfig_supportsBatteryGameMode,
false);
mAllowDownscale = array.getBoolean(GameModeConfig_allowGameDownscaling,
@@ -610,8 +610,8 @@
}
} catch (NameNotFoundException | XmlPullParserException | IOException ex) {
// set flag back to default values when parsing fails
- mPerfModeOptedIn = false;
- mBatteryModeOptedIn = false;
+ mPerfModeOverridden = false;
+ mBatteryModeOverridden = false;
mAllowDownscale = true;
mAllowAngle = true;
mAllowFpsOverride = true;
@@ -667,8 +667,8 @@
GameModeConfiguration(KeyValueListParser parser) {
mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED);
- // isGameModeOptedIn() returns if an app will handle all of the changes necessary
- // for a particular game mode. If so, the Android framework (i.e.
+ // willGamePerformOptimizations() returns if an app will handle all of the changes
+ // necessary for a particular game mode. If so, the Android framework (i.e.
// GameManagerService) will not do anything for the app (like window scaling or
// using ANGLE).
mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode)
@@ -775,8 +775,8 @@
* "com.android.app.gamemode.battery.enabled" with a value of "true"
*/
public boolean willGamePerformOptimizations(@GameMode int gameMode) {
- return (mBatteryModeOptedIn && gameMode == GameManager.GAME_MODE_BATTERY)
- || (mPerfModeOptedIn && gameMode == GameManager.GAME_MODE_PERFORMANCE);
+ return (mBatteryModeOverridden && gameMode == GameManager.GAME_MODE_BATTERY)
+ || (mPerfModeOverridden && gameMode == GameManager.GAME_MODE_PERFORMANCE);
}
private int getAvailableGameModesBitfield() {
@@ -787,10 +787,10 @@
field |= modeToBitmask(mode);
}
}
- if (mBatteryModeOptedIn) {
+ if (mBatteryModeOverridden) {
field |= modeToBitmask(GameManager.GAME_MODE_BATTERY);
}
- if (mPerfModeOptedIn) {
+ if (mPerfModeOverridden) {
field |= modeToBitmask(GameManager.GAME_MODE_PERFORMANCE);
}
return field;
@@ -814,14 +814,14 @@
}
/**
- * Get an array of a package's opted-in game modes.
+ * Get an array of a package's overridden game modes.
*/
- public @GameMode int[] getOptedInGameModes() {
- if (mBatteryModeOptedIn && mPerfModeOptedIn) {
+ public @GameMode int[] getOverriddenGameModes() {
+ if (mBatteryModeOverridden && mPerfModeOverridden) {
return new int[]{GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE};
- } else if (mBatteryModeOptedIn) {
+ } else if (mBatteryModeOverridden) {
return new int[]{GameManager.GAME_MODE_BATTERY};
- } else if (mPerfModeOptedIn) {
+ } else if (mPerfModeOverridden) {
return new int[]{GameManager.GAME_MODE_PERFORMANCE};
} else {
return new int[]{};
@@ -864,18 +864,18 @@
public boolean isActive() {
synchronized (mModeConfigLock) {
- return mModeConfigs.size() > 0 || mBatteryModeOptedIn || mPerfModeOptedIn;
+ return mModeConfigs.size() > 0 || mBatteryModeOverridden || mPerfModeOverridden;
}
}
GamePackageConfiguration copyAndApplyOverride(GamePackageConfiguration overrideConfig) {
GamePackageConfiguration copy = new GamePackageConfiguration(mPackageName);
// if a game mode is overridden, we treat it with the highest priority and reset any
- // opt-in game modes so that interventions are always executed.
- copy.mPerfModeOptedIn = mPerfModeOptedIn && !(overrideConfig != null
+ // overridden game modes so that interventions are always executed.
+ copy.mPerfModeOverridden = mPerfModeOverridden && !(overrideConfig != null
&& overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE)
!= null);
- copy.mBatteryModeOptedIn = mBatteryModeOptedIn && !(overrideConfig != null
+ copy.mBatteryModeOverridden = mBatteryModeOverridden && !(overrideConfig != null
&& overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY)
!= null);
@@ -1092,12 +1092,12 @@
final @GameMode int activeGameMode = getGameModeFromSettingsUnchecked(packageName, userId);
final GamePackageConfiguration config = getConfig(packageName, userId);
if (config != null) {
- final @GameMode int[] optedInGameModes = config.getOptedInGameModes();
+ final @GameMode int[] overriddenGameModes = config.getOverriddenGameModes();
final @GameMode int[] availableGameModes = config.getAvailableGameModes();
GameModeInfo.Builder gameModeInfoBuilder = new GameModeInfo.Builder()
.setActiveGameMode(activeGameMode)
.setAvailableGameModes(availableGameModes)
- .setOptedInGameModes(optedInGameModes)
+ .setOverriddenGameModes(overriddenGameModes)
.setDownscalingAllowed(config.mAllowDownscale)
.setFpsOverrideAllowed(config.mAllowFpsOverride);
for (int gameMode : availableGameModes) {
@@ -2059,7 +2059,7 @@
if (atomTag == FrameworkStatsLog.GAME_MODE_INFO) {
data.add(
FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.GAME_MODE_INFO, uid,
- gameModesToStatsdGameModes(config.getOptedInGameModes()),
+ gameModesToStatsdGameModes(config.getOverriddenGameModes()),
gameModesToStatsdGameModes(config.getAvailableGameModes())));
} else if (atomTag == FrameworkStatsLog.GAME_MODE_CONFIGURATION) {
for (int gameMode : config.getAvailableGameModes()) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index aa8ee3d..58cf7ef 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11427,8 +11427,8 @@
}
public List<AudioRecordingConfiguration> getActiveRecordingConfigurations() {
- final boolean isPrivileged =
- (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
+ final boolean isPrivileged = Binder.getCallingUid() == Process.SYSTEM_UID
+ || (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
android.Manifest.permission.MODIFY_AUDIO_ROUTING));
return mRecordMonitor.getActiveRecordingConfigurations(isPrivileged);
}
diff --git a/services/core/java/com/android/server/audio/TEST_MAPPING b/services/core/java/com/android/server/audio/TEST_MAPPING
index f3a73f0..8fbcb9a 100644
--- a/services/core/java/com/android/server/audio/TEST_MAPPING
+++ b/services/core/java/com/android/server/audio/TEST_MAPPING
@@ -13,6 +13,14 @@
"include-filter": "android.media.audio.cts.SpatializerTest"
}
]
+ },
+ {
+ "name": "AudioPolicyDeathTest",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ }
+ ]
}
]
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index bc9bc03..4fcde97 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3475,6 +3475,8 @@
return;
} else {
mActiveNetwork = null;
+ mUnderlyingNetworkCapabilities = null;
+ mUnderlyingLinkProperties = null;
}
if (mScheduledHandleNetworkLostFuture != null) {
@@ -3664,9 +3666,6 @@
scheduleRetryNewIkeSession();
}
- mUnderlyingNetworkCapabilities = null;
- mUnderlyingLinkProperties = null;
-
// Close all obsolete state, but keep VPN alive incase a usable network comes up.
// (Mirrors VpnService behavior)
Log.d(TAG, "Resetting state for token: " + mCurrentToken);
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 81d782e..0da04a2 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -294,6 +294,9 @@
// Manages Keyboard backlight
private final KeyboardBacklightController mKeyboardBacklightController;
+ // Manages Keyboard modifier keys remapping
+ private final KeyRemapper mKeyRemapper;
+
// Maximum number of milliseconds to wait for input event injection.
private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
@@ -408,6 +411,7 @@
mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
mKeyboardBacklightController = new KeyboardBacklightController(mContext, mNative,
mDataStore, injector.getLooper());
+ mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
mUseDevInputEventForAudioJack =
mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
@@ -536,6 +540,7 @@
mKeyboardLayoutManager.systemRunning();
mBatteryController.systemRunning();
mKeyboardBacklightController.systemRunning();
+ mKeyRemapper.systemRunning();
}
private void reloadDeviceAliases() {
@@ -2738,6 +2743,27 @@
return mKeyboardLayoutManager.getKeyboardLayoutOverlay(identifier);
}
+ @EnforcePermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+ @Override // Binder call
+ public void remapModifierKey(int fromKey, int toKey) {
+ super.remapModifierKey_enforcePermission();
+ mKeyRemapper.remapKey(fromKey, toKey);
+ }
+
+ @EnforcePermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+ @Override // Binder call
+ public void clearAllModifierKeyRemappings() {
+ super.clearAllModifierKeyRemappings_enforcePermission();
+ mKeyRemapper.clearAllKeyRemappings();
+ }
+
+ @EnforcePermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+ @Override // Binder call
+ public Map<Integer, Integer> getModifierKeyRemapping() {
+ super.getModifierKeyRemapping_enforcePermission();
+ return mKeyRemapper.getKeyRemapping();
+ }
+
// Native callback.
@SuppressWarnings("unused")
private String getDeviceAlias(String uniqueId) {
diff --git a/services/core/java/com/android/server/input/KeyRemapper.java b/services/core/java/com/android/server/input/KeyRemapper.java
new file mode 100644
index 0000000..950e094
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyRemapper.java
@@ -0,0 +1,161 @@
+/*
+ * 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.server.input;
+
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.view.InputDevice;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A component of {@link InputManagerService} responsible for managing key remappings.
+ *
+ * @hide
+ */
+final class KeyRemapper implements InputManager.InputDeviceListener {
+
+ private static final int MSG_UPDATE_EXISTING_DEVICES = 1;
+ private static final int MSG_REMAP_KEY = 2;
+ private static final int MSG_CLEAR_ALL_REMAPPING = 3;
+
+ private final Context mContext;
+ private final NativeInputManagerService mNative;
+ // The PersistentDataStore should be locked before use.
+ @GuardedBy("mDataStore")
+ private final PersistentDataStore mDataStore;
+ private final Handler mHandler;
+
+ KeyRemapper(Context context, NativeInputManagerService nativeService,
+ PersistentDataStore dataStore, Looper looper) {
+ mContext = context;
+ mNative = nativeService;
+ mDataStore = dataStore;
+ mHandler = new Handler(looper, this::handleMessage);
+ }
+
+ public void systemRunning() {
+ InputManager inputManager = Objects.requireNonNull(
+ mContext.getSystemService(InputManager.class));
+ inputManager.registerInputDeviceListener(this, mHandler);
+
+ Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES,
+ inputManager.getInputDeviceIds());
+ mHandler.sendMessage(msg);
+ }
+
+ public void remapKey(int fromKey, int toKey) {
+ Message msg = Message.obtain(mHandler, MSG_REMAP_KEY, fromKey, toKey);
+ mHandler.sendMessage(msg);
+ }
+
+ public void clearAllKeyRemappings() {
+ Message msg = Message.obtain(mHandler, MSG_CLEAR_ALL_REMAPPING);
+ mHandler.sendMessage(msg);
+ }
+
+ public Map<Integer, Integer> getKeyRemapping() {
+ synchronized (mDataStore) {
+ return mDataStore.getKeyRemapping();
+ }
+ }
+
+ private void addKeyRemapping(int fromKey, int toKey) {
+ InputManager inputManager = Objects.requireNonNull(
+ mContext.getSystemService(InputManager.class));
+ for (int deviceId : inputManager.getInputDeviceIds()) {
+ InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+ if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {
+ mNative.addKeyRemapping(deviceId, fromKey, toKey);
+ }
+ }
+ }
+
+ private void remapKeyInternal(int fromKey, int toKey) {
+ addKeyRemapping(fromKey, toKey);
+ synchronized (mDataStore) {
+ try {
+ if (fromKey == toKey) {
+ mDataStore.clearMappedKey(fromKey);
+ } else {
+ mDataStore.remapKey(fromKey, toKey);
+ }
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
+ }
+ }
+
+ private void clearAllRemappingsInternal() {
+ synchronized (mDataStore) {
+ try {
+ Map<Integer, Integer> keyRemapping = mDataStore.getKeyRemapping();
+ for (int fromKey : keyRemapping.keySet()) {
+ mDataStore.clearMappedKey(fromKey);
+
+ // Remapping to itself will clear the remapping on native side
+ addKeyRemapping(fromKey, fromKey);
+ }
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
+ }
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ InputManager inputManager = Objects.requireNonNull(
+ mContext.getSystemService(InputManager.class));
+ InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+ if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {
+ Map<Integer, Integer> remapping = getKeyRemapping();
+ remapping.forEach(
+ (fromKey, toKey) -> mNative.addKeyRemapping(deviceId, fromKey, toKey));
+ }
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ }
+
+ private boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE_EXISTING_DEVICES:
+ for (int deviceId : (int[]) msg.obj) {
+ onInputDeviceAdded(deviceId);
+ }
+ return true;
+ case MSG_REMAP_KEY:
+ remapKeyInternal(msg.arg1, msg.arg2);
+ return true;
+ case MSG_CLEAR_ALL_REMAPPING:
+ clearAllRemappingsInternal();
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index cfa7fb1..8781c6e 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -47,6 +47,8 @@
int getSwitchState(int deviceId, int sourceMask, int sw);
+ void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode);
+
boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode);
@@ -235,6 +237,9 @@
public native int getSwitchState(int deviceId, int sourceMask, int sw);
@Override
+ public native void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode);
+
+ @Override
public native boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes,
boolean[] keyExists);
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index 1bb10c7..375377a7 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -27,14 +27,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParserException;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -64,6 +63,8 @@
final class PersistentDataStore {
static final String TAG = "InputManager";
+ private static final int INVALID_VALUE = -1;
+
// Input device state by descriptor.
private final HashMap<String, InputDeviceState> mInputDevices =
new HashMap<String, InputDeviceState>();
@@ -77,6 +78,9 @@
// True if there are changes to be saved.
private boolean mDirty;
+ // Storing key remapping
+ private Map<Integer, Integer> mKeyRemapping = new HashMap<>();
+
public PersistentDataStore() {
this(new Injector());
}
@@ -187,6 +191,30 @@
return state.getKeyboardBacklightBrightness(lightId);
}
+ public boolean remapKey(int fromKey, int toKey) {
+ loadIfNeeded();
+ if (mKeyRemapping.getOrDefault(fromKey, INVALID_VALUE) == toKey) {
+ return false;
+ }
+ mKeyRemapping.put(fromKey, toKey);
+ setDirty();
+ return true;
+ }
+
+ public boolean clearMappedKey(int key) {
+ loadIfNeeded();
+ if (mKeyRemapping.containsKey(key)) {
+ mKeyRemapping.remove(key);
+ setDirty();
+ }
+ return true;
+ }
+
+ public Map<Integer, Integer> getKeyRemapping() {
+ loadIfNeeded();
+ return new HashMap<>(mKeyRemapping);
+ }
+
public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
boolean changed = false;
for (InputDeviceState state : mInputDevices.values()) {
@@ -229,6 +257,7 @@
}
private void clearState() {
+ mKeyRemapping.clear();
mInputDevices.clear();
}
@@ -280,7 +309,9 @@
XmlUtils.beginDocument(parser, "input-manager-state");
final int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- if (parser.getName().equals("input-devices")) {
+ if (parser.getName().equals("key-remapping")) {
+ loadKeyRemappingFromXml(parser);
+ } else if (parser.getName().equals("input-devices")) {
loadInputDevicesFromXml(parser);
}
}
@@ -307,10 +338,31 @@
}
}
+ private void loadKeyRemappingFromXml(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (parser.getName().equals("remap")) {
+ int fromKey = parser.getAttributeInt(null, "from-key");
+ int toKey = parser.getAttributeInt(null, "to-key");
+ mKeyRemapping.put(fromKey, toKey);
+ }
+ }
+ }
+
private void saveToXml(TypedXmlSerializer serializer) throws IOException {
serializer.startDocument(null, true);
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
serializer.startTag(null, "input-manager-state");
+ serializer.startTag(null, "key-remapping");
+ for (int fromKey : mKeyRemapping.keySet()) {
+ int toKey = mKeyRemapping.get(fromKey);
+ serializer.startTag(null, "remap");
+ serializer.attributeInt(null, "from-key", fromKey);
+ serializer.attributeInt(null, "to-key", toKey);
+ serializer.endTag(null, "remap");
+ }
+ serializer.endTag(null, "key-remapping");
serializer.startTag(null, "input-devices");
for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
final String descriptor = entry.getKey();
@@ -329,7 +381,6 @@
private static final String[] CALIBRATION_NAME = { "x_scale",
"x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
- private static final int INVALID_VALUE = -1;
private final TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
@Nullable
private String mCurrentKeyboardLayout;
diff --git a/services/core/java/com/android/server/pm/AppStateHelper.java b/services/core/java/com/android/server/pm/AppStateHelper.java
index 9ea350f..e6e8212a 100644
--- a/services/core/java/com/android/server/pm/AppStateHelper.java
+++ b/services/core/java/com/android/server/pm/AppStateHelper.java
@@ -16,15 +16,22 @@
package com.android.server.pm;
+import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
+import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING;
+
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.ActivityManagerInternal;
import android.content.Context;
+import android.media.AudioManager;
import android.media.IAudioService;
import android.os.ServiceManager;
+import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.ArraySet;
import com.android.internal.util.ArrayUtils;
+import com.android.server.LocalServices;
import java.util.ArrayList;
import java.util.List;
@@ -72,6 +79,43 @@
}
/**
+ * True if any app is using voice communication.
+ */
+ private boolean hasVoiceCall() {
+ var am = mContext.getSystemService(AudioManager.class);
+ try {
+ for (var apc : am.getActivePlaybackConfigurations()) {
+ if (!apc.isActive()) {
+ continue;
+ }
+ var usage = apc.getAudioAttributes().getUsage();
+ if (usage == USAGE_VOICE_COMMUNICATION
+ || usage == USAGE_VOICE_COMMUNICATION_SIGNALLING) {
+ return true;
+ }
+ }
+ } catch (Exception ignore) {
+ }
+ return false;
+ }
+
+ /**
+ * True if the app is recording audio.
+ */
+ private boolean isRecordingAudio(String packageName) {
+ var am = mContext.getSystemService(AudioManager.class);
+ try {
+ for (var arc : am.getActiveRecordingConfigurations()) {
+ if (TextUtils.equals(arc.getClientPackageName(), packageName)) {
+ return true;
+ }
+ }
+ } catch (Exception ignore) {
+ }
+ return false;
+ }
+
+ /**
* True if the app is in the foreground.
*/
private boolean isAppForeground(String packageName) {
@@ -89,8 +133,7 @@
* True if the app is playing/recording audio.
*/
private boolean hasActiveAudio(String packageName) {
- // TODO(b/235306967): also check recording
- return hasAudioFocus(packageName);
+ return hasAudioFocus(packageName) || isRecordingAudio(packageName);
}
/**
@@ -143,16 +186,16 @@
* True if there is an ongoing phone call.
*/
public boolean isInCall() {
- // To be implemented
- return false;
+ // TelecomManager doesn't handle the case where some apps don't implement ConnectionService.
+ // We check apps using voice communication to detect if the device is in call.
+ var tm = mContext.getSystemService(TelecomManager.class);
+ return tm.isInCall() || hasVoiceCall();
}
/**
* Returns a list of packages which depend on {@code packageNames}. These are the packages
* that will be affected when updating {@code packageNames} and should participate in
* the evaluation of install constraints.
- *
- * TODO(b/235306967): Also include bounded services as dependency.
*/
public List<String> getDependencyPackages(List<String> packageNames) {
var results = new ArraySet<String>();
@@ -167,6 +210,10 @@
}
}
}
+ var amInternal = LocalServices.getService(ActivityManagerInternal.class);
+ for (var packageName : packageNames) {
+ results.addAll(amInternal.getClientPackages(packageName));
+ }
return new ArrayList<>(results);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 409d352..4803c5e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -154,6 +154,11 @@
/** Destroy sessions older than this on storage free request */
private static final long MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS = 8 * DateUtils.HOUR_IN_MILLIS;
+ /** Threshold of historical sessions size */
+ private static final int HISTORICAL_SESSIONS_THRESHOLD = 500;
+ /** Size of historical sessions to be cleared when reaching threshold */
+ private static final int HISTORICAL_CLEAR_SIZE = 400;
+
/**
* Allow verification-skipping if it's a development app installed through ADB with
* disable verification flag specified.
@@ -549,6 +554,10 @@
CharArrayWriter writer = new CharArrayWriter();
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
session.dump(pw);
+ if (mHistoricalSessions.size() > HISTORICAL_SESSIONS_THRESHOLD) {
+ Slog.d(TAG, "Historical sessions size reaches threshold, clear the oldest");
+ mHistoricalSessions.subList(0, HISTORICAL_CLEAR_SIZE).clear();
+ }
mHistoricalSessions.add(writer.toString());
int installerUid = session.getInstallerUid();
diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java
index fad61b8..d163d3d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerLocal.java
+++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java
@@ -30,7 +30,6 @@
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
/**
* In-process API for server side PackageManager related infrastructure.
@@ -177,8 +176,6 @@
@NonNull
Map<String, PackageState> getPackageStates();
- void forAllPackageStates(@NonNull Consumer<PackageState> consumer);
-
@Override
void close();
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index bc9f7b2..89b74f4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -369,6 +369,8 @@
// Current settings file.
private final File mSettingsFilename;
+ // Compressed current settings file.
+ private final File mCompressedSettingsFilename;
// Previous settings file.
// Removed when the current settings file successfully stored.
private final File mPreviousSettingsFilename;
@@ -639,6 +641,7 @@
mRuntimePermissionsPersistence = null;
mPermissionDataProvider = null;
mSettingsFilename = null;
+ mCompressedSettingsFilename = null;
mPreviousSettingsFilename = null;
mPackageListFilename = null;
mStoppedPackagesFilename = null;
@@ -710,6 +713,7 @@
|FileUtils.S_IROTH|FileUtils.S_IXOTH,
-1, -1);
mSettingsFilename = new File(mSystemDir, "packages.xml");
+ mCompressedSettingsFilename = new File(mSystemDir, "packages.compressed");
mPreviousSettingsFilename = new File(mSystemDir, "packages-backup.xml");
mPackageListFilename = new File(mSystemDir, "packages.list");
FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
@@ -751,6 +755,7 @@
mLock = null;
mRuntimePermissionsPersistence = r.mRuntimePermissionsPersistence;
mSettingsFilename = null;
+ mCompressedSettingsFilename = null;
mPreviousSettingsFilename = null;
mPackageListFilename = null;
mStoppedPackagesFilename = null;
@@ -2465,7 +2470,7 @@
mReadMessages.append("Reading from backup stopped packages file\n");
PackageManagerService.reportSettingsProblem(Log.INFO,
"Need to read from backup stopped packages file");
- if (mSettingsFilename.exists()) {
+ if (mStoppedPackagesFilename.exists()) {
// If both the backup and normal file exist, we
// ignore the normal one since it might have been
// corrupted.
@@ -2588,6 +2593,8 @@
Slog.w(PackageManagerService.TAG, "Preserving older settings backup");
}
}
+ // Compressed settings are not valid anymore.
+ mCompressedSettingsFilename.delete();
mPastSignatures.clear();
@@ -2677,10 +2684,30 @@
mPreviousSettingsFilename.delete();
FileUtils.setPermissions(mSettingsFilename.toString(),
- FileUtils.S_IRUSR|FileUtils.S_IWUSR
- |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
+ FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
-1, -1);
+ final FileInputStream fis = new FileInputStream(mSettingsFilename);
+ final AtomicFile compressed = new AtomicFile(mCompressedSettingsFilename);
+ final FileOutputStream fos = compressed.startWrite();
+
+ BackgroundThread.getHandler().post(() -> {
+ try {
+ if (!nativeCompressLz4(fis.getFD().getInt$(), fos.getFD().getInt$())) {
+ throw new IOException("Failed to compress");
+ }
+ compressed.finishWrite(fos);
+ FileUtils.setPermissions(mCompressedSettingsFilename.toString(),
+ FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP
+ | FileUtils.S_IWGRP, -1, -1);
+ } catch (IOException e) {
+ Slog.e(PackageManagerService.TAG, "Failed to write compressed settings file: "
+ + mCompressedSettingsFilename, e);
+ compressed.delete();
+ }
+ IoUtils.closeQuietly(fis);
+ });
+
writeKernelMappingLPr();
writePackageListLPr();
writeAllUsersPackageRestrictionsLPr(sync);
@@ -2703,6 +2730,8 @@
//Debug.stopMethodTracing();
}
+ private native boolean nativeCompressLz4(int inputFd, int outputFd);
+
private void writeKernelRemoveUserLPr(int userId) {
if (mKernelMappingFilename == null) return;
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 0362ddd..4fddc9c 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1470,9 +1470,15 @@
}
// Then make sure none of the activities have more than the max number of shortcuts.
+ int total = 0;
for (int i = counts.size() - 1; i >= 0; i--) {
- service.enforceMaxActivityShortcuts(counts.valueAt(i));
+ int count = counts.valueAt(i);
+ service.enforceMaxActivityShortcuts(count);
+ total += count;
}
+
+ // Finally make sure that the app doesn't have more than the max number of shortcuts.
+ service.enforceMaxAppShortcuts(total);
}
/**
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 83720f1..12a33ee 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -181,6 +181,9 @@
static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15;
@VisibleForTesting
+ static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 60;
+
+ @VisibleForTesting
static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
@VisibleForTesting
@@ -257,6 +260,11 @@
String KEY_MAX_SHORTCUTS = "max_shortcuts";
/**
+ * Key name for the max dynamic shortcuts per app. (int)
+ */
+ String KEY_MAX_SHORTCUTS_PER_APP = "max_shortcuts_per_app";
+
+ /**
* Key name for icon compression quality, 0-100.
*/
String KEY_ICON_QUALITY = "icon_quality";
@@ -329,9 +337,14 @@
new SparseArray<>();
/**
+ * Max number of dynamic + manifest shortcuts that each activity can have at a time.
+ */
+ private int mMaxShortcutsPerActivity;
+
+ /**
* Max number of dynamic + manifest shortcuts that each application can have at a time.
*/
- private int mMaxShortcuts;
+ private int mMaxShortcutsPerApp;
/**
* Max number of updating API calls that each application can make during the interval.
@@ -804,9 +817,12 @@
mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong(
ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL));
- mMaxShortcuts = Math.max(0, (int) parser.getLong(
+ mMaxShortcutsPerActivity = Math.max(0, (int) parser.getLong(
ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY));
+ mMaxShortcutsPerApp = Math.max(0, (int) parser.getLong(
+ ConfigConstants.KEY_MAX_SHORTCUTS_PER_APP, DEFAULT_MAX_SHORTCUTS_PER_APP));
+
final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
? (int) parser.getLong(
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
@@ -1746,16 +1762,33 @@
* {@link #getMaxActivityShortcuts()}.
*/
void enforceMaxActivityShortcuts(int numShortcuts) {
- if (numShortcuts > mMaxShortcuts) {
+ if (numShortcuts > mMaxShortcutsPerActivity) {
throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
}
}
/**
+ * @throws IllegalArgumentException if {@code numShortcuts} is bigger than
+ * {@link #getMaxAppShortcuts()}.
+ */
+ void enforceMaxAppShortcuts(int numShortcuts) {
+ if (numShortcuts > mMaxShortcutsPerApp) {
+ throw new IllegalArgumentException("Max number of dynamic shortcuts per app exceeded");
+ }
+ }
+
+ /**
* Return the max number of dynamic + manifest shortcuts for each launcher icon.
*/
int getMaxActivityShortcuts() {
- return mMaxShortcuts;
+ return mMaxShortcutsPerActivity;
+ }
+
+ /**
+ * Return the max number of dynamic + manifest shortcuts for each launcher icon.
+ */
+ int getMaxAppShortcuts() {
+ return mMaxShortcutsPerApp;
}
/**
@@ -2188,6 +2221,8 @@
ps.ensureNotImmutable(shortcut.getId(), /*ignoreInvisible=*/ true);
fillInDefaultActivity(Arrays.asList(shortcut));
+ enforceMaxAppShortcuts(ps.getShortcutCount());
+
if (!shortcut.hasRank()) {
shortcut.setRank(0);
}
@@ -2575,7 +2610,7 @@
throws RemoteException {
verifyCaller(packageName, userId);
- return mMaxShortcuts;
+ return mMaxShortcutsPerActivity;
}
@Override
@@ -4724,7 +4759,7 @@
pw.print(" maxUpdatesPerInterval: ");
pw.println(mMaxUpdatesPerInterval);
pw.print(" maxShortcutsPerActivity: ");
- pw.println(mMaxShortcuts);
+ pw.println(mMaxShortcutsPerActivity);
pw.println();
mStatLogger.dump(pw, " ");
@@ -5211,7 +5246,7 @@
@VisibleForTesting
int getMaxShortcutsForTest() {
- return mMaxShortcuts;
+ return mMaxShortcutsPerActivity;
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index 024b63e..f8e1547 100644
--- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -34,7 +34,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
/** @hide */
public class PackageManagerLocalImpl implements PackageManagerLocal {
@@ -198,18 +197,5 @@
return mFilteredPackageStates;
}
-
- @Override
- public void forAllPackageStates(@NonNull Consumer<PackageState> consumer) {
- checkClosed();
-
- var packageStates = mSnapshot.getPackageStates();
- for (int index = 0, size = packageStates.size(); index < size; index++) {
- var packageState = packageStates.valueAt(index);
- if (!mSnapshot.shouldFilterApplication(packageState, mCallingUid, mUserId)) {
- consumer.accept(packageState);
- }
- }
- }
}
}
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index 69e7bf1..165c52d 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -322,6 +322,10 @@
return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_COMPANION) != 0;
}
+ public boolean isModule() {
+ return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_MODULE) != 0;
+ }
+
public boolean isRetailDemo() {
return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO) != 0;
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index e56edeb..2a2bcab 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -238,6 +238,8 @@
NOTIFICATION_PERMISSIONS.add(Manifest.permission.POST_NOTIFICATIONS);
}
+ @NonNull private final ApexManager mApexManager;
+
/** Set of source package names for Privileged Permission Allowlist */
private final ArraySet<String> mPrivilegedPermissionAllowlistSourcePackageNames =
new ArraySet<>();
@@ -421,6 +423,7 @@
mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
mIsLeanback = availableFeatures.containsKey(PackageManager.FEATURE_LEANBACK);
+ mApexManager = ApexManager.getInstance();
mPrivilegedPermissionAllowlistSourcePackageNames.add(PLATFORM_PACKAGE_NAME);
// PackageManager.hasSystemFeature() is not used here because PackageManagerService
@@ -3309,9 +3312,8 @@
return true;
}
final String permissionName = permission.getName();
- final ApexManager apexManager = ApexManager.getInstance();
final String containingApexPackageName =
- apexManager.getActiveApexPackageNameContainingPackage(packageName);
+ mApexManager.getActiveApexPackageNameContainingPackage(packageName);
if (isInSystemConfigPrivAppPermissions(pkg, permissionName,
containingApexPackageName)) {
return true;
@@ -3365,8 +3367,7 @@
} else if (pkg.isSystemExt()) {
permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
} else if (containingApexPackageName != null) {
- final ApexManager apexManager = ApexManager.getInstance();
- final String apexName = apexManager.getApexModuleNameForPackageName(
+ final String apexName = mApexManager.getApexModuleNameForPackageName(
containingApexPackageName);
final Set<String> privAppPermissions = systemConfig.getPrivAppPermissions(
pkg.getPackageName());
@@ -3582,6 +3583,11 @@
// Special permission for the recents app.
allowed = true;
}
+ if (!allowed && bp.isModule() && mApexManager.getActiveApexPackageNameContainingPackage(
+ pkg.getPackageName()) != null) {
+ // Special permission granted for APKs inside APEX modules.
+ allowed = true;
+ }
return allowed;
}
diff --git a/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java b/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
index b2080b2..90a0c7c 100644
--- a/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
+++ b/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
@@ -16,7 +16,6 @@
package com.android.server.pm.snapshot;
-import android.annotation.SystemApi;
import android.content.pm.PackageManagerInternal;
import com.android.server.pm.Computer;
@@ -32,6 +31,5 @@
*
* @hide
*/
-@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public interface PackageDataSnapshot {
}
diff --git a/services/core/java/com/android/server/security/rkp/OWNERS b/services/core/java/com/android/server/security/rkp/OWNERS
new file mode 100644
index 0000000..348f940
--- /dev/null
+++ b/services/core/java/com/android/server/security/rkp/OWNERS
@@ -0,0 +1 @@
+file:platform/frameworks/base:master:/core/java/android/security/rkp/OWNERS
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
new file mode 100644
index 0000000..65a4b38
--- /dev/null
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
@@ -0,0 +1,126 @@
+/*
+ * 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.server.security.rkp;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.security.rkp.IGetKeyCallback;
+import android.security.rkp.IGetRegistrationCallback;
+import android.security.rkp.IRegistration;
+import android.security.rkp.IRemoteProvisioning;
+import android.security.rkp.service.RegistrationProxy;
+import android.util.Log;
+
+import com.android.server.SystemService;
+
+import java.time.Duration;
+
+/**
+ * Implements the remote provisioning system service. This service is backed by a mainline
+ * module, allowing the underlying implementation to be updated. The code here is a thin
+ * proxy for the code in android.security.rkp.service.
+ *
+ * @hide
+ */
+public class RemoteProvisioningService extends SystemService {
+ public static final String TAG = "RemoteProvisionSysSvc";
+ private static final Duration CREATE_REGISTRATION_TIMEOUT = Duration.ofSeconds(10);
+ private final RemoteProvisioningImpl mBinderImpl = new RemoteProvisioningImpl();
+
+ /** @hide */
+ public RemoteProvisioningService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.REMOTE_PROVISIONING_SERVICE, mBinderImpl);
+ }
+
+ private final class RemoteProvisioningImpl extends IRemoteProvisioning.Stub {
+
+ final class RegistrationBinder extends IRegistration.Stub {
+ static final String TAG = RemoteProvisioningService.TAG;
+ private final RegistrationProxy mRegistration;
+
+ RegistrationBinder(RegistrationProxy registration) {
+ mRegistration = registration;
+ }
+
+ @Override
+ public void getKey(int keyId, IGetKeyCallback callback) {
+ Log.e(TAG, "RegistrationBinder.getKey NOT YET IMPLEMENTED");
+ }
+
+ @Override
+ public void cancelGetKey(IGetKeyCallback callback) {
+ Log.e(TAG, "RegistrationBinder.cancelGetKey NOT YET IMPLEMENTED");
+ }
+
+ @Override
+ public void storeUpgradedKey(byte[] oldKeyBlob, byte[] newKeyBlob) {
+ Log.e(TAG, "RegistrationBinder.storeUpgradedKey NOT YET IMPLEMENTED");
+ }
+ }
+
+ @Override
+ public void getRegistration(String irpcName, IGetRegistrationCallback callback)
+ throws RemoteException {
+ final int callerUid = Binder.getCallingUidOrThrow();
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ Log.i(TAG, "getRegistration(" + irpcName + ")");
+ RegistrationProxy.createAsync(
+ getContext(),
+ callerUid,
+ irpcName,
+ CREATE_REGISTRATION_TIMEOUT,
+ getContext().getMainExecutor(),
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(RegistrationProxy registration) {
+ try {
+ callback.onSuccess(new RegistrationBinder(registration));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling success callback", e);
+ }
+ }
+
+ @Override
+ public void onError(Exception error) {
+ try {
+ callback.onError(error.toString());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling error callback", e);
+ }
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Override
+ public void cancelGetRegistration(IGetRegistrationCallback callback)
+ throws RemoteException {
+ Log.i(TAG, "cancelGetRegistration()");
+ callback.onError("cancelGetRegistration not yet implemented");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 707d0044..e145898 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -38,6 +38,7 @@
import android.util.Slog;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.infra.AbstractMasterSystemService;
@@ -161,7 +162,7 @@
return null;
}
- // Used in testing.
+ @VisibleForTesting
void provideDataStream(@UserIdInt int userId, ParcelFileDescriptor parcelFileDescriptor,
RemoteCallback callback) {
synchronized (mLock) {
@@ -174,7 +175,7 @@
}
}
- // Used in testing.
+ @VisibleForTesting
void provideData(@UserIdInt int userId, PersistableBundle data, SharedMemory sharedMemory,
RemoteCallback callback) {
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index b16602e..14131e6 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -235,6 +235,7 @@
// We don't have an application callback, let's find the destination of the back gesture
// The search logic should align with ActivityClientController#finishActivity
prevActivity = currentTask.topRunningActivity(currentActivity.token, INVALID_TASK_ID);
+ final boolean isOccluded = isKeyguardOccluded(window);
// TODO Dialog window does not need to attach on activity, check
// window.mAttrs.type != TYPE_BASE_APPLICATION
if ((window.getParent().getChildCount() > 1
@@ -244,16 +245,24 @@
backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
removedWindowContainer = window;
} else if (prevActivity != null) {
- // We have another Activity in the same currentTask to go to
- backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
- removedWindowContainer = currentActivity;
- prevTask = prevActivity.getTask();
+ if (!isOccluded || prevActivity.canShowWhenLocked()) {
+ // We have another Activity in the same currentTask to go to
+ backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
+ removedWindowContainer = currentActivity;
+ prevTask = prevActivity.getTask();
+ } else {
+ backType = BackNavigationInfo.TYPE_CALLBACK;
+ }
} else if (currentTask.returnsToHomeRootTask()) {
- // Our Task should bring back to home
- removedWindowContainer = currentTask;
- prevTask = currentTask.getDisplayArea().getRootHomeTask();
- backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
- mShowWallpaper = true;
+ if (isOccluded) {
+ backType = BackNavigationInfo.TYPE_CALLBACK;
+ } else {
+ // Our Task should bring back to home
+ removedWindowContainer = currentTask;
+ prevTask = currentTask.getDisplayArea().getRootHomeTask();
+ backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+ mShowWallpaper = true;
+ }
} else if (currentActivity.isRootOfTask()) {
// TODO(208789724): Create single source of truth for this, maybe in
// RootWindowContainer
@@ -267,7 +276,9 @@
backType = BackNavigationInfo.TYPE_CALLBACK;
} else {
prevActivity = prevTask.getTopNonFinishingActivity();
- if (prevTask.isActivityTypeHome()) {
+ if (prevActivity == null || (isOccluded && !prevActivity.canShowWhenLocked())) {
+ backType = BackNavigationInfo.TYPE_CALLBACK;
+ } else if (prevTask.isActivityTypeHome()) {
backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
mShowWallpaper = true;
} else {
@@ -323,6 +334,12 @@
return mAnimationTargets.mComposed && mAnimationTargets.mWaitTransition;
}
+ boolean isKeyguardOccluded(WindowState focusWindow) {
+ final KeyguardController kc = mWindowManagerService.mAtmService.mKeyguardController;
+ final int displayId = focusWindow.getDisplayId();
+ return kc.isKeyguardLocked(displayId) && kc.isDisplayOccluded(displayId);
+ }
+
// For legacy transition.
/**
* Once we find the transition targets match back animation targets, remove the target from
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index b419f36..300deca 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2128,15 +2128,10 @@
}
void updateSystemBarAttributes() {
- WindowState winCandidate = mFocusedWindow;
- if (winCandidate == null && mTopFullscreenOpaqueWindowState != null
- && (mTopFullscreenOpaqueWindowState.mAttrs.flags
- & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0) {
- // Only focusable window can take system bar control.
- winCandidate = mTopFullscreenOpaqueWindowState;
- }
// If there is no window focused, there will be nobody to handle the events
// anyway, so just hang on in whatever state we're in until things settle down.
+ WindowState winCandidate = mFocusedWindow != null ? mFocusedWindow
+ : mTopFullscreenOpaqueWindowState;
if (winCandidate == null) {
return;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a74a4b0..6d4a526 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1591,15 +1591,22 @@
removeChild(r, reason);
});
} else {
+ final ArrayList<ActivityRecord> finishingActivities = new ArrayList<>();
+ forAllActivities(r -> {
+ if (r.finishing || (excludingTaskOverlay && r.isTaskOverlay())) {
+ return;
+ }
+ finishingActivities.add(r);
+ });
+
// Finish or destroy apps from the bottom to ensure that all the other activity have
// been finished and the top task in another task gets resumed when a top activity is
// removed. Otherwise, the next top activity could be started while the top activity
// is removed, which is not necessary since the next top activity is on the same Task
// and should also be removed.
- forAllActivities((r) -> {
- if (r.finishing || (excludingTaskOverlay && r.isTaskOverlay())) {
- return;
- }
+ for (int i = finishingActivities.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = finishingActivities.get(i);
+
// Prevent the transition from being executed too early if the top activity is
// resumed but the mVisibleRequested of any other activity is true, the transition
// should wait until next activity resumed.
@@ -1609,7 +1616,7 @@
} else {
r.destroyIfPossible(reason);
}
- }, false /* traverseTopToBottom */);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c6578ef..1b7bd9e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2090,7 +2090,10 @@
} else {
final Task task = getTask();
final boolean canFromTask = task != null && task.canAffectSystemUiFlags();
- return canFromTask && mActivityRecord.isVisible();
+ return canFromTask && mActivityRecord.isVisible()
+ // Do not let snapshot window control the bar
+ && (mAttrs.type != TYPE_APPLICATION_STARTING
+ || !(mStartingData instanceof SnapshotStartingData));
}
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index e661688..1bcf4d8 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -71,6 +71,7 @@
"com_android_server_PersistentDataBlockService.cpp",
"com_android_server_am_LowMemDetector.cpp",
"com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
+ "com_android_server_pm_Settings.cpp",
"com_android_server_sensor_SensorService.cpp",
"com_android_server_wm_TaskFpsCallbackController.cpp",
"onload.cpp",
@@ -152,6 +153,7 @@
"libpsi",
"libdataloader",
"libincfs",
+ "liblz4",
"android.hardware.audio.common@2.0",
"android.media.audio.common.types-V1-ndk",
"android.hardware.broadcastradio@1.0",
@@ -167,7 +169,7 @@
"android.hardware.graphics.bufferqueue@2.0",
"android.hardware.graphics.common@1.2",
"android.hardware.graphics.mapper@4.0",
- "android.hardware.input.processor-V1-ndk",
+ "android.hardware.input.processor-V2-ndk",
"android.hardware.ir@1.0",
"android.hardware.light@2.0",
"android.hardware.memtrack-V1-ndk",
@@ -232,3 +234,26 @@
"com_android_server_app_GameManagerService.cpp",
],
}
+
+// Settings JNI library for unit tests.
+cc_library_shared {
+ name: "libservices.core.settings.testonly",
+ defaults: ["libservices.core-libs"],
+
+ cpp_std: "c++2a",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wthread-safety",
+ ],
+
+ srcs: [
+ "com_android_server_pm_Settings.cpp",
+ "onload_settings.cpp",
+ ],
+
+ header_libs: [
+ "bionic_libc_platform_headers",
+ ],
+}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 969056e..145e088 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1578,6 +1578,12 @@
return vec;
}
+static void nativeAddKeyRemapping(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+ jint fromKeyCode, jint toKeyCode) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->getInputManager()->getReader().addKeyRemapping(deviceId, fromKeyCode, toKeyCode);
+}
+
static jboolean nativeHasKeys(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint sourceMask,
jintArray keyCodes, jbooleanArray outFlags) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2360,6 +2366,7 @@
{"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState},
{"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState},
{"getSwitchState", "(III)I", (void*)nativeGetSwitchState},
+ {"addKeyRemapping", "(III)V", (void*)nativeAddKeyRemapping},
{"hasKeys", "(II[I[Z)Z", (void*)nativeHasKeys},
{"getKeyCodeForKeyLocation", "(II)I", (void*)nativeGetKeyCodeForKeyLocation},
{"createInputChannel", "(Ljava/lang/String;)Landroid/view/InputChannel;",
diff --git a/services/core/jni/com_android_server_pm_Settings.cpp b/services/core/jni/com_android_server_pm_Settings.cpp
new file mode 100644
index 0000000..9633a11
--- /dev/null
+++ b/services/core/jni/com_android_server_pm_Settings.cpp
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_ADB
+#define LOG_TAG "Settings-jni"
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/no_destructor.h>
+#include <core_jni_helpers.h>
+#include <lz4frame.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <vector>
+
+namespace android {
+
+namespace {
+
+struct LZ4FCContextDeleter {
+ void operator()(LZ4F_cctx* cctx) { LZ4F_freeCompressionContext(cctx); }
+};
+
+static constexpr int LZ4_BUFFER_SIZE = 64 * 1024;
+
+static bool writeToFile(std::vector<char>& outBuffer, int fdOut) {
+ if (!android::base::WriteFully(fdOut, outBuffer.data(), outBuffer.size())) {
+ PLOG(ERROR) << "Error to write to output file";
+ return false;
+ }
+ outBuffer.clear();
+ return true;
+}
+
+static bool compressAndWriteLz4(LZ4F_cctx* context, std::vector<char>& inBuffer,
+ std::vector<char>& outBuffer, int fdOut) {
+ auto inSize = inBuffer.size();
+ if (inSize > 0) {
+ auto prvSize = outBuffer.size();
+ auto outSize = LZ4F_compressBound(inSize, nullptr);
+ outBuffer.resize(prvSize + outSize);
+ auto rc = LZ4F_compressUpdate(context, outBuffer.data() + prvSize, outSize, inBuffer.data(),
+ inSize, nullptr);
+ if (LZ4F_isError(rc)) {
+ LOG(ERROR) << "LZ4F_compressUpdate failed: " << LZ4F_getErrorName(rc);
+ return false;
+ }
+ outBuffer.resize(prvSize + rc);
+ }
+
+ if (outBuffer.size() > LZ4_BUFFER_SIZE) {
+ return writeToFile(outBuffer, fdOut);
+ }
+
+ return true;
+}
+
+static jboolean nativeCompressLz4(JNIEnv* env, jclass klass, jint fdIn, jint fdOut) {
+ LZ4F_cctx* cctx;
+ if (LZ4F_createCompressionContext(&cctx, LZ4F_VERSION) != 0) {
+ LOG(ERROR) << "Failed to initialize LZ4 compression context.";
+ return false;
+ }
+ std::unique_ptr<LZ4F_cctx, LZ4FCContextDeleter> context(cctx);
+
+ std::vector<char> inBuffer, outBuffer;
+ inBuffer.reserve(LZ4_BUFFER_SIZE);
+ outBuffer.reserve(2 * LZ4_BUFFER_SIZE);
+
+ LZ4F_preferences_t prefs;
+
+ memset(&prefs, 0, sizeof(prefs));
+
+ // Set compression parameters.
+ prefs.autoFlush = 0;
+ prefs.compressionLevel = 0;
+ prefs.frameInfo.blockMode = LZ4F_blockLinked;
+ prefs.frameInfo.blockSizeID = LZ4F_default;
+ prefs.frameInfo.blockChecksumFlag = LZ4F_noBlockChecksum;
+ prefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
+ prefs.favorDecSpeed = 0;
+
+ struct stat sb;
+ if (fstat(fdIn, &sb) == -1) {
+ PLOG(ERROR) << "Failed to obtain input file size.";
+ return false;
+ }
+ prefs.frameInfo.contentSize = sb.st_size;
+
+ // Write header first.
+ outBuffer.resize(LZ4F_HEADER_SIZE_MAX);
+ auto rc = LZ4F_compressBegin(context.get(), outBuffer.data(), outBuffer.size(), &prefs);
+ if (LZ4F_isError(rc)) {
+ LOG(ERROR) << "LZ4F_compressBegin failed: " << LZ4F_getErrorName(rc);
+ return false;
+ }
+ outBuffer.resize(rc);
+
+ bool eof = false;
+ while (!eof) {
+ constexpr auto capacity = LZ4_BUFFER_SIZE;
+ inBuffer.resize(capacity);
+ auto read = TEMP_FAILURE_RETRY(::read(fdIn, inBuffer.data(), inBuffer.size()));
+ if (read < 0) {
+ PLOG(ERROR) << "Failed to read from input file.";
+ return false;
+ }
+
+ inBuffer.resize(read);
+
+ if (read == 0) {
+ eof = true;
+ }
+
+ if (!compressAndWriteLz4(context.get(), inBuffer, outBuffer, fdOut)) {
+ return false;
+ }
+ }
+
+ // Footer.
+ auto prvSize = outBuffer.size();
+ outBuffer.resize(outBuffer.capacity());
+ rc = LZ4F_compressEnd(context.get(), outBuffer.data() + prvSize, outBuffer.size() - prvSize,
+ nullptr);
+ if (LZ4F_isError(rc)) {
+ LOG(ERROR) << "LZ4F_compressEnd failed: " << LZ4F_getErrorName(rc);
+ return false;
+ }
+ outBuffer.resize(prvSize + rc);
+
+ if (!writeToFile(outBuffer, fdOut)) {
+ return false;
+ }
+
+ return true;
+}
+
+static const JNINativeMethod method_table[] = {
+ {"nativeCompressLz4", "(II)Z", (void*)nativeCompressLz4},
+};
+
+} // namespace
+
+int register_android_server_com_android_server_pm_Settings(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/pm/Settings", method_table,
+ NELEM(method_table));
+}
+
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 00f851f..1845057 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -56,6 +56,7 @@
int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(JNIEnv* env);
int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
+int register_android_server_com_android_server_pm_Settings(JNIEnv* env);
int register_android_server_AdbDebuggingManager(JNIEnv* env);
int register_android_server_FaceService(JNIEnv* env);
int register_android_server_GpuService(JNIEnv* env);
@@ -114,6 +115,7 @@
register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);
register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(env);
register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
+ register_android_server_com_android_server_pm_Settings(env);
register_android_server_AdbDebuggingManager(env);
register_android_server_FaceService(env);
register_android_server_GpuService(env);
diff --git a/services/core/jni/onload_settings.cpp b/services/core/jni/onload_settings.cpp
new file mode 100644
index 0000000..b21c34a
--- /dev/null
+++ b/services/core/jni/onload_settings.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#include "jni.h"
+#include "utils/Log.h"
+
+namespace android {
+int register_android_server_com_android_server_pm_Settings(JNIEnv* env);
+};
+
+using namespace android;
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
+ JNIEnv* env = NULL;
+ jint result = -1;
+
+ if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
+ ALOGE("GetEnv failed!");
+ return result;
+ }
+ ALOG_ASSERT(env, "Could not retrieve the env!");
+
+ register_android_server_com_android_server_pm_Settings(env);
+
+ return JNI_VERSION_1_4;
+}
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
index 0f6ef03..3453cbd 100644
--- a/services/core/jni/tvinput/JTvInputHal.cpp
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -42,8 +42,11 @@
std::shared_ptr<ITvInputWrapper>(new ITvInputWrapper(hidlITvInput)),
looper);
}
- ::ndk::SpAIBinder binder(AServiceManager_waitForService(TV_INPUT_AIDL_SERVICE_NAME));
- std::shared_ptr<AidlITvInput> aidlITvInput = AidlITvInput::fromBinder(binder);
+ std::shared_ptr<AidlITvInput> aidlITvInput = nullptr;
+ if (AServiceManager_isDeclared(TV_INPUT_AIDL_SERVICE_NAME)) {
+ ::ndk::SpAIBinder binder(AServiceManager_waitForService(TV_INPUT_AIDL_SERVICE_NAME));
+ aidlITvInput = AidlITvInput::fromBinder(binder);
+ }
if (aidlITvInput == nullptr) {
ALOGE("Couldn't get tv.input service.");
return nullptr;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 775e3d8..1ec2438 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -718,6 +718,16 @@
private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence";
private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = false;
+ // TODO(b/258425381) remove the flag after rollout.
+ private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
+ private static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false;
+
+ /**
+ * This feature flag is checked once after boot and this value us used until the next reboot to
+ * avoid needing to handle the flag changing on the fly.
+ */
+ private final boolean mKeepProfilesRunning = isKeepProfilesRunningFlagEnabled();
+
/**
* For apps targeting U+
* Enable multiple admins to coexist on the same device.
@@ -9892,6 +9902,8 @@
(size == 1 ? "" : "s"));
}
pw.println();
+ pw.println("Keep profiles running: " + mKeepProfilesRunning);
+ pw.println();
mPolicyCache.dump(pw);
pw.println();
@@ -13533,6 +13545,11 @@
}
}
+ @Override
+ public boolean isKeepProfilesRunningEnabled() {
+ return mKeepProfilesRunning;
+ }
+
private @Mode int findInteractAcrossProfilesResetMode(String packageName) {
return getDefaultCrossProfilePackages().contains(packageName)
? AppOpsManager.MODE_ALLOWED
@@ -19167,4 +19184,11 @@
ENABLE_COEXISTENCE_FLAG,
DEFAULT_ENABLE_COEXISTENCE_FLAG);
}
+
+ private static boolean isKeepProfilesRunningFlagEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_DEVICE_POLICY_MANAGER,
+ KEEP_PROFILES_RUNNING_FLAG,
+ DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e96eff21..e41e781 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -187,6 +187,7 @@
import com.android.server.security.FileIntegrityService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
+import com.android.server.security.rkp.RemoteProvisioningService;
import com.android.server.sensorprivacy.SensorPrivacyService;
import com.android.server.sensors.SensorService;
import com.android.server.signedconfig.SignedConfigService;
@@ -1361,11 +1362,16 @@
mSystemServiceManager.startService(BugreportManagerService.class);
t.traceEnd();
- // Serivce for GPU and GPU driver.
+ // Service for GPU and GPU driver.
t.traceBegin("GpuService");
mSystemServiceManager.startService(GpuService.class);
t.traceEnd();
+ // Handles system process requests for remotely provisioned keys & data.
+ t.traceBegin("StartRemoteProvisioningService");
+ mSystemServiceManager.startService(RemoteProvisioningService.class);
+ t.traceEnd();
+
t.traceEnd(); // startCoreServices
}
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 939fb6a..70a5c3f 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -28,7 +28,7 @@
],
srcs: [
- "src/**/*.java",
+ "src/server/**/*.java",
],
static_libs: [
@@ -60,3 +60,52 @@
enabled: false,
},
}
+
+android_test {
+ name: "FrameworksImeTests",
+ defaults: [
+ "modules-utils-testable-device-config-defaults",
+ ],
+
+ srcs: [
+ "src/com/android/inputmethodservice/**/*.java",
+ ],
+
+ manifest: "src/com/android/inputmethodservice/AndroidManifest.xml",
+ test_config: "src/com/android/inputmethodservice/AndroidTest.xml",
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.runner",
+ "androidx.test.espresso.core",
+ "androidx.test.espresso.contrib",
+ "androidx.test.ext.truth",
+ "frameworks-base-testutils",
+ "mockito-target-extended-minus-junit4",
+ "platform-test-annotations",
+ "services.core",
+ "servicestests-core-utils",
+ "servicestests-utils-mockito-extended",
+ "truth-prebuilt",
+ "SimpleImeTestingLib",
+ "SimpleImeImsLib",
+ ],
+
+ libs: [
+ "android.test.mock",
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ data: [
+ ":SimpleTestIme",
+ ],
+
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: ["device-tests"],
+
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
new file mode 100644
index 0000000..0104f71
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.inputmethod.imetests">
+
+ <uses-sdk android:targetSdkVersion="31" />
+
+ <!-- Permissions required for granting and logging -->
+ <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
+ <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
+ <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
+ <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
+
+ <!-- Permissions for reading system info -->
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <!-- The "targetPackage" reference the instruments APK package, which is the FakeImeApk, while
+ the test package is "com.android.inputmethod.imetests" (FrameworksImeTests.apk).-->
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.apps.inputmethod.simpleime"
+ android:label="Frameworks IME Tests" />
+</manifest>
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
new file mode 100644
index 0000000..6c24d6d
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<configuration description="Runs Frameworks IME Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="FrameworksImeTests.apk" />
+ <option name="test-file-name" value="SimpleTestIme.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="FrameworksImeTests" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.inputmethod.imetests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+
+ <!-- Collect the files in the dump directory for debugging -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/sdcard/FrameworksImeTests/" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+</configuration>
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
new file mode 100644
index 0000000..16a9845
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -0,0 +1,276 @@
+/*
+ * 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.inputmethodservice;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper;
+import com.android.apps.inputmethod.simpleime.testing.TestActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class InputMethodServiceTest {
+ private static final String TAG = "SimpleIMSTest";
+ private static final String INPUT_METHOD_SERVICE_NAME = ".SimpleInputMethodService";
+ private static final String EDIT_TEXT_DESC = "Input box";
+ private static final long TIMEOUT_IN_SECONDS = 3;
+
+ public Instrumentation mInstrumentation;
+ public UiDevice mUiDevice;
+ public Context mContext;
+ public String mTargetPackageName;
+ public TestActivity mActivity;
+ public EditText mEditText;
+ public InputMethodServiceWrapper mInputMethodService;
+ public String mInputMethodId;
+
+ @Before
+ public void setUp() throws Exception {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mUiDevice = UiDevice.getInstance(mInstrumentation);
+ mContext = mInstrumentation.getContext();
+ mTargetPackageName = mInstrumentation.getTargetContext().getPackageName();
+ mInputMethodId = getInputMethodId();
+ prepareIme();
+ prepareEditor();
+
+ // Waits for input binding ready.
+ eventually(
+ () -> {
+ mInputMethodService =
+ InputMethodServiceWrapper.getInputMethodServiceWrapperForTesting();
+ assertThat(mInputMethodService).isNotNull();
+
+ // The editor won't bring up keyboard by default.
+ assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
+ assertThat(mInputMethodService.getCurrentInputViewStarted()).isFalse();
+ });
+ }
+
+ @Test
+ public void testShowHideKeyboard_byUserAction() throws InterruptedException {
+ // Performs click on editor box to bring up the soft keyboard.
+ Log.i(TAG, "Click on EditText.");
+ verifyInputViewStatus(() -> clickOnEditorText(), true /* inputViewStarted */);
+
+ // Press back key to hide soft keyboard.
+ Log.i(TAG, "Press back");
+ verifyInputViewStatus(
+ () -> assertThat(mUiDevice.pressHome()).isTrue(), false /* inputViewStarted */);
+ }
+
+ @Test
+ public void testShowHideKeyboard_byApi() throws InterruptedException {
+ // Triggers to show IME via public API.
+ verifyInputViewStatus(
+ () -> assertThat(mActivity.showImeWithWindowInsetsController()).isTrue(),
+ true /* inputViewStarted */);
+
+ // Triggers to hide IME via public API.
+ // TODO(b/242838873): investigate why WIC#hide(ime()) does not work, likely related to
+ // triggered from IME process.
+ verifyInputViewStatusOnMainSync(
+ () -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(),
+ false /* inputViewStarted */);
+ }
+
+ @Test
+ public void testShowHideSelf() throws InterruptedException {
+ // IME requests to show itself without any flags: expect shown.
+ Log.i(TAG, "Call IMS#requestShowSelf(0)");
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestShowSelf(0), true /* inputViewStarted */);
+
+ // IME requests to hide itself with flag: HIDE_IMPLICIT_ONLY, expect not hide (shown).
+ Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY),
+ true /* inputViewStarted */);
+
+ // IME request to hide itself without any flags: expect hidden.
+ Log.i(TAG, "Call IMS#requestHideSelf(0)");
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestHideSelf(0), false /* inputViewStarted */);
+
+ // IME request to show itself with flag SHOW_IMPLICIT: expect shown.
+ Log.i(TAG, "Call IMS#requestShowSelf(InputMethodManager.SHOW_IMPLICIT)");
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT),
+ true /* inputViewStarted */);
+
+ // IME request to hide itself with flag: HIDE_IMPLICIT_ONLY, expect hidden.
+ Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY),
+ false /* inputViewStarted */);
+ }
+
+ private void verifyInputViewStatus(Runnable runnable, boolean inputViewStarted)
+ throws InterruptedException {
+ verifyInputViewStatusInternal(runnable, inputViewStarted, false /*runOnMainSync*/);
+ }
+
+ private void verifyInputViewStatusOnMainSync(Runnable runnable, boolean inputViewStarted)
+ throws InterruptedException {
+ verifyInputViewStatusInternal(runnable, inputViewStarted, true /*runOnMainSync*/);
+ }
+
+ private void verifyInputViewStatusInternal(
+ Runnable runnable, boolean inputViewStarted, boolean runOnMainSync)
+ throws InterruptedException {
+ CountDownLatch signal = new CountDownLatch(1);
+ mInputMethodService.setCountDownLatchForTesting(signal);
+ // Runnable to trigger onStartInputView()/ onFinishInputView()
+ if (runOnMainSync) {
+ mInstrumentation.runOnMainSync(runnable);
+ } else {
+ runnable.run();
+ }
+ // Waits for onStartInputView() to finish.
+ mInstrumentation.waitForIdleSync();
+ signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+ // Input is not finished.
+ assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
+ assertThat(mInputMethodService.getCurrentInputViewStarted()).isEqualTo(inputViewStarted);
+ }
+
+ @Test
+ public void testFullScreenMode() throws Exception {
+ Log.i(TAG, "Set orientation natural");
+ verifyFullscreenMode(() -> setOrientation(0), true /* orientationPortrait */);
+
+ Log.i(TAG, "Set orientation left");
+ verifyFullscreenMode(() -> setOrientation(1), false /* orientationPortrait */);
+
+ Log.i(TAG, "Set orientation right");
+ verifyFullscreenMode(() -> setOrientation(2), false /* orientationPortrait */);
+
+ mUiDevice.unfreezeRotation();
+ }
+
+ private void setOrientation(int orientation) {
+ // Simple wrapper for catching RemoteException.
+ try {
+ switch (orientation) {
+ case 1:
+ mUiDevice.setOrientationLeft();
+ break;
+ case 2:
+ mUiDevice.setOrientationRight();
+ break;
+ default:
+ mUiDevice.setOrientationNatural();
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void verifyFullscreenMode(Runnable runnable, boolean orientationPortrait)
+ throws InterruptedException {
+ CountDownLatch signal = new CountDownLatch(1);
+ mInputMethodService.setCountDownLatchForTesting(signal);
+
+ // Runnable to trigger onConfigurationChanged()
+ try {
+ runnable.run();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ // Waits for onConfigurationChanged() to finish.
+ mInstrumentation.waitForIdleSync();
+ signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+
+ clickOnEditorText();
+ eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isTrue());
+
+ assertThat(mInputMethodService.getResources().getConfiguration().orientation)
+ .isEqualTo(
+ orientationPortrait
+ ? Configuration.ORIENTATION_PORTRAIT
+ : Configuration.ORIENTATION_LANDSCAPE);
+ EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo();
+ assertThat(editorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN).isEqualTo(0);
+ assertThat(editorInfo.internalImeOptions & EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT)
+ .isEqualTo(
+ orientationPortrait ? EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT : 0);
+ assertThat(mInputMethodService.onEvaluateFullscreenMode()).isEqualTo(!orientationPortrait);
+ assertThat(mInputMethodService.isFullscreenMode()).isEqualTo(!orientationPortrait);
+
+ mUiDevice.pressBack();
+ }
+
+ private void prepareIme() throws Exception {
+ executeShellCommand("ime enable " + mInputMethodId);
+ executeShellCommand("ime set " + mInputMethodId);
+ mInstrumentation.waitForIdleSync();
+ Log.i(TAG, "Finish preparing IME");
+ }
+
+ private void prepareEditor() {
+ mActivity = TestActivity.start(mInstrumentation);
+ mEditText = mActivity.mEditText;
+ Log.i(TAG, "Finish preparing activity with editor.");
+ }
+
+ private String getInputMethodId() {
+ return mTargetPackageName + "/" + INPUT_METHOD_SERVICE_NAME;
+ }
+
+ private String executeShellCommand(String cmd) throws Exception {
+ Log.i(TAG, "Run command: " + cmd);
+ return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ .executeShellCommand(cmd);
+ }
+
+ private void clickOnEditorText() {
+ // Find the editText and click it.
+ UiObject2 editTextUiObject =
+ mUiDevice.wait(
+ Until.findObject(By.desc(EDIT_TEXT_DESC)),
+ TimeUnit.SECONDS.toMillis(TIMEOUT_IN_SECONDS));
+ assertThat(editTextUiObject).isNotNull();
+ editTextUiObject.click();
+ mInstrumentation.waitForIdleSync();
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
new file mode 100644
index 0000000..ef50476
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
@@ -0,0 +1,62 @@
+// 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 {
+ // 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"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "SimpleTestIme",
+
+ srcs: [
+ "src/com/android/apps/inputmethod/simpleime/*.java",
+ ],
+
+ static_libs: [
+ "SimpleImeImsLib",
+ "SimpleImeTestingLib",
+ ],
+ resource_dirs: ["res"],
+ manifest: "AndroidManifest.xml",
+
+ dex_preopt: {
+ enabled: false,
+ },
+ optimize: {
+ enabled: false,
+ },
+ export_package_resources: true,
+ sdk_version: "current",
+}
+
+android_library {
+ name: "SimpleImeImsLib",
+ srcs: [
+ "src/com/android/apps/inputmethod/simpleime/ims/*.java",
+ ],
+ sdk_version: "current",
+}
+
+android_library {
+ name: "SimpleImeTestingLib",
+ srcs: [
+ "src/com/android/apps/inputmethod/simpleime/testing/*.java",
+ ],
+ sdk_version: "current",
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
new file mode 100644
index 0000000..802caf1
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.apps.inputmethod.simpleime">
+
+ <uses-sdk android:targetSdkVersion="31" />
+
+ <application android:debuggable="true"
+ android:label="@string/app_name">
+ <service
+ android:name="com.android.apps.inputmethod.simpleime.SimpleInputMethodService"
+ android:label="@string/app_name"
+ android:directBootAware="true"
+ android:permission="android.permission.BIND_INPUT_METHOD"
+ android:exported="true">
+
+ <meta-data
+ android:name="android.view.im"
+ android:resource="@xml/method"/>
+
+ <intent-filter>
+ <action android:name="android.view.InputMethod"/>
+ </intent-filter>
+ </service>
+
+ <!-- This is for test only. -->
+ <activity android:name="com.android.apps.inputmethod.simpleime.testing.TestActivity"
+ android:exported="false"
+ android:label="TestActivity"
+ android:launchMode="singleInstance"
+ android:excludeFromRecents="true"
+ android:noHistory="true"
+ android:taskAffinity="">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/drawable/key_border.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/drawable/key_border.xml
new file mode 100644
index 0000000..dbfcc30
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/drawable/key_border.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle" >
+ <solid
+ android:color="#FAFAFA" >
+ </solid>
+ <stroke
+ android:width="1dp"
+ android:color="#0F000000" >
+ </stroke>
+ <corners
+ android:radius="2dp" >
+ </corners>
+</shape>
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/input_view.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/input_view.xml
new file mode 100644
index 0000000..f229270
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/input_view.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/input"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/qwerty_10_9_9.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/qwerty_10_9_9.xml
new file mode 100644
index 0000000..ee94ea9
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/qwerty_10_9_9.xml
@@ -0,0 +1,200 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/KeyboardArea">
+
+ <View style="@style/KeyboardRow.Header"/>
+
+ <LinearLayout style="@style/KeyboardRow">
+ <TextView
+ android:id="@+id/key_pos_0_0"
+ android:text="q"
+ android:tag="KEYCODE_Q"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_1"
+ android:text="w"
+ android:tag="KEYCODE_W"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_2"
+ android:text="e"
+ android:tag="KEYCODE_E"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_3"
+ android:text="r"
+ android:tag="KEYCODE_R"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_4"
+ android:text="t"
+ android:tag="KEYCODE_T"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_5"
+ android:text="y"
+ android:tag="KEYCODE_Y"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_6"
+ android:text="u"
+ android:tag="KEYCODE_U"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_7"
+ android:text="i"
+ android:tag="KEYCODE_I"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_8"
+ android:text="o"
+ android:tag="KEYCODE_O"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_9"
+ android:text="p"
+ android:tag="KEYCODE_P"
+ style="@style/SoftKey"/>
+ </LinearLayout>
+
+ <LinearLayout style="@style/KeyboardRow">
+ <TextView
+ android:id="@+id/key_pos_1_0"
+ android:text="a"
+ android:tag="KEYCODE_A"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_1"
+ android:text="s"
+ android:tag="KEYCODE_S"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_2"
+ android:text="d"
+ android:tag="KEYCODE_D"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_3"
+ android:text="f"
+ android:tag="KEYCODE_F"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_4"
+ android:text="g"
+ android:tag="KEYCODE_G"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_5"
+ android:text="h"
+ android:tag="KEYCODE_H"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_6"
+ android:text="j"
+ android:tag="KEYCODE_J"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_7"
+ android:text="k"
+ android:tag="KEYCODE_K"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_8"
+ android:text="l"
+ android:tag="KEYCODE_L"
+ style="@style/SoftKey"/>
+ </LinearLayout>
+
+ <LinearLayout style="@style/KeyboardRow">
+ <TextView
+ android:id="@+id/key_pos_shift"
+ android:text="SHI"
+ android:tag="KEYCODE_SHIFT"
+ style="@style/SoftKey.Function"/>
+ <TextView
+ android:id="@+id/key_pos_2_0"
+ android:text="z"
+ android:tag="KEYCODE_Z"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_2_1"
+ android:text="x"
+ android:tag="KEYCODE_X"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_2_2"
+ style="@style/SoftKey"
+ android:text="c"
+ android:tag="KEYCODE_C"/>
+ <TextView
+ android:id="@+id/key_pos_2_3"
+ android:text="v"
+ android:tag="KEYCODE_V"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_2_4"
+ android:text="b"
+ android:tag="KEYCODE_B"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_2_5"
+ android:text="n"
+ android:tag="KEYCODE_N"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_2_6"
+ android:text="m"
+ android:tag="KEYCODE_M"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_del"
+ android:text="DEL"
+ android:tag="KEYCODE_DEL"
+ style="@style/SoftKey.Function"/>
+ </LinearLayout>
+
+ <LinearLayout style="@style/KeyboardRow">
+ <TextView
+ android:id="@+id/key_pos_symbol"
+ android:text="TAB"
+ android:tag="KEYCODE_TAB"
+ style="@style/SoftKey.Function"/>
+ <TextView
+ android:id="@+id/key_pos_comma"
+ android:text=","
+ android:tag="KEYCODE_COMMA"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_space"
+ android:text="SPACE"
+ android:tag="KEYCODE_SPACE"
+ style="@style/SoftKey.Space"/>
+ <TextView
+ android:id="@+id/key_pos_period"
+ android:text="."
+ android:tag="KEYCODE_PERIOD"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_enter"
+ android:text="ENT"
+ android:tag="KEYCODE_ENTER"
+ style="@style/SoftKey.Function.Bottom"/>
+ </LinearLayout>
+</LinearLayout>
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/dimens.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/dimens.xml
new file mode 100644
index 0000000..1a4959e
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <dimen name="text_size_normal">24dp</dimen>
+ <dimen name="text_size_symbol">14dp</dimen>
+
+ <dimen name="keyboard_header_height">40dp</dimen>
+ <dimen name="keyboard_row_height">50dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/strings.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/strings.xml
new file mode 100644
index 0000000..11377fa
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <string name="app_name">Fake IME</string>
+</resources>
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/styles.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/styles.xml
new file mode 100644
index 0000000..83f7bc3
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/styles.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <style name="KeyboardArea">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_gravity">bottom</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:background">#FFFFFFFF</item>
+ </style>
+
+ <style name="KeyboardRow">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">@dimen/keyboard_row_height</item>
+ <item name="android:orientation">horizontal</item>
+ </style>
+
+ <style name="KeyboardRow.Header">
+ <item name="android:layout_height">@dimen/keyboard_header_height</item>
+ <item name="android:background">#FFEEEEEE</item>
+ </style>
+
+ <style name="SoftKey">
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:layout_weight">2</item>
+ <item name="android:gravity">center</item>
+ <item name="android:textColor">#FF000000</item>
+ <item name="android:textSize">@dimen/text_size_normal</item>
+ <item name="android:fontFamily">roboto-regular</item>
+ <item name="android:background">@drawable/key_border</item>
+ </style>
+
+ <style name="SoftKey.Function">
+ <item name="android:layout_weight">3</item>
+ <item name="android:textColor">#FF333333</item>
+ <item name="android:textSize">@dimen/text_size_symbol</item>
+ </style>
+
+ <style name="SoftKey.Function.Bottom">
+ <item name="android:layout_weight">3</item>
+ <item name="android:textColor">#FF333333</item>
+ <item name="android:textSize">@dimen/text_size_symbol</item>
+ </style>
+
+ <style name="SoftKey.Space">
+ <item name="android:layout_weight">10</item>
+ <item name="android:textColor">#FF333333</item>
+ <item name="android:textSize">@dimen/text_size_symbol</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml
new file mode 100644
index 0000000..872b068
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android">
+ <subtype
+ android:label="FakeIme"
+ android:imeSubtypeLocale="en_US"
+ android:imeSubtypeMode="keyboard"/>
+</input-method>
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/KeyCodeConstants.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/KeyCodeConstants.java
new file mode 100644
index 0000000..990fa24
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/KeyCodeConstants.java
@@ -0,0 +1,68 @@
+/*
+ * 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.apps.inputmethod.simpleime;
+
+import android.view.KeyEvent;
+
+import java.util.HashMap;
+
+/** Holder of key codes and their name. */
+public final class KeyCodeConstants {
+ private KeyCodeConstants() {}
+
+ static final HashMap<String, Integer> KEY_NAME_TO_CODE_MAP = new HashMap<>();
+
+ static {
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_A", KeyEvent.KEYCODE_A);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_B", KeyEvent.KEYCODE_B);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_C", KeyEvent.KEYCODE_C);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_D", KeyEvent.KEYCODE_D);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_E", KeyEvent.KEYCODE_E);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_F", KeyEvent.KEYCODE_F);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_G", KeyEvent.KEYCODE_G);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_H", KeyEvent.KEYCODE_H);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_I", KeyEvent.KEYCODE_I);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_J", KeyEvent.KEYCODE_J);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_K", KeyEvent.KEYCODE_K);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_L", KeyEvent.KEYCODE_L);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_M", KeyEvent.KEYCODE_M);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_N", KeyEvent.KEYCODE_N);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_O", KeyEvent.KEYCODE_O);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_P", KeyEvent.KEYCODE_P);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_Q", KeyEvent.KEYCODE_Q);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_R", KeyEvent.KEYCODE_R);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_S", KeyEvent.KEYCODE_S);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_T", KeyEvent.KEYCODE_T);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_U", KeyEvent.KEYCODE_U);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_V", KeyEvent.KEYCODE_V);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_W", KeyEvent.KEYCODE_W);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_X", KeyEvent.KEYCODE_X);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_Y", KeyEvent.KEYCODE_Y);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_Z", KeyEvent.KEYCODE_Z);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_SHIFT", KeyEvent.KEYCODE_SHIFT_LEFT);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_DEL", KeyEvent.KEYCODE_DEL);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_SPACE", KeyEvent.KEYCODE_SPACE);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_ENTER", KeyEvent.KEYCODE_ENTER);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_COMMA", KeyEvent.KEYCODE_COMMA);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_PERIOD", KeyEvent.KEYCODE_PERIOD);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_TAB", KeyEvent.KEYCODE_TAB);
+ }
+
+ public static boolean isAlphaKeyCode(int keyCode) {
+ return keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z;
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleInputMethodService.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleInputMethodService.java
new file mode 100644
index 0000000..48942a3
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleInputMethodService.java
@@ -0,0 +1,67 @@
+/*
+ * 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.apps.inputmethod.simpleime;
+
+import android.inputmethodservice.InputMethodService;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.widget.FrameLayout;
+
+import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper;
+
+/** The {@link InputMethodService} implementation for SimpeTestIme app. */
+public class SimpleInputMethodService extends InputMethodServiceWrapper {
+ private static final String TAG = "SimpleIMS";
+
+ private FrameLayout mInputView;
+
+ @Override
+ public View onCreateInputView() {
+ Log.i(TAG, "onCreateInputView()");
+ mInputView = (FrameLayout) LayoutInflater.from(this).inflate(R.layout.input_view, null);
+ return mInputView;
+ }
+
+ @Override
+ public void onStartInputView(EditorInfo info, boolean restarting) {
+ super.onStartInputView(info, restarting);
+ mInputView.removeAllViews();
+ SimpleKeyboard keyboard = new SimpleKeyboard(this, R.layout.qwerty_10_9_9);
+ mInputView.addView(keyboard.inflateKeyboardView(LayoutInflater.from(this), mInputView));
+ }
+
+ void handle(String data, int keyboardState) {
+ InputConnection inputConnection = getCurrentInputConnection();
+ Integer keyCode = KeyCodeConstants.KEY_NAME_TO_CODE_MAP.get(data);
+ Log.v(TAG, "keyCode: " + keyCode);
+ if (keyCode != null) {
+ inputConnection.sendKeyEvent(
+ new KeyEvent(
+ SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(),
+ KeyEvent.ACTION_DOWN,
+ keyCode,
+ 0,
+ KeyCodeConstants.isAlphaKeyCode(keyCode) ? keyboardState : 0));
+ }
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboard.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboard.java
new file mode 100644
index 0000000..b16ec9eb
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboard.java
@@ -0,0 +1,126 @@
+/*
+ * 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.apps.inputmethod.simpleime;
+
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/** Controls the visible virtual keyboard view. */
+final class SimpleKeyboard {
+ private static final String TAG = "SimpleKeyboard";
+
+ private static final int[] SOFT_KEY_IDS =
+ new int[] {
+ R.id.key_pos_0_0,
+ R.id.key_pos_0_1,
+ R.id.key_pos_0_2,
+ R.id.key_pos_0_3,
+ R.id.key_pos_0_4,
+ R.id.key_pos_0_5,
+ R.id.key_pos_0_6,
+ R.id.key_pos_0_7,
+ R.id.key_pos_0_8,
+ R.id.key_pos_0_9,
+ R.id.key_pos_1_0,
+ R.id.key_pos_1_1,
+ R.id.key_pos_1_2,
+ R.id.key_pos_1_3,
+ R.id.key_pos_1_4,
+ R.id.key_pos_1_5,
+ R.id.key_pos_1_6,
+ R.id.key_pos_1_7,
+ R.id.key_pos_1_8,
+ R.id.key_pos_2_0,
+ R.id.key_pos_2_1,
+ R.id.key_pos_2_2,
+ R.id.key_pos_2_3,
+ R.id.key_pos_2_4,
+ R.id.key_pos_2_5,
+ R.id.key_pos_2_6,
+ R.id.key_pos_shift,
+ R.id.key_pos_del,
+ R.id.key_pos_symbol,
+ R.id.key_pos_comma,
+ R.id.key_pos_space,
+ R.id.key_pos_period,
+ R.id.key_pos_enter,
+ };
+
+ private final SimpleInputMethodService mSimpleInputMethodService;
+ private final int mViewResId;
+ private final SparseArray<TextView> mSoftKeyViews = new SparseArray<>();
+ private View mKeyboardView;
+ private int mKeyboardState;
+
+ SimpleKeyboard(SimpleInputMethodService simpleInputMethodService, int viewResId) {
+ this.mSimpleInputMethodService = simpleInputMethodService;
+ this.mViewResId = viewResId;
+ this.mKeyboardState = 0;
+ }
+
+ View inflateKeyboardView(LayoutInflater inflater, ViewGroup inputView) {
+ mKeyboardView = inflater.inflate(mViewResId, inputView, false);
+ mapSoftKeys();
+ return mKeyboardView;
+ }
+
+ private void mapSoftKeys() {
+ for (int id : SOFT_KEY_IDS) {
+ TextView softKeyView = mKeyboardView.findViewById(id);
+ mSoftKeyViews.put(id, softKeyView);
+ String tagData = softKeyView.getTag() != null ? softKeyView.getTag().toString() : null;
+ softKeyView.setOnClickListener(v -> handle(tagData));
+ }
+ }
+
+ private void handle(String data) {
+ Log.i(TAG, "handle(): " + data);
+ if (TextUtils.isEmpty(data)) {
+ return;
+ }
+ if ("KEYCODE_SHIFT".equals(data)) {
+ handleShift();
+ return;
+ }
+
+ mSimpleInputMethodService.handle(data, mKeyboardState);
+ }
+
+ private void handleShift() {
+ mKeyboardState = toggleShiftState(mKeyboardState);
+ Log.v(TAG, "currentKeyboardState: " + mKeyboardState);
+ boolean isShiftOn = isShiftOn(mKeyboardState);
+ for (int i = 0; i < mSoftKeyViews.size(); i++) {
+ TextView softKeyView = mSoftKeyViews.valueAt(i);
+ softKeyView.setAllCaps(isShiftOn);
+ }
+ }
+
+ private static boolean isShiftOn(int state) {
+ return (state & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON;
+ }
+
+ private static int toggleShiftState(int state) {
+ return state ^ KeyEvent.META_SHIFT_ON;
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
new file mode 100644
index 0000000..b706a65
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
@@ -0,0 +1,102 @@
+/*
+ * 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.apps.inputmethod.simpleime.ims;
+
+import android.content.res.Configuration;
+import android.inputmethodservice.InputMethodService;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+
+import java.util.concurrent.CountDownLatch;
+
+/** Wrapper of {@link InputMethodService} to expose interfaces for testing purpose. */
+public class InputMethodServiceWrapper extends InputMethodService {
+ private static final String TAG = "InputMethodServiceWrapper";
+
+ private static InputMethodServiceWrapper sInputMethodServiceWrapper;
+
+ public static InputMethodServiceWrapper getInputMethodServiceWrapperForTesting() {
+ return sInputMethodServiceWrapper;
+ }
+
+ private boolean mInputViewStarted;
+ private CountDownLatch mCountDownLatchForTesting;
+
+ public boolean getCurrentInputViewStarted() {
+ return mInputViewStarted;
+ }
+
+ public void setCountDownLatchForTesting(CountDownLatch countDownLatchForTesting) {
+ mCountDownLatchForTesting = countDownLatchForTesting;
+ }
+
+ @Override
+ public void onCreate() {
+ Log.i(TAG, "onCreate()");
+ super.onCreate();
+ sInputMethodServiceWrapper = this;
+ }
+
+ @Override
+ public void onStartInput(EditorInfo info, boolean restarting) {
+ Log.i(TAG, "onStartInput() editor=" + info + ", restarting=" + restarting);
+ super.onStartInput(info, restarting);
+ }
+
+ @Override
+ public void onStartInputView(EditorInfo info, boolean restarting) {
+ Log.i(TAG, "onStartInputView() editor=" + info + ", restarting=" + restarting);
+ super.onStartInputView(info, restarting);
+ mInputViewStarted = true;
+ if (mCountDownLatchForTesting != null) {
+ mCountDownLatchForTesting.countDown();
+ }
+ }
+
+ @Override
+ public void onFinishInput() {
+ Log.i(TAG, "onFinishInput()");
+ super.onFinishInput();
+ }
+
+ @Override
+ public void onFinishInputView(boolean finishingInput) {
+ Log.i(TAG, "onFinishInputView()");
+ super.onFinishInputView(finishingInput);
+ mInputViewStarted = false;
+
+ if (mCountDownLatchForTesting != null) {
+ mCountDownLatchForTesting.countDown();
+ }
+ }
+
+ @Override
+ public void requestHideSelf(int flags) {
+ Log.i(TAG, "requestHideSelf() " + flags);
+ super.requestHideSelf(flags);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ Log.i(TAG, "onConfigurationChanged() " + newConfig);
+ super.onConfigurationChanged(newConfig);
+
+ if (mCountDownLatchForTesting != null) {
+ mCountDownLatchForTesting.countDown();
+ }
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java
new file mode 100644
index 0000000..0eec7e6
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java
@@ -0,0 +1,105 @@
+/*
+ * 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.apps.inputmethod.simpleime.testing;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+/**
+ * A special activity for testing purpose.
+ *
+ * <p>This is used when the instruments package is SimpleTestIme, as the Intent needs to be started
+ * in the instruments package. More details see {@link
+ * Instrumentation#startActivitySync(Intent)}.</>
+ */
+public class TestActivity extends Activity {
+ private static final String TAG = "TestActivity";
+
+ /**
+ * Start a new test activity with an editor and wait for it to begin running before returning.
+ *
+ * @param instrumentation application instrumentation
+ * @return the newly started activity
+ */
+ public static TestActivity start(Instrumentation instrumentation) {
+ Intent intent =
+ new Intent()
+ .setAction(Intent.ACTION_MAIN)
+ .setClass(instrumentation.getTargetContext(), TestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ return (TestActivity) instrumentation.startActivitySync(intent);
+ }
+
+ public EditText mEditText;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+ LinearLayout rootView = new LinearLayout(this);
+ mEditText = new EditText(this);
+ mEditText.setContentDescription("Input box");
+ rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+ setContentView(rootView);
+ mEditText.requestFocus();
+ super.onCreate(savedInstanceState);
+ }
+
+ /** Shows soft keyboard via InputMethodManager. */
+ public boolean showImeWithInputMethodManager(int flags) {
+ InputMethodManager imm = getSystemService(InputMethodManager.class);
+ boolean result = imm.showSoftInput(mEditText, flags);
+ Log.i(TAG, "hideIme() via InputMethodManager, result=" + result);
+ return result;
+ }
+
+ /** Shows soft keyboard via WindowInsetsController. */
+ public boolean showImeWithWindowInsetsController() {
+ WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
+ windowInsetsController.show(WindowInsets.Type.ime());
+ Log.i(TAG, "showIme() via WindowInsetsController");
+ return true;
+ }
+
+ /** Hides soft keyboard via InputMethodManager. */
+ public boolean hideImeWithInputMethodManager(int flags) {
+ InputMethodManager imm = getSystemService(InputMethodManager.class);
+ boolean result = imm.hideSoftInputFromWindow(mEditText.getWindowToken(), flags);
+ Log.i(TAG, "hideIme() via InputMethodManager, result=" + result);
+ return result;
+ }
+
+ /** Hides soft keyboard via WindowInsetsController. */
+ public boolean hideImeWithWindowInsetsController() {
+ WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
+ windowInsetsController.hide(WindowInsets.Type.ime());
+ Log.i(TAG, "hideIme() via WindowInsetsController");
+ return true;
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index ebd6b64..cc26593 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -94,6 +94,7 @@
"libunwindstack",
"libutils",
"netd_aidl_interface-V5-cpp",
+ "libservices.core.settings.testonly",
],
dxflags: ["--multi-dex"],
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java
index 3727d66..b3f64b6 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java
@@ -111,6 +111,10 @@
private static final String PACKAGE_NAME_3 = "com.android.app3";
private static final int TEST_RESOURCE_ID = 2131231283;
+ static {
+ System.loadLibrary("services.core.settings.testonly");
+ }
+
@Mock
RuntimePermissionsPersistence mRuntimePermissionsPersistence;
@Mock
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index de09b19..8d78cd6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -195,6 +195,12 @@
false, null, false, null);
}
+ private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
+ BroadcastRecord record, int recordIndex, long enqueueTime) {
+ queue.enqueueOrReplaceBroadcast(record, recordIndex);
+ record.enqueueTime = enqueueTime;
+ }
+
@Test
public void testRunnableList_Simple() {
assertRunnableList(List.of(), mHead);
@@ -549,29 +555,32 @@
mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 2;
BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+ long timeCounter = 100;
// mix of broadcasts, with more than 2 fg/urgent
- queue.enqueueOrReplaceBroadcast(
- makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
- queue.enqueueOrReplaceBroadcast(
- makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)), 0);
- queue.enqueueOrReplaceBroadcast(
- makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
- queue.enqueueOrReplaceBroadcast(
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)),
+ 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)),
+ 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
- queue.enqueueOrReplaceBroadcast(
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES),
- optInteractive), 0);
- queue.enqueueOrReplaceBroadcast(
+ optInteractive), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
- optInteractive), 0);
- queue.enqueueOrReplaceBroadcast(
+ optInteractive), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
- queue.enqueueOrReplaceBroadcast(
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL),
- optInteractive), 0);
+ optInteractive), 0, timeCounter++);
queue.makeActiveNextPending();
assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
@@ -604,35 +613,38 @@
mConstants.MAX_CONSECUTIVE_NORMAL_DISPATCHES = 2;
final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+ long timeCounter = 100;
// mix of broadcasts, with more than 2 normal
- queue.enqueueOrReplaceBroadcast(
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED)
- .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
- queue.enqueueOrReplaceBroadcast(
- makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
- queue.enqueueOrReplaceBroadcast(
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)),
+ 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_PACKAGE_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
- queue.enqueueOrReplaceBroadcast(
- makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)), 0);
- queue.enqueueOrReplaceBroadcast(
- makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
- queue.enqueueOrReplaceBroadcast(
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)),
+ 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
- queue.enqueueOrReplaceBroadcast(
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES),
- optInteractive), 0);
- queue.enqueueOrReplaceBroadcast(
+ optInteractive), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
- optInteractive), 0);
- queue.enqueueOrReplaceBroadcast(
+ optInteractive), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
- queue.enqueueOrReplaceBroadcast(
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL),
- optInteractive), 0);
+ optInteractive), 0, timeCounter++);
queue.makeActiveNextPending();
assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
@@ -659,6 +671,63 @@
}
/**
+ * Verify that BroadcastProcessQueue#setPrioritizeEarliest() works as expected.
+ */
+ @Test
+ public void testPrioritizeEarliest() {
+ final BroadcastOptions optInteractive = BroadcastOptions.makeBasic();
+ optInteractive.setInteractive(true);
+
+ BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+ queue.setPrioritizeEarliest(true);
+ long timeCounter = 100;
+
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED)
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)),
+ 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_PACKAGE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)),
+ 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)),
+ 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
+ optInteractive), 0, timeCounter++);
+
+ // When we mark BroadcastProcessQueue to prioritize earliest, we should
+ // expect to dispatch broadcasts in the order they were enqueued
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+ // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_PACKAGE_CHANGED, queue.getActive().intent.getAction());
+ // and then back to prioritizing urgent ones
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_TIME_TICK, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+ // verify the reset-count-then-resume worked too
+ queue.makeActiveNextPending();
+ assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
+ queue.getActive().intent.getAction());
+ }
+
+ /**
* Verify that sending a broadcast that removes any matching pending
* broadcasts is applied as expected.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index a8d8945..d3fa92c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -647,15 +647,15 @@
}
}
- private void checkReportedOptedInGameModes(GameManagerService gameManagerService,
- int... requiredOptedInModes) {
- Arrays.sort(requiredOptedInModes);
- // check GetModeInfo.getOptedInGameModes
+ private void checkReportedOverriddenGameModes(GameManagerService gameManagerService,
+ int... requiredOverriddenModes) {
+ Arrays.sort(requiredOverriddenModes);
+ // check GetModeInfo.getOverriddenGameModes
GameModeInfo info = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
assertNotNull(info);
- int[] optedInModes = info.getOptedInGameModes();
- Arrays.sort(optedInModes);
- assertArrayEquals(requiredOptedInModes, optedInModes);
+ int[] overriddenModes = info.getOverriddenGameModes();
+ Arrays.sort(overriddenModes);
+ assertArrayEquals(requiredOverriddenModes, overriddenModes);
}
private void checkDownscaling(GameManagerService gameManagerService,
@@ -697,7 +697,7 @@
assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps());
}
- private boolean checkOptedIn(GameManagerService gameManagerService, int gameMode) {
+ private boolean checkOverridden(GameManagerService gameManagerService, int gameMode) {
GameManagerService.GamePackageConfiguration config =
gameManagerService.getConfig(mPackageName, USER_ID_1);
return config.willGamePerformOptimizations(gameMode);
@@ -870,8 +870,8 @@
mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
- assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
- assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_BATTERY));
+ assertFalse(checkOverridden(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
+ assertFalse(checkOverridden(gameManagerService, GameManager.GAME_MODE_BATTERY));
checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);
gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, 3, "40",
@@ -884,9 +884,9 @@
mockInterventionsDisabledAllOptInFromXml();
gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
- assertTrue(checkOptedIn(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
+ assertTrue(checkOverridden(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
// opt-in is still false for battery mode as override exists
- assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_BATTERY));
+ assertFalse(checkOverridden(gameManagerService, GameManager.GAME_MODE_BATTERY));
}
/**
@@ -1310,7 +1310,7 @@
checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
GameManager.GAME_MODE_CUSTOM);
- checkReportedOptedInGameModes(gameManagerService);
+ checkReportedOverriddenGameModes(gameManagerService);
assertEquals(new GameModeConfiguration.Builder()
.setFpsOverride(30)
@@ -1337,7 +1337,7 @@
checkReportedAvailableGameModes(gameManagerService,
GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
GameManager.GAME_MODE_CUSTOM);
- checkReportedOptedInGameModes(gameManagerService);
+ checkReportedOverriddenGameModes(gameManagerService);
assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
@@ -1357,7 +1357,7 @@
checkReportedAvailableGameModes(gameManagerService,
GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD,
GameManager.GAME_MODE_CUSTOM);
- checkReportedOptedInGameModes(gameManagerService);
+ checkReportedOverriddenGameModes(gameManagerService);
assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
@@ -1382,7 +1382,7 @@
}
@Test
- public void testGetGameModeInfoWithAllGameModesOptedIn_noDeviceConfig()
+ public void testGetGameModeInfoWithAllGameModesOverridden_noDeviceConfig()
throws Exception {
mockModifyGameModeGranted();
mockInterventionsEnabledAllOptInFromXml();
@@ -1390,14 +1390,14 @@
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
- verifyAllModesOptedInAndInterventionsAvailable(gameManagerService, gameModeInfo);
+ verifyAllModesOverriddenAndInterventionsAvailable(gameManagerService, gameModeInfo);
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
}
@Test
- public void testGetGameModeInfoWithAllGameModesOptedIn_allDeviceConfig()
+ public void testGetGameModeInfoWithAllGameModesOverridden_allDeviceConfig()
throws Exception {
mockModifyGameModeGranted();
mockInterventionsEnabledAllOptInFromXml();
@@ -1405,26 +1405,26 @@
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
- verifyAllModesOptedInAndInterventionsAvailable(gameManagerService, gameModeInfo);
+ verifyAllModesOverriddenAndInterventionsAvailable(gameManagerService, gameModeInfo);
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
}
- private void verifyAllModesOptedInAndInterventionsAvailable(
+ private void verifyAllModesOverriddenAndInterventionsAvailable(
GameManagerService gameManagerService,
GameModeInfo gameModeInfo) {
checkReportedAvailableGameModes(gameManagerService,
GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
- checkReportedOptedInGameModes(gameManagerService,
+ checkReportedOverriddenGameModes(gameManagerService,
GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY);
assertTrue(gameModeInfo.isFpsOverrideAllowed());
assertTrue(gameModeInfo.isDownscalingAllowed());
}
@Test
- public void testGetGameModeInfoWithBatteryModeOptedIn_withBatteryDeviceConfig()
+ public void testGetGameModeInfoWithBatteryModeOverridden_withBatteryDeviceConfig()
throws Exception {
mockModifyGameModeGranted();
mockInterventionsEnabledBatteryOptInFromXml();
@@ -1435,14 +1435,14 @@
checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
- checkReportedOptedInGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY);
+ checkReportedOverriddenGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY);
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
}
@Test
- public void testGetGameModeInfoWithPerformanceModeOptedIn_withAllDeviceConfig()
+ public void testGetGameModeInfoWithPerformanceModeOverridden_withAllDeviceConfig()
throws Exception {
mockModifyGameModeGranted();
mockInterventionsEnabledPerformanceOptInFromXml();
@@ -1454,7 +1454,7 @@
checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
GameManager.GAME_MODE_CUSTOM);
- checkReportedOptedInGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE);
+ checkReportedOverriddenGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE);
assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
@@ -1993,7 +1993,7 @@
}
@Test
- public void testResetInterventions_onGameModeOptedIn() throws Exception {
+ public void testResetInterventions_onGameModeOverridden() throws Exception {
mockModifyGameModeGranted();
String configStringBefore =
"mode=2,downscaleFactor=1.0,fps=90";
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 fc737d0..8e48490 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -34,6 +34,8 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -46,6 +48,7 @@
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
+import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
@@ -63,6 +66,7 @@
import com.android.server.LocalServices;
import com.android.server.PowerAllowlistInternal;
import com.android.server.SystemServiceManager;
+import com.android.server.job.controllers.ConnectivityController;
import com.android.server.job.controllers.JobStatus;
import com.android.server.pm.UserManagerInternal;
import com.android.server.usage.AppStandbyInternal;
@@ -102,6 +106,7 @@
.initMocks(this)
.strictness(Strictness.LENIENT)
.mockStatic(LocalServices.class)
+ .mockStatic(PermissionChecker.class)
.mockStatic(ServiceManager.class)
.startMocking();
@@ -193,6 +198,15 @@
jobInfoBuilder.build(), callingUid, "com.android.test", 0, testTag);
}
+ private void grantRunLongJobsPermission(boolean grant) {
+ final int permissionStatus = grant
+ ? PermissionChecker.PERMISSION_GRANTED : PermissionChecker.PERMISSION_HARD_DENIED;
+ doReturn(permissionStatus)
+ .when(() -> PermissionChecker.checkPermissionForPreflight(
+ any(), eq(android.Manifest.permission.RUN_LONG_JOBS),
+ anyInt(), anyInt(), anyString()));
+ }
+
@Test
public void testGetMinJobExecutionGuaranteeMs() {
JobStatus ejMax = createJobStatus("testGetMinJobExecutionGuaranteeMs",
@@ -207,6 +221,15 @@
createJobInfo(5).setPriority(JobInfo.PRIORITY_HIGH));
JobStatus jobDef = createJobStatus("testGetMinJobExecutionGuaranteeMs",
createJobInfo(6));
+ JobStatus jobDT = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+ createJobInfo(7)
+ .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ JobStatus jobUI = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+ createJobInfo(8)); // TODO(255371817): add setUserInitiated(true)
+ JobStatus jobUIDT = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+ // TODO(255371817): add setUserInitiated(true)
+ createJobInfo(9)
+ .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
spyOn(ejMax);
spyOn(ejHigh);
@@ -214,6 +237,9 @@
spyOn(ejHighDowngraded);
spyOn(jobHigh);
spyOn(jobDef);
+ spyOn(jobDT);
+ spyOn(jobUI);
+ spyOn(jobUIDT);
when(ejMax.shouldTreatAsExpeditedJob()).thenReturn(true);
when(ejHigh.shouldTreatAsExpeditedJob()).thenReturn(true);
@@ -221,6 +247,16 @@
when(ejHighDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false);
when(jobHigh.shouldTreatAsExpeditedJob()).thenReturn(false);
when(jobDef.shouldTreatAsExpeditedJob()).thenReturn(false);
+ when(jobUI.shouldTreatAsUserInitiated()).thenReturn(true);
+ when(jobUIDT.shouldTreatAsUserInitiated()).thenReturn(true);
+
+ ConnectivityController connectivityController = mService.getConnectivityController();
+ spyOn(connectivityController);
+ mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
+ mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS = 60 * MINUTE_IN_MILLIS;
+ mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
+ mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
+ mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(ejMax));
@@ -234,8 +270,81 @@
mService.getMinJobExecutionGuaranteeMs(jobHigh));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobDef));
+ grantRunLongJobsPermission(false); // Without permission
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobDT));
+ grantRunLongJobsPermission(true); // With permission
+ doReturn(ConnectivityController.UNKNOWN_TIME)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobDT));
+ doReturn(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS / 2)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobDT));
+ doReturn(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS * 2)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobDT));
+ doReturn(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS * 2)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobDT));
+ // UserInitiated
+ assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUI));
+ grantRunLongJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+ grantRunLongJobsPermission(true); // With permission
+ doReturn(ConnectivityController.UNKNOWN_TIME)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+ doReturn(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS / 2)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+ doReturn(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS * 2)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(
+ (long) (mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
+ * 2 * 1.5),
+ mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+ doReturn(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS * 2)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUIDT));
}
+ @Test
+ public void testGetMaxJobExecutionTimeMs() {
+ JobStatus jobDT = createJobStatus("testGetMaxJobExecutionTimeMs",
+ createJobInfo(7)
+ .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ JobStatus jobUI = createJobStatus("testGetMaxJobExecutionTimeMs",
+ createJobInfo(9)); // TODO(255371817): add setUserInitiated(true)
+ JobStatus jobUIDT = createJobStatus("testGetMaxJobExecutionTimeMs",
+ // TODO(255371817): add setUserInitiated(true)
+ createJobInfo(10)
+ .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+
+ spyOn(jobDT);
+ spyOn(jobUI);
+ spyOn(jobUIDT);
+
+ when(jobUI.shouldTreatAsUserInitiated()).thenReturn(true);
+ when(jobUIDT.shouldTreatAsUserInitiated()).thenReturn(true);
+
+ grantRunLongJobsPermission(true);
+
+ assertEquals(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobDT));
+ assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUI));
+ assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUIDT));
+ }
/**
* Confirm that {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int)}
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 1f85f2c..42e22f3 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
@@ -24,6 +24,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -35,6 +36,7 @@
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -240,20 +242,20 @@
.setLinkDownstreamBandwidthKbps(1).build(), mConstants));
// Slow downstream
assertFalse(controller.isSatisfied(createJobStatus(job), net,
- createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(137)
+ createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(140)
.setLinkDownstreamBandwidthKbps(1).build(), mConstants));
// Slow upstream
assertFalse(controller.isSatisfied(createJobStatus(job), net,
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(1)
- .setLinkDownstreamBandwidthKbps(137).build(), mConstants));
+ .setLinkDownstreamBandwidthKbps(140).build(), mConstants));
// Network good enough
assertTrue(controller.isSatisfied(createJobStatus(job), net,
- createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(137)
- .setLinkDownstreamBandwidthKbps(137).build(), mConstants));
+ createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(140)
+ .setLinkDownstreamBandwidthKbps(140).build(), mConstants));
// Network slightly too slow given reduced time
assertFalse(controller.isSatisfied(createJobStatus(job), net,
- createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(130)
- .setLinkDownstreamBandwidthKbps(130).build(), mConstants));
+ createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(139)
+ .setLinkDownstreamBandwidthKbps(139).build(), mConstants));
// Slow network is too slow, but device is charging and network is unmetered.
when(mService.isBatteryCharging()).thenReturn(true);
controller.onBatteryStateChangedLocked();
@@ -1188,6 +1190,78 @@
assertFalse(unnetworked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
}
+ @Test
+ public void testCalculateTransferTimeMs() {
+ assertEquals(ConnectivityController.UNKNOWN_TIME,
+ ConnectivityController.calculateTransferTimeMs(1, 0));
+ assertEquals(ConnectivityController.UNKNOWN_TIME,
+ ConnectivityController.calculateTransferTimeMs(JobInfo.NETWORK_BYTES_UNKNOWN, 512));
+ assertEquals(1, ConnectivityController.calculateTransferTimeMs(1, 8));
+ assertEquals(1000, ConnectivityController.calculateTransferTimeMs(1000, 8));
+ assertEquals(8, ConnectivityController.calculateTransferTimeMs(1024, 1024));
+ }
+
+ @Test
+ public void testGetEstimatedTransferTimeMs() {
+ final ArgumentCaptor<NetworkCallback> callbackCaptor =
+ ArgumentCaptor.forClass(NetworkCallback.class);
+ doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture());
+
+ final JobStatus job = createJobStatus(createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(10_000),
+ DataUnit.MEBIBYTES.toBytes(1_000))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
+
+ final JobStatus jobNoEstimates = createJobStatus(createJob());
+ assertEquals(ConnectivityController.UNKNOWN_TIME,
+ controller.getEstimatedTransferTimeMs(jobNoEstimates));
+
+ // No network
+ job.network = null;
+ assertEquals(ConnectivityController.UNKNOWN_TIME,
+ controller.getEstimatedTransferTimeMs(job));
+
+ final NetworkCallback generalCallback = callbackCaptor.getValue();
+
+ // No capabilities
+ final Network network = mock(Network.class);
+ answerNetwork(generalCallback, null, null, network, null);
+ job.network = network;
+ assertEquals(ConnectivityController.UNKNOWN_TIME,
+ controller.getEstimatedTransferTimeMs(job));
+
+ // Capabilities don't have bandwidth values
+ NetworkCapabilities caps = createCapabilitiesBuilder().build();
+ answerNetwork(generalCallback, null, null, network, caps);
+ assertEquals(ConnectivityController.UNKNOWN_TIME,
+ controller.getEstimatedTransferTimeMs(job));
+
+ // Capabilities only has downstream bandwidth
+ caps = createCapabilitiesBuilder()
+ .setLinkDownstreamBandwidthKbps(1024)
+ .build();
+ answerNetwork(generalCallback, null, null, network, caps);
+ assertEquals(81920 * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
+
+ // Capabilities only has upstream bandwidth
+ caps = createCapabilitiesBuilder()
+ .setLinkUpstreamBandwidthKbps(2 * 1024)
+ .build();
+ answerNetwork(generalCallback, null, null, network, caps);
+ assertEquals(4096 * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
+
+ // Capabilities only both stream bandwidths
+ caps = createCapabilitiesBuilder()
+ .setLinkDownstreamBandwidthKbps(1024)
+ .setLinkUpstreamBandwidthKbps(2 * 1024)
+ .build();
+ answerNetwork(generalCallback, null, null, network, caps);
+ assertEquals((81920 + 4096) * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
+ }
+
private void answerNetwork(@NonNull NetworkCallback generalCallback,
@Nullable NetworkCallback uidCallback, @Nullable Network lastNetwork,
@Nullable Network net, @Nullable NetworkCapabilities caps) {
@@ -1198,11 +1272,15 @@
}
} else {
generalCallback.onAvailable(net);
- generalCallback.onCapabilitiesChanged(net, caps);
+ if (caps != null) {
+ generalCallback.onCapabilitiesChanged(net, caps);
+ }
if (uidCallback != null) {
uidCallback.onAvailable(net);
uidCallback.onBlockedStatusChanged(net, ConnectivityManager.BLOCKED_REASON_NONE);
- uidCallback.onCapabilitiesChanged(net, caps);
+ if (caps != null) {
+ uidCallback.onCapabilitiesChanged(net, caps);
+ }
}
}
}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 9eb3b92..7149265 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -98,6 +98,7 @@
<uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK"/>
<uses-permission
android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
+ <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
<uses-permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY" />
<uses-permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt
new file mode 100644
index 0000000..c22782c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt
@@ -0,0 +1,150 @@
+/*
+ * 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.server.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManager
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.view.InputDevice
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
+
+private fun createKeyboard(deviceId: Int): InputDevice =
+ InputDevice.Builder()
+ .setId(deviceId)
+ .setName("Device $deviceId")
+ .setDescriptor("descriptor $deviceId")
+ .setSources(InputDevice.SOURCE_KEYBOARD)
+ .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+ .setExternal(true)
+ .build()
+
+/**
+ * Tests for {@link KeyRemapper}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:KeyRemapperTests
+ */
+@Presubmit
+class KeyRemapperTests {
+
+ companion object {
+ const val DEVICE_ID = 1
+ val REMAPPABLE_KEYS = intArrayOf(
+ KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_RIGHT,
+ KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_META_RIGHT,
+ KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT,
+ KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_RIGHT,
+ KeyEvent.KEYCODE_CAPS_LOCK
+ )
+ }
+
+ @get:Rule
+ val rule = MockitoJUnit.rule()!!
+
+ @Mock
+ private lateinit var iInputManager: IInputManager
+ @Mock
+ private lateinit var native: NativeInputManagerService
+ private lateinit var mKeyRemapper: KeyRemapper
+ private lateinit var context: Context
+ private lateinit var dataStore: PersistentDataStore
+ private lateinit var testLooper: TestLooper
+
+ @Before
+ fun setup() {
+ context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
+ override fun openRead(): InputStream? {
+ throw FileNotFoundException()
+ }
+
+ override fun startWrite(): FileOutputStream? {
+ throw IOException()
+ }
+
+ override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
+ })
+ testLooper = TestLooper()
+ mKeyRemapper = KeyRemapper(
+ context,
+ native,
+ dataStore,
+ testLooper.looper
+ )
+ val inputManager = InputManager.resetInstance(iInputManager)
+ Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+ .thenReturn(inputManager)
+ Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
+ }
+
+ @After
+ fun tearDown() {
+ InputManager.clearInstance()
+ }
+
+ @Test
+ fun testKeyRemapping() {
+ val keyboard = createKeyboard(DEVICE_ID)
+ Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard)
+
+ for (i in REMAPPABLE_KEYS.indices) {
+ val fromKeyCode = REMAPPABLE_KEYS[i]
+ val toKeyCode = REMAPPABLE_KEYS[(i + 1) % REMAPPABLE_KEYS.size]
+ mKeyRemapper.remapKey(fromKeyCode, toKeyCode)
+ testLooper.dispatchNext()
+ }
+
+ val remapping = mKeyRemapper.keyRemapping
+ val expectedSize = REMAPPABLE_KEYS.size
+ assertEquals("Remapping size should be $expectedSize", expectedSize, remapping.size)
+
+ for (i in REMAPPABLE_KEYS.indices) {
+ val fromKeyCode = REMAPPABLE_KEYS[i]
+ val toKeyCode = REMAPPABLE_KEYS[(i + 1) % REMAPPABLE_KEYS.size]
+ assertEquals(
+ "Remapping should include mapping from $fromKeyCode to $toKeyCode",
+ toKeyCode,
+ remapping.getOrDefault(fromKeyCode, -1)
+ )
+ }
+
+ mKeyRemapper.clearAllKeyRemappings()
+ testLooper.dispatchNext()
+
+ assertEquals(
+ "Remapping size should be 0 after clearAllModifierKeyRemappings",
+ 0,
+ mKeyRemapper.keyRemapping.size
+ )
+ }
+}
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 3bce860..1b77c95 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -20,24 +20,23 @@
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.window.BackNavigationInfo.typeToString;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.hardware.HardwareBuffer;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.WindowManager;
@@ -48,7 +47,6 @@
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedCallbackInfo;
import android.window.OnBackInvokedDispatcher;
-import android.window.TaskSnapshot;
import android.window.WindowOnBackInvokedDispatcher;
import com.android.server.LocalServices;
@@ -67,6 +65,7 @@
private BackNavigationController mBackNavigationController;
private WindowManagerInternal mWindowManagerInternal;
private BackAnimationAdapter mBackAnimationAdapter;
+ private Task mRootHomeTask;
@Before
public void setUp() throws Exception {
@@ -76,6 +75,7 @@
LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
mBackNavigationController.setWindowManager(mWm);
mBackAnimationAdapter = mock(BackAnimationAdapter.class);
+ mRootHomeTask = initHomeActivity();
}
@Test
@@ -101,7 +101,8 @@
ActivityRecord recordA = createActivityRecord(taskA);
Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any());
- withSystemCallback(createTopTaskWithActivity());
+ final Task topTask = createTopTaskWithActivity();
+ withSystemCallback(topTask);
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
assertThat(typeToString(backNavigationInfo.getType()))
@@ -111,6 +112,20 @@
verify(mBackNavigationController).scheduleAnimationLocked(
eq(BackNavigationInfo.TYPE_CROSS_TASK), any(), eq(mBackAnimationAdapter),
any());
+
+ // reset drawning status
+ topTask.forAllWindows(w -> {
+ makeWindowVisibleAndDrawn(w);
+ }, true);
+ setupKeyguardOccluded();
+ backNavigationInfo = startBackNavigation();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
+
+ doReturn(true).when(recordA).canShowWhenLocked();
+ backNavigationInfo = startBackNavigation();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK));
}
@Test
@@ -137,6 +152,20 @@
assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
assertThat(typeToString(backNavigationInfo.getType()))
.isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
+
+ // reset drawing status
+ testCase.recordFront.forAllWindows(w -> {
+ makeWindowVisibleAndDrawn(w);
+ }, true);
+ setupKeyguardOccluded();
+ backNavigationInfo = startBackNavigation();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
+
+ doReturn(true).when(testCase.recordBack).canShowWhenLocked();
+ backNavigationInfo = startBackNavigation();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
}
@Test
@@ -164,12 +193,17 @@
@Test
public void preparesForBackToHome() {
- Task task = createTopTaskWithActivity();
- withSystemCallback(task);
+ final Task topTask = createTopTaskWithActivity();
+ withSystemCallback(topTask);
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertThat(typeToString(backNavigationInfo.getType()))
.isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME));
+
+ setupKeyguardOccluded();
+ backNavigationInfo = startBackNavigation();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
}
@Test
@@ -302,14 +336,22 @@
};
}
- @NonNull
- private TaskSnapshotController createMockTaskSnapshotController() {
- TaskSnapshotController taskSnapshotController = mock(TaskSnapshotController.class);
- TaskSnapshot taskSnapshot = mock(TaskSnapshot.class);
- when(taskSnapshot.getHardwareBuffer()).thenReturn(mock(HardwareBuffer.class));
- when(taskSnapshotController.getSnapshot(anyInt(), anyInt(), anyBoolean(), anyBoolean()))
- .thenReturn(taskSnapshot);
- return taskSnapshotController;
+ private Task initHomeActivity() {
+ final Task task = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
+ task.forAllLeafTasks((t) -> {
+ if (t.getTopMostActivity() == null) {
+ final ActivityRecord r = createActivityRecord(t);
+ Mockito.doNothing().when(t).reparentSurfaceControl(any(), any());
+ Mockito.doNothing().when(r).reparentSurfaceControl(any(), any());
+ }
+ }, true);
+ return task;
+ }
+
+ private void setupKeyguardOccluded() {
+ final KeyguardController kc = mRootHomeTask.mTaskSupervisor.getKeyguardController();
+ doReturn(true).when(kc).isKeyguardLocked(anyInt());
+ doReturn(true).when(kc).isDisplayOccluded(anyInt());
}
@NonNull
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 435c39c..69e3244 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -413,6 +413,16 @@
}
@Test
+ public void testCanAffectSystemUiFlags_starting() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION_STARTING, "app");
+ app.mActivityRecord.setVisible(true);
+ app.mStartingData = new SnapshotStartingData(mWm, null, 0);
+ assertFalse(app.canAffectSystemUiFlags());
+ app.mStartingData = new SplashScreenStartingData(mWm, 0, 0);
+ assertTrue(app.canAffectSystemUiFlags());
+ }
+
+ @Test
public void testCanAffectSystemUiFlags_disallow() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
app.mActivityRecord.setVisible(true);
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
index 133c176..cc3781a 100644
--- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
@@ -246,6 +246,12 @@
}
@Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, Bundle options) {
+ sendBroadcast(intent);
+ }
+
+ @Override
public void sendStickyBroadcast(Intent intent) {
sendBroadcast(intent);
}